From aa11231ee5285016e7d326c834ba061401ac511d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 30 Sep 2020 11:07:35 +0100 Subject: [PATCH 001/187] prepare web release 3.1.0 (#1716) --- CHANGES.md | 25 +++++++++++++++++-------- Cargo.toml | 2 +- actix-web-codegen/CHANGES.md | 23 ++++++++++++----------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 86e022409..5fd3869f9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,13 +1,22 @@ # Changes ## Unreleased - 2020-xx-xx -### Changed -* Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allow `NormalizePath` - to keep the trailing slash's existance as it is. [#1695] -* Fix `ResourceMap` recursive references when printing/debugging. [#1708] -* Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` via `web::Data::from` [#1710] + +## 3.1.0 - 2020-09-29 +### Changed +* Add `TrailingSlash::MergeOnly` behaviour to `NormalizePath`, which allows `NormalizePath` + to retain any trailing slashes. [#1695] +* Remove bound `std::marker::Sized` from `web::Data` to support storing `Arc` + via `web::Data::from` [#1710] + +### Fixed +* `ResourceMap` debug printing is no longer infinitely recursive. [#1708] + +[#1695]: https://github.com/actix/actix-web/pull/1695 [#1708]: https://github.com/actix/actix-web/pull/1708 +[#1710]: https://github.com/actix/actix-web/pull/1710 + ## 3.0.2 - 2020-09-15 ### Fixed @@ -177,7 +186,7 @@ ### Deleted -* Delete HttpServer::run(), it is not useful witht async/await +* Delete HttpServer::run(), it is not useful with async/await ## [2.0.0-alpha.3] - 2019-12-07 @@ -222,7 +231,7 @@ ### Changed -* Make UrlEncodedError::Overflow more informativve +* Make UrlEncodedError::Overflow more informative * Use actix-testing for testing utils @@ -240,7 +249,7 @@ * Re-implement Host predicate (#989) -* Form immplements Responder, returning a `application/x-www-form-urlencoded` response +* Form implements Responder, returning a `application/x-www-form-urlencoded` response * Add `into_inner` to `Data` diff --git a/Cargo.toml b/Cargo.toml index d4da4af11..56158389c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.0.2" +version = "3.1.0" authors = ["Nikolay Kim "] description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust." readme = "README.md" diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index ad1a22b88..1ab51f924 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -5,7 +5,7 @@ ## 0.4.0 - 2020-09-20 * Added compile success and failure testing. [#1677] -* Add `route` macro for supporting multiple HTTP methods guards. +* Add `route` macro for supporting multiple HTTP methods guards. [#1674] [#1677]: https://github.com/actix/actix-web/pull/1677 [#1674]: https://github.com/actix/actix-web/pull/1674 @@ -21,47 +21,48 @@ [#1559]: https://github.com/actix/actix-web/pull/1559 -## [0.2.2] - 2020-05-23 +## 0.2.2 - 2020-05-23 * Add resource middleware on actix-web-codegen [#1467] [#1467]: https://github.com/actix/actix-web/pull/1467 -## [0.2.1] - 2020-02-25 + +## 0.2.1 - 2020-02-25 * Add `#[allow(missing_docs)]` attribute to generated structs [#1368] * Allow the handler function to be named as `config` [#1290] [#1368]: https://github.com/actix/actix-web/issues/1368 [#1290]: https://github.com/actix/actix-web/issues/1290 -## [0.2.0] - 2019-12-13 +## 0.2.0 - 2019-12-13 * Generate code for actix-web 2.0 -## [0.1.3] - 2019-10-14 +## 0.1.3 - 2019-10-14 * Bump up `syn` & `quote` to 1.0 * Provide better error message -## [0.1.2] - 2019-06-04 +## 0.1.2 - 2019-06-04 * Add macros for head, options, trace, connect and patch http methods -## [0.1.1] - 2019-06-01 +## 0.1.1 - 2019-06-01 * Add syn "extra-traits" feature -## [0.1.0] - 2019-05-18 +## 0.1.0 - 2019-05-18 * Release -## [0.1.0-beta.1] - 2019-04-20 +## 0.1.0-beta.1 - 2019-04-20 * Gen code for actix-web 1.0.0-beta.1 -## [0.1.0-alpha.6] - 2019-04-14 +## 0.1.0-alpha.6 - 2019-04-14 * Gen code for actix-web 1.0.0-alpha.6 -## [0.1.0-alpha.1] - 2019-03-28 +## 0.1.0-alpha.1 - 2019-03-28 * Initial impl From c2c71cc6269fa5a432a51e2802be5f2327d7633a Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 1 Oct 2020 18:19:09 +0900 Subject: [PATCH 002/187] Fix/suppress clippy warnings (#1720) --- actix-http/src/lib.rs | 1 + src/lib.rs | 1 + src/scope.rs | 1 - src/types/json.rs | 2 +- src/types/payload.rs | 2 +- 5 files changed, 4 insertions(+), 3 deletions(-) diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index b52e8179c..fab91be2b 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -7,6 +7,7 @@ clippy::new_without_default, clippy::borrow_interior_mutable_const )] +#![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42). #[macro_use] extern crate log; diff --git a/src/lib.rs b/src/lib.rs index 327cba954..edc8456ba 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,6 +67,7 @@ #![deny(rust_2018_idioms)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] +#![allow(clippy::rc_buffer)] // FXIME: We should take a closer look for the warnings at some point. #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/src/scope.rs b/src/scope.rs index 25b5366d8..2520fd7ae 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -58,7 +58,6 @@ type BoxedResponse = LocalBoxFuture<'static, Result>; /// * /{project_id}/path1 - responds to all http method /// * /{project_id}/path2 - `GET` requests /// * /{project_id}/path3 - `HEAD` requests -/// pub struct Scope { endpoint: T, rdef: String, diff --git a/src/types/json.rs b/src/types/json.rs index 8da5a3bdb..081a022e8 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -283,7 +283,7 @@ impl JsonConfig { fn from_req(req: &HttpRequest) -> &Self { req.app_data::() .or_else(|| req.app_data::>().map(|d| d.as_ref())) - .unwrap_or_else(|| &DEFAULT_CONFIG) + .unwrap_or(&DEFAULT_CONFIG) } } diff --git a/src/types/payload.rs b/src/types/payload.rs index bbdd89525..4ff5ef4b4 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -284,7 +284,7 @@ impl PayloadConfig { fn from_req(req: &HttpRequest) -> &Self { req.app_data::() .or_else(|| req.app_data::>().map(|d| d.as_ref())) - .unwrap_or_else(|| &DEFAULT_CONFIG) + .unwrap_or(&DEFAULT_CONFIG) } } From 26c1a901d9bef752a1f6afe3af6adcba1ca78ce7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 6 Oct 2020 21:56:28 +0100 Subject: [PATCH 003/187] add files preference for utf8 text responses (#1714) --- actix-files/CHANGES.md | 8 +++ actix-files/src/encoding.rs | 52 ++++++++++++++++++++ actix-files/src/files.rs | 13 ++++- actix-files/src/lib.rs | 10 ++-- actix-files/src/named.rs | 91 ++++++++++++++++++++++------------- actix-files/tests/encoding.rs | 40 +++++++++++++++ actix-files/tests/utf8.txt | 3 ++ rust-toolchain | 1 - 8 files changed, 177 insertions(+), 41 deletions(-) create mode 100644 actix-files/src/encoding.rs create mode 100644 actix-files/tests/encoding.rs create mode 100644 actix-files/tests/utf8.txt delete mode 100644 rust-toolchain diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 75d616ff9..271476d91 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,14 @@ # Changes ## [Unreleased] - 2020-xx-xx +* Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] + +[#1714]: https://github.com/actix/actix-web/pull/1714 + + +## [0.3.0] - 2020-09-11 +* No significant changes from 0.3.0-beta.1. + ## [0.3.0-beta.1] - 2020-07-15 * Update `v_htmlescape` to 0.10 diff --git a/actix-files/src/encoding.rs b/actix-files/src/encoding.rs new file mode 100644 index 000000000..95997e313 --- /dev/null +++ b/actix-files/src/encoding.rs @@ -0,0 +1,52 @@ +use mime::Mime; + +/// Transforms MIME `text/*` types into their UTF-8 equivalent, if supported. +/// +/// MIME types that are converted +/// - application/javascript +/// - text/html +/// - text/css +/// - text/plain +/// - text/csv +/// - text/tab-separated-values +pub(crate) fn equiv_utf8_text(ct: Mime) -> Mime { + // use (roughly) order of file-type popularity for a web server + + if ct == mime::APPLICATION_JAVASCRIPT { + return mime::APPLICATION_JAVASCRIPT_UTF_8; + } + + if ct == mime::TEXT_HTML { + return mime::TEXT_HTML_UTF_8; + } + + if ct == mime::TEXT_CSS { + return mime::TEXT_CSS_UTF_8; + } + + if ct == mime::TEXT_PLAIN { + return mime::TEXT_PLAIN_UTF_8; + } + + if ct == mime::TEXT_CSV { + return mime::TEXT_CSV_UTF_8; + } + + if ct == mime::TEXT_TAB_SEPARATED_VALUES { + return mime::TEXT_TAB_SEPARATED_VALUES_UTF_8; + } + + ct +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_equiv_utf8_text() { + assert_eq!(equiv_utf8_text(mime::TEXT_PLAIN), mime::TEXT_PLAIN_UTF_8); + assert_eq!(equiv_utf8_text(mime::TEXT_XML), mime::TEXT_XML); + assert_eq!(equiv_utf8_text(mime::IMAGE_PNG), mime::IMAGE_PNG); + } +} diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 2b55e1aa9..5a783e2dd 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -138,24 +138,33 @@ impl Files { self } - #[inline] /// Specifies whether to use ETag or not. /// /// Default is true. + #[inline] pub fn use_etag(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::ETAG, value); self } - #[inline] /// Specifies whether to use Last-Modified or not. /// /// Default is true. + #[inline] pub fn use_last_modified(mut self, value: bool) -> Self { self.file_flags.set(named::Flags::LAST_MD, value); self } + /// Specifies whether text responses should signal a UTF-8 encoding. + /// + /// Default is false (but will default to true in a future version). + #[inline] + pub fn prefer_utf8(mut self, value: bool) -> Self { + self.file_flags.set(named::Flags::PREFER_UTF8, value); + self + } + /// Specifies custom guards to use for directory listings and files. /// /// Default behaviour allows GET and HEAD. diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 1fc7cb3f3..120345c40 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -8,12 +8,8 @@ //! use actix_files::Files; //! //! let app = App::new() -//! .service(Files::new("/static", ".")); +//! .service(Files::new("/static", ".").prefer_utf8(true)); //! ``` -//! -//! # Implementation Quirks -//! - If a filename contains non-ascii characters, that file will be served with the `charset=utf-8` -//! extension on the Content-Type header. #![deny(rust_2018_idioms)] #![warn(missing_docs, missing_debug_implementations)] @@ -30,6 +26,7 @@ use mime_guess::from_ext; mod chunked; mod directory; +mod encoding; mod error; mod files; mod named; @@ -93,6 +90,9 @@ mod tests { #[actix_rt::test] async fn test_file_extension_to_mime() { + let m = file_extension_to_mime(""); + assert_eq!(m, mime::APPLICATION_OCTET_STREAM); + let m = file_extension_to_mime("jpg"); assert_eq!(m, mime::IMAGE_JPEG); diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 3caa4a809..dacb51136 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -22,20 +22,21 @@ use bitflags::bitflags; use futures_util::future::{ready, Ready}; use mime_guess::from_path; -use crate::range::HttpRange; use crate::ChunkedReadFile; +use crate::{encoding::equiv_utf8_text, range::HttpRange}; bitflags! { pub(crate) struct Flags: u8 { - const ETAG = 0b0000_0001; - const LAST_MD = 0b0000_0010; + const ETAG = 0b0000_0001; + const LAST_MD = 0b0000_0010; const CONTENT_DISPOSITION = 0b0000_0100; + const PREFER_UTF8 = 0b0000_1000; } } impl Default for Flags { fn default() -> Self { - Flags::all() + Flags::from_bits_truncate(0b0000_0111) } } @@ -92,6 +93,7 @@ impl NamedFile { }; let ct = from_path(&path).first_or_octet_stream(); + let disposition = match ct.type_() { mime::IMAGE | mime::TEXT | mime::VIDEO => DispositionType::Inline, _ => DispositionType::Attachment, @@ -215,24 +217,33 @@ impl NamedFile { self } - #[inline] - ///Specifies whether to use ETag or not. + /// Specifies whether to use ETag or not. /// - ///Default is true. + /// Default is true. + #[inline] pub fn use_etag(mut self, value: bool) -> Self { self.flags.set(Flags::ETAG, value); self } - #[inline] - ///Specifies whether to use Last-Modified or not. + /// Specifies whether to use Last-Modified or not. /// - ///Default is true. + /// Default is true. + #[inline] pub fn use_last_modified(mut self, value: bool) -> Self { self.flags.set(Flags::LAST_MD, value); self } + /// Specifies whether text responses should signal a UTF-8 encoding. + /// + /// Default is false (but will default to true in a future version). + #[inline] + pub fn prefer_utf8(mut self, value: bool) -> Self { + self.flags.set(Flags::PREFER_UTF8, value); + self + } + pub(crate) fn etag(&self) -> Option { // This etag format is similar to Apache's. self.modified.as_ref().map(|mtime| { @@ -268,18 +279,24 @@ impl NamedFile { /// Creates an `HttpResponse` with file as a streaming body. pub fn into_response(self, req: &HttpRequest) -> Result { if self.status_code != StatusCode::OK { - let mut resp = HttpResponse::build(self.status_code); + let mut res = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { - res.header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - }); + if self.flags.contains(Flags::PREFER_UTF8) { + let ct = equiv_utf8_text(self.content_type.clone()); + res.header(header::CONTENT_TYPE, ct.to_string()); + } else { + res.header(header::CONTENT_TYPE, self.content_type.to_string()); + } + + if self.flags.contains(Flags::CONTENT_DISPOSITION) { + res.header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + } if let Some(current_encoding) = self.encoding { - resp.encoding(current_encoding); + res.encoding(current_encoding); } let reader = ChunkedReadFile { @@ -290,7 +307,7 @@ impl NamedFile { counter: 0, }; - return Ok(resp.streaming(reader)); + return Ok(res.streaming(reader)); } let etag = if self.flags.contains(Flags::ETAG) { @@ -342,25 +359,33 @@ impl NamedFile { }; let mut resp = HttpResponse::build(self.status_code); - resp.set(header::ContentType(self.content_type.clone())) - .if_true(self.flags.contains(Flags::CONTENT_DISPOSITION), |res| { - res.header( - header::CONTENT_DISPOSITION, - self.content_disposition.to_string(), - ); - }); + + if self.flags.contains(Flags::PREFER_UTF8) { + let ct = equiv_utf8_text(self.content_type.clone()); + resp.header(header::CONTENT_TYPE, ct.to_string()); + } else { + resp.header(header::CONTENT_TYPE, self.content_type.to_string()); + } + + if self.flags.contains(Flags::CONTENT_DISPOSITION) { + resp.header( + header::CONTENT_DISPOSITION, + self.content_disposition.to_string(), + ); + } // default compressing if let Some(current_encoding) = self.encoding { resp.encoding(current_encoding); } - resp.if_some(last_modified, |lm, resp| { - resp.set(header::LastModified(lm)); - }) - .if_some(etag, |etag, resp| { - resp.set(header::ETag(etag)); - }); + if let Some(lm) = last_modified { + resp.header(header::LAST_MODIFIED, lm.to_string()); + } + + if let Some(etag) = etag { + resp.header(header::ETAG, etag.to_string()); + } resp.header(header::ACCEPT_RANGES, "bytes"); diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs new file mode 100644 index 000000000..d7e01b305 --- /dev/null +++ b/actix-files/tests/encoding.rs @@ -0,0 +1,40 @@ +use actix_files::Files; +use actix_web::{ + http::{ + header::{self, HeaderValue}, + StatusCode, + }, + test::{self, TestRequest}, + App, +}; + +#[actix_rt::test] +async fn test_utf8_file_contents() { + // use default ISO-8859-1 encoding + let mut srv = + test::init_service(App::new().service(Files::new("/", "./tests"))).await; + + let req = TestRequest::with_uri("/utf8.txt").to_request(); + let res = test::call_service(&mut srv, req).await; + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(header::CONTENT_TYPE), + Some(&HeaderValue::from_static("text/plain")), + ); + + // prefer UTF-8 encoding + let mut srv = test::init_service( + App::new().service(Files::new("/", "./tests").prefer_utf8(true)), + ) + .await; + + let req = TestRequest::with_uri("/utf8.txt").to_request(); + let res = test::call_service(&mut srv, req).await; + + assert_eq!(res.status(), StatusCode::OK); + assert_eq!( + res.headers().get(header::CONTENT_TYPE), + Some(&HeaderValue::from_static("text/plain; charset=utf-8")), + ); +} diff --git a/actix-files/tests/utf8.txt b/actix-files/tests/utf8.txt new file mode 100644 index 000000000..d8590e2f5 --- /dev/null +++ b/actix-files/tests/utf8.txt @@ -0,0 +1,3 @@ +中文内容显示正确。 + +English is OK. diff --git a/rust-toolchain b/rust-toolchain deleted file mode 100644 index a50908ca3..000000000 --- a/rust-toolchain +++ /dev/null @@ -1 +0,0 @@ -1.42.0 From 34b23f31c93b9b8d02a211636b62133d9fc1f769 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 6 Oct 2020 22:08:33 +0100 Subject: [PATCH 004/187] prepare files release 0.4.0 --- actix-files/CHANGES.md | 56 +++++++++++++++++++++--------------------- actix-files/Cargo.toml | 4 +-- actix-files/README.md | 24 ++++++++++++------ actix-files/src/lib.rs | 2 +- 4 files changed, 48 insertions(+), 38 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 271476d91..978d1c69d 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,20 +1,24 @@ # Changes -## [Unreleased] - 2020-xx-xx +## Unreleased - 2020-xx-xx + + +## 0.4.0 - 2020-10-06 * Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] [#1714]: https://github.com/actix/actix-web/pull/1714 -## [0.3.0] - 2020-09-11 +## 0.3.0 - 2020-09-11 * No significant changes from 0.3.0-beta.1. -## [0.3.0-beta.1] - 2020-07-15 +## 0.3.0-beta.1 - 2020-07-15 * Update `v_htmlescape` to 0.10 * Update `actix-web` and `actix-http` dependencies to beta.1 -## [0.3.0-alpha.1] - 2020-05-23 + +## 0.3.0-alpha.1 - 2020-05-23 * Update `actix-web` and `actix-http` dependencies to alpha * Fix some typos in the docs * Bump minimum supported Rust version to 1.40 @@ -22,77 +26,73 @@ [#1384]: https://github.com/actix/actix-web/pull/1384 -## [0.2.1] - 2019-12-22 +## 0.2.1 - 2019-12-22 * Use the same format for file URLs regardless of platforms -## [0.2.0] - 2019-12-20 +## 0.2.0 - 2019-12-20 * Fix BodyEncoding trait import #1220 -## [0.2.0-alpha.1] - 2019-12-07 +## 0.2.0-alpha.1 - 2019-12-07 * Migrate to `std::future` -## [0.1.7] - 2019-11-06 -* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) - -## [0.1.6] - 2019-10-14 +## 0.1.7 - 2019-11-06 +* Add an additional `filename*` param in the `Content-Disposition` header of + `actix_files::NamedFile` to be more compatible. (#1151) +## 0.1.6 - 2019-10-14 * Add option to redirect to a slash-ended path `Files` #1132 -## [0.1.5] - 2019-10-08 +## 0.1.5 - 2019-10-08 * Bump up `mime_guess` crate version to 2.0.1 - * Bump up `percent-encoding` crate version to 2.1 - * Allow user defined request guards for `Files` #1113 -## [0.1.4] - 2019-07-20 +## 0.1.4 - 2019-07-20 * Allow to disable `Content-Disposition` header #686 -## [0.1.3] - 2019-06-28 +## 0.1.3 - 2019-06-28 * Do not set `Content-Length` header, let actix-http set it #930 -## [0.1.2] - 2019-06-13 +## 0.1.2 - 2019-06-13 * Content-Length is 0 for NamedFile HEAD request #914 - * Fix ring dependency from actix-web default features for #741 -## [0.1.1] - 2019-06-01 +## 0.1.1 - 2019-06-01 * Static files are incorrectly served as both chunked and with length #812 -## [0.1.0] - 2019-05-25 -* NamedFile last-modified check always fails due to nano-seconds - in file modified date #820 +## 0.1.0 - 2019-05-25 +* NamedFile last-modified check always fails due to nano-seconds in file modified date #820 -## [0.1.0-beta.4] - 2019-05-12 +## 0.1.0-beta.4 - 2019-05-12 * Update actix-web to beta.4 -## [0.1.0-beta.1] - 2019-04-20 +## 0.1.0-beta.1 - 2019-04-20 * Update actix-web to beta.1 -## [0.1.0-alpha.6] - 2019-04-14 +## 0.1.0-alpha.6 - 2019-04-14 * Update actix-web to alpha6 -## [0.1.0-alpha.4] - 2019-04-08 +## 0.1.0-alpha.4 - 2019-04-08 * Update actix-web to alpha4 -## [0.1.0-alpha.2] - 2019-04-02 +## 0.1.0-alpha.2 - 2019-04-02 * Add default handler support -## [0.1.0-alpha.1] - 2019-03-28 +## 0.1.0-alpha.1 - 2019-03-28 * Initial impl diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 634296c56..c829887ba 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-files" -version = "0.3.0" +version = "0.4.0" authors = ["Nikolay Kim "] -description = "Static files support for actix web." +description = "Static file serving for Actix Web" readme = "README.md" keywords = ["actix", "http", "async", "futures"] homepage = "https://actix.rs" diff --git a/actix-files/README.md b/actix-files/README.md index 5a5a62083..d31439361 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -1,9 +1,19 @@ -# Static files support for actix web [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-files)](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) +# actix-files -## Documentation & community resources +> Static file serving for Actix Web -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-files/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-files](https://crates.io/crates/actix-files) -* Minimum supported Rust version: 1.40 or later +[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-files) +[![Documentation](https://docs.rs/actix-files/badge.svg)](https://docs.rs/actix-files) +[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +![License](https://img.shields.io/crates/l/actix-files.svg) +
+[![dependency status](https://deps.rs/crate/actix-files/0.4.0/status.svg)](https://deps.rs/crate/actix-files/0.4.0) +[![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) + +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-files/) +- [Example Project](https://github.com/actix/examples/tree/master/static_index) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum supported Rust version: 1.42 or later diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 120345c40..662fba0a3 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -1,4 +1,4 @@ -//! Static files support for Actix Web. +//! Static file serving for Actix Web. //! //! Provides a non-blocking service for serving static files from disk. //! From d765e9099dbda47831a72d3fa149ec03f4e37576 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sat, 10 Oct 2020 02:26:05 +0200 Subject: [PATCH 005/187] Fix clippy::rc_buffer (#1728) --- src/app.rs | 4 ++-- src/app_service.rs | 26 +++++++++++++------------- src/config.rs | 4 ++-- src/lib.rs | 1 - src/route.rs | 2 ++ src/scope.rs | 23 ++++++++++++----------- 6 files changed, 31 insertions(+), 29 deletions(-) diff --git a/src/app.rs b/src/app.rs index fdedb0a75..6a4b97b69 100644 --- a/src/app.rs +++ b/src/app.rs @@ -459,8 +459,8 @@ where { fn into_factory(self) -> AppInit { AppInit { - data: Rc::new(self.data), - data_factories: Rc::new(self.data_factories), + data: self.data.into_boxed_slice().into(), + data_factories: self.data_factories.into_boxed_slice().into(), endpoint: self.endpoint, services: Rc::new(RefCell::new(self.services)), external: RefCell::new(self.external), diff --git a/src/app_service.rs b/src/app_service.rs index 98d8c8a8d..e5f8dd9cf 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -39,8 +39,8 @@ where { pub(crate) endpoint: T, pub(crate) extensions: RefCell>, - pub(crate) data: Rc>>, - pub(crate) data_factories: Rc>, + pub(crate) data: Rc<[Box]>, + pub(crate) data_factories: Rc<[FnDataFactory]>, pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, @@ -88,15 +88,15 @@ where // complete pipeline creation *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { default, - services: Rc::new( - services - .into_iter() - .map(|(mut rdef, srv, guards, nested)| { - rmap.add(&mut rdef, nested); - (rdef, srv, RefCell::new(guards)) - }) - .collect(), - ), + services: services + .into_iter() + .map(|(mut rdef, srv, guards, nested)| { + rmap.add(&mut rdef, nested); + (rdef, srv, RefCell::new(guards)) + }) + .collect::>() + .into_boxed_slice() + .into(), }); // external resources @@ -147,7 +147,7 @@ where rmap: Rc, config: AppConfig, - data: Rc>>, + data: Rc<[Box]>, extensions: Option, _t: PhantomData, @@ -273,7 +273,7 @@ where } pub struct AppRoutingFactory { - services: Rc>)>>, + services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, default: Rc, } diff --git a/src/config.rs b/src/config.rs index 0f49288ec..f7bebb4c5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -31,7 +31,7 @@ pub struct AppService { Option, Option>, )>, - service_data: Rc>>, + service_data: Rc<[Box]>, } impl AppService { @@ -39,7 +39,7 @@ impl AppService { pub(crate) fn new( config: AppConfig, default: Rc, - service_data: Rc>>, + service_data: Rc<[Box]>, ) -> Self { AppService { config, diff --git a/src/lib.rs b/src/lib.rs index edc8456ba..327cba954 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -67,7 +67,6 @@ #![deny(rust_2018_idioms)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] -#![allow(clippy::rc_buffer)] // FXIME: We should take a closer look for the warnings at some point. #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/src/route.rs b/src/route.rs index 129a67332..e9e9d1f5d 100644 --- a/src/route.rs +++ b/src/route.rs @@ -1,3 +1,5 @@ +#![allow(clippy::rc_buffer)] // inner value is mutated before being shared (`Rc::get_mut`) + use std::future::Future; use std::pin::Pin; use std::rc::Rc; diff --git a/src/scope.rs b/src/scope.rs index 2520fd7ae..1c5d8700b 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -442,16 +442,17 @@ where *self.factory_ref.borrow_mut() = Some(ScopeFactory { data: self.data.take().map(Rc::new), default: self.default.clone(), - services: Rc::new( - cfg.into_services() - .1 - .into_iter() - .map(|(mut rdef, srv, guards, nested)| { - rmap.add(&mut rdef, nested); - (rdef, srv, RefCell::new(guards)) - }) - .collect(), - ), + services: cfg + .into_services() + .1 + .into_iter() + .map(|(mut rdef, srv, guards, nested)| { + rmap.add(&mut rdef, nested); + (rdef, srv, RefCell::new(guards)) + }) + .collect::>() + .into_boxed_slice() + .into(), }); // get guards @@ -473,7 +474,7 @@ where pub struct ScopeFactory { data: Option>, - services: Rc>)>>, + services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, default: Rc>>>, } From 2f8491414604c397754c9ab18c0028aa46002194 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 19 Oct 2020 11:52:05 +0900 Subject: [PATCH 006/187] Skip some tests that cause ICE on nightly (#1740) --- actix-web-codegen/tests/trybuild.rs | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 1bc2bd25e..6c7c58986 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -6,9 +6,8 @@ fn compile_macros() { t.compile_fail("tests/trybuild/simple-fail.rs"); t.pass("tests/trybuild/route-ok.rs"); - t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); - t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); + test_route_duplicate_unexpected_method(&t); test_route_missing_method(&t) } @@ -25,3 +24,13 @@ fn test_route_missing_method(t: &trybuild::TestCases) { #[rustversion::nightly] fn test_route_missing_method(_t: &trybuild::TestCases) {} + +// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed. +#[rustversion::not(nightly)] +fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) { + t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); + t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); +} + +#[rustversion::nightly] +fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {} From cfd5b381f13937bb00be6bc427c8687a5f5b9732 Mon Sep 17 00:00:00 2001 From: Matt Gathu Date: Mon, 19 Oct 2020 08:18:16 +0200 Subject: [PATCH 007/187] Implement Logger middleware regex exclude pattern (#1723) Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ Cargo.toml | 2 +- src/middleware/logger.rs | 41 ++++++++++++++++++++++++++++++++++++++-- 3 files changed, 42 insertions(+), 3 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5fd3869f9..ea77607c7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx +* Implement Logger middleware regex exclude pattern [#1723] +[#1723]: https://github.com/actix/actix-web/pull/1723 ## 3.1.0 - 2020-09-29 ### Changed diff --git a/Cargo.toml b/Cargo.toml index 56158389c..3960b4d36 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -91,7 +91,7 @@ log = "0.4" mime = "0.3" socket2 = "0.3" pin-project = "0.4.17" -regex = "1.3" +regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.6.1" diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 51d4722d7..9a38d345b 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -13,7 +13,7 @@ use actix_service::{Service, Transform}; use bytes::Bytes; use futures_util::future::{ok, Ready}; use log::debug; -use regex::Regex; +use regex::{Regex, RegexSet}; use time::OffsetDateTime; use crate::dev::{BodySize, MessageBody, ResponseBody}; @@ -92,6 +92,7 @@ pub struct Logger(Rc); struct Inner { format: Format, exclude: HashSet, + exclude_regex: RegexSet, } impl Logger { @@ -100,6 +101,7 @@ impl Logger { Logger(Rc::new(Inner { format: Format::new(format), exclude: HashSet::new(), + exclude_regex: RegexSet::empty(), })) } @@ -111,6 +113,16 @@ impl Logger { .insert(path.into()); self } + + /// Ignore and do not log access info for paths that match regex + pub fn exclude_regex>(mut self, path: T) -> Self { + let inner = Rc::get_mut(&mut self.0).unwrap(); + let mut patterns = inner.exclude_regex.patterns().to_vec(); + patterns.push(path.into()); + let regex_set = RegexSet::new(patterns).unwrap(); + inner.exclude_regex = regex_set; + self + } } impl Default for Logger { @@ -123,6 +135,7 @@ impl Default for Logger { Logger(Rc::new(Inner { format: Format::default(), exclude: HashSet::new(), + exclude_regex: RegexSet::empty(), })) } } @@ -168,7 +181,9 @@ where } fn call(&mut self, req: ServiceRequest) -> Self::Future { - if self.inner.exclude.contains(req.path()) { + if self.inner.exclude.contains(req.path()) + || self.inner.exclude_regex.is_match(req.path()) + { LoggerResponse { fut: self.service.call(req), format: None, @@ -538,6 +553,28 @@ mod tests { let _res = srv.call(req).await; } + #[actix_rt::test] + async fn test_logger_exclude_regex() { + let srv = |req: ServiceRequest| { + ok(req.into_response( + HttpResponse::build(StatusCode::OK) + .header("X-Test", "ttt") + .finish(), + )) + }; + let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test") + .exclude_regex("\\w"); + + let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + + let req = TestRequest::with_header( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + ) + .to_srv_request(); + let _res = srv.call(req).await.unwrap(); + } + #[actix_rt::test] async fn test_url_path() { let mut format = Format::new("%T %U"); From e563025b1644be0d332eba381745fd47e5b626ef Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 19 Oct 2020 12:51:30 +0100 Subject: [PATCH 008/187] always construct shortslice using debug checked new constructor (#1741) --- actix-http/src/ws/mask.rs | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 726b1a4a1..d37d57eb1 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -4,7 +4,9 @@ use std::ptr::copy_nonoverlapping; use std::slice; // Holds a slice guaranteed to be shorter than 8 bytes -struct ShortSlice<'a>(&'a mut [u8]); +struct ShortSlice<'a> { + inner: &'a mut [u8], +} impl<'a> ShortSlice<'a> { /// # Safety @@ -12,10 +14,11 @@ impl<'a> ShortSlice<'a> { unsafe fn new(slice: &'a mut [u8]) -> Self { // Sanity check for debug builds debug_assert!(slice.len() < 8); - ShortSlice(slice) + ShortSlice { inner: slice } } + fn len(&self) -> usize { - self.0.len() + self.inner.len() } } @@ -56,7 +59,7 @@ pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { fn xor_short(buf: ShortSlice<'_>, mask: u64) { // SAFETY: we know that a `ShortSlice` fits in a u64 unsafe { - let (ptr, len) = (buf.0.as_mut_ptr(), buf.0.len()); + let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len()); let mut b: u64 = 0; #[allow(trivial_casts)] copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); @@ -96,7 +99,13 @@ fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) { // SAFETY: we know the middle section is correctly aligned, and the outer // sections are smaller than 8 bytes - unsafe { (ShortSlice::new(head), cast_slice(mid), ShortSlice(tail)) } + unsafe { + ( + ShortSlice::new(head), + cast_slice(mid), + ShortSlice::new(tail), + ) + } } else { // We didn't cross even one aligned boundary! From f92742bdacfab73b4df3b5c995ad67290a311f8f Mon Sep 17 00:00:00 2001 From: ghizzo01 <36126125+ghizzo01@users.noreply.github.com> Date: Mon, 19 Oct 2020 19:24:22 +0200 Subject: [PATCH 009/187] Bump base64 to 0.13 (#1744) --- actix-http/CHANGES.md | 2 +- actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 2 +- awc/Cargo.toml | 2 +- test-server/CHANGES.md | 1 + test-server/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 5 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6a98c4ca7..b72a7801a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,7 +1,7 @@ # Changes ## Unreleased - 2020-xx-xx - +* Upgrade `base64` to `0.13`. ## 2.0.0 - 2020-09-11 * No significant changes from `2.0.0-beta.4`. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0bbde881d..a4c100b92 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -49,7 +49,7 @@ actix-threadpool = "0.3.1" actix-tls = { version = "2.0.0", optional = true } actix = { version = "0.10.0", optional = true } -base64 = "0.12" +base64 = "0.13" bitflags = "1.2" bytes = "0.5.3" cookie = { version = "0.14.1", features = ["percent-encode"] } diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 07a469746..8babba113 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,7 +1,7 @@ # Changes ## Unreleased - 2020-xx-xx - +* Upgrade `base64` to `0.13`. ## 2.0.0 - 2020-09-11 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index c67b6ba6f..b8cf53e06 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -42,7 +42,7 @@ actix-service = "1.0.6" actix-http = "2.0.0" actix-rt = "1.0.0" -base64 = "0.12" +base64 = "0.13" bytes = "0.5.3" derive_more = "0.99.2" futures-core = { version = "0.3.5", default-features = false } diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 0a11e2cae..e3a59e5bf 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2020-xx-xx * add ability to set address for `TestServer` [#1645] +* Upgrade `base64` to `0.13`. [#1645]: https://github.com/actix/actix-web/pull/1645 diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index d06bd5dec..3ee6b8a30 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -38,7 +38,7 @@ actix-server = "1.0.0" actix-testing = "1.0.0" awc = "2.0.0" -base64 = "0.12" +base64 = "0.13" bytes = "0.5.3" futures-core = { version = "0.3.5", default-features = false } http = "0.2.0" From 98243db9f102627550bacbeced0f2b197a0ad51a Mon Sep 17 00:00:00 2001 From: cquintana-verbio <51116651+cquintana-verbio@users.noreply.github.com> Date: Tue, 20 Oct 2020 18:35:34 +0200 Subject: [PATCH 010/187] Print unconfigured `Data` type when attempting extraction (#1743) Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/data.rs | 6 ++++-- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ea77607c7..b3d3c180c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2020-xx-xx * Implement Logger middleware regex exclude pattern [#1723] +* Print unconfigured `Data` type when attempting extraction. [#1743] [#1723]: https://github.com/actix/actix-web/pull/1723 +[#1743]: https://github.com/actix/actix-web/pull/1743 ## 3.1.0 - 2020-09-29 ### Changed diff --git a/src/data.rs b/src/data.rs index 6405fd901..01d36569b 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,3 +1,4 @@ +use std::any::type_name; use std::ops::Deref; use std::sync::Arc; @@ -121,8 +122,9 @@ impl FromRequest for Data { } else { log::debug!( "Failed to construct App-level Data extractor. \ - Request path: {:?}", - req.path() + Request path: {:?} (type: {})", + req.path(), + type_name::(), ); err(ErrorInternalServerError( "App data is not configured, to configure use App::data()", From d45a1aa6b6e805e07d1a92c230b368342c9a2ec8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 24 Oct 2020 18:49:50 +0100 Subject: [PATCH 011/187] Add `web::ReqData` extractor (#1748) Co-authored-by: Jonas Platte --- CHANGES.md | 10 ++- src/data.rs | 33 +++------ src/lib.rs | 1 + src/request_data.rs | 175 ++++++++++++++++++++++++++++++++++++++++++++ src/web.rs | 1 + 5 files changed, 197 insertions(+), 23 deletions(-) create mode 100644 src/request_data.rs diff --git a/CHANGES.md b/CHANGES.md index b3d3c180c..9ea3a2094 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,17 @@ # Changes ## Unreleased - 2020-xx-xx -* Implement Logger middleware regex exclude pattern [#1723] -* Print unconfigured `Data` type when attempting extraction. [#1743] +### Added +* Implement `exclude_regex` for Logger middleware. [#1723] +* Add request-local data extractor `web::ReqData`. [#1748] + +### Changed +* Print non-configured `Data` type when attempting extraction. [#1743] [#1723]: https://github.com/actix/actix-web/pull/1723 [#1743]: https://github.com/actix/actix-web/pull/1743 +[#1748]: https://github.com/actix/actix-web/pull/1748 + ## 3.1.0 - 2020-09-29 ### Changed diff --git a/src/data.rs b/src/data.rs index 01d36569b..19c258ff0 100644 --- a/src/data.rs +++ b/src/data.rs @@ -20,25 +20,20 @@ pub(crate) type FnDataFactory = /// Application data. /// -/// Application data is an arbitrary data attached to the app. -/// Application data is available to all routes and could be added -/// during application configuration process -/// with `App::data()` method. +/// Application level data is a piece of arbitrary data attached to the app, scope, or resource. +/// Application data is available to all routes and can be added during the application +/// configuration process via `App::data()`. /// -/// Application data could be accessed by using `Data` -/// extractor where `T` is data type. +/// Application data can be accessed by using `Data` extractor where `T` is data type. /// -/// **Note**: http server accepts an application factory rather than -/// an application instance. Http server constructs an application -/// instance for each thread, thus application data must be constructed -/// multiple times. If you want to share data between different -/// threads, a shareable object should be used, e.g. `Send + Sync`. Application -/// data does not need to be `Send` or `Sync`. Internally `Data` type -/// uses `Arc`. if your data implements `Send` + `Sync` traits you can -/// use `web::Data::new()` and avoid double `Arc`. +/// **Note**: http server accepts an application factory rather than an application instance. HTTP +/// server constructs an application instance for each thread, thus application data must be +/// constructed multiple times. If you want to share data between different threads, a shareable +/// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send` +/// or `Sync`. Internally `Data` uses `Arc`. /// -/// If route data is not set for a handler, using `Data` extractor would -/// cause *Internal Server Error* response. +/// If route data is not set for a handler, using `Data` extractor would cause *Internal +/// Server Error* response. /// /// ```rust /// use std::sync::Mutex; @@ -48,7 +43,7 @@ pub(crate) type FnDataFactory = /// counter: usize, /// } /// -/// /// Use `Data` extractor to access data in handler. +/// /// Use the `Data` extractor to access data in a handler. /// async fn index(data: web::Data>) -> impl Responder { /// let mut data = data.lock().unwrap(); /// data.counter += 1; @@ -71,10 +66,6 @@ pub struct Data(Arc); impl Data { /// Create new `Data` instance. - /// - /// Internally `Data` type uses `Arc`. if your data implements - /// `Send` + `Sync` traits you can use `web::Data::new()` and - /// avoid double `Arc`. pub fn new(state: T) -> Data { Data(Arc::new(state)) } diff --git a/src/lib.rs b/src/lib.rs index 327cba954..088444e05 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -81,6 +81,7 @@ mod handler; mod info; pub mod middleware; mod request; +mod request_data; mod resource; mod responder; mod rmap; diff --git a/src/request_data.rs b/src/request_data.rs new file mode 100644 index 000000000..c01930418 --- /dev/null +++ b/src/request_data.rs @@ -0,0 +1,175 @@ +use std::{any::type_name, ops::Deref}; + +use actix_http::error::{Error, ErrorInternalServerError}; +use futures_util::future; + +use crate::{dev::Payload, FromRequest, HttpRequest}; + +/// Request-local data extractor. +/// +/// Request-local data is arbitrary data attached to an individual request, usually +/// by middleware. It can be set via `extensions_mut` on [`HttpRequest`][htr_ext_mut] +/// or [`ServiceRequest`][srv_ext_mut]. +/// +/// Unlike app data, request data is dropped when the request has finished processing. This makes it +/// useful as a kind of messaging system between middleware and request handlers. It uses the same +/// types-as-keys storage system as app data. +/// +/// # Mutating Request Data +/// Note that since extractors must output owned data, only types that `impl Clone` can use this +/// extractor. A clone is taken of the required request data and can, therefore, not be directly +/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or +/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not +/// provided to make this potential foot-gun more obvious. +/// +/// # Example +/// ```rust,no_run +/// # use actix_web::{web, HttpResponse, HttpRequest, Responder}; +/// +/// #[derive(Debug, Clone, PartialEq)] +/// struct FlagFromMiddleware(String); +/// +/// /// Use the `ReqData` extractor to access request data in a handler. +/// async fn handler( +/// req: HttpRequest, +/// opt_flag: Option>, +/// ) -> impl Responder { +/// // use an optional extractor if the middleware is +/// // not guaranteed to add this type of requests data +/// if let Some(flag) = opt_flag { +/// assert_eq!(&flag.into_inner(), req.extensions().get::().unwrap()); +/// } +/// +/// HttpResponse::Ok() +/// } +/// ``` +/// +/// [htr_ext_mut]: crate::HttpRequest::extensions_mut +/// [srv_ext_mut]: crate::dev::ServiceRequest::extensions_mut +#[derive(Debug, Clone)] +pub struct ReqData(T); + +impl ReqData { + /// Consumes the `ReqData`, returning it's wrapped data. + pub fn into_inner(self) -> T { + self.0 + } +} + +impl Deref for ReqData { + type Target = T; + + fn deref(&self) -> &T { + &self.0 + } +} + +impl FromRequest for ReqData { + type Config = (); + type Error = Error; + type Future = future::Ready>; + + fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { + if let Some(st) = req.extensions().get::() { + future::ok(ReqData(st.clone())) + } else { + log::debug!( + "Failed to construct App-level ReqData extractor. \ + Request path: {:?} (type: {})", + req.path(), + type_name::(), + ); + future::err(ErrorInternalServerError( + "Missing expected request extension data", + )) + } + } +} + +#[cfg(test)] +mod tests { + use std::{cell::RefCell, rc::Rc}; + + use futures_util::TryFutureExt as _; + + use super::*; + use crate::{ + dev::Service, + http::{Method, StatusCode}, + test::{init_service, TestRequest}, + web, App, HttpMessage, HttpResponse, + }; + + #[actix_rt::test] + async fn req_data_extractor() { + let mut srv = init_service( + App::new() + .wrap_fn(|req, srv| { + if req.method() == Method::POST { + req.extensions_mut().insert(42u32); + } + + srv.call(req) + }) + .service(web::resource("/test").to( + |req: HttpRequest, data: Option>| { + if req.method() != Method::POST { + assert!(data.is_none()); + } + + if let Some(data) = data { + assert_eq!(*data, 42); + assert_eq!( + Some(data.into_inner()), + req.extensions().get::().copied() + ); + } + + HttpResponse::Ok() + }, + )), + ) + .await; + + let req = TestRequest::get().uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + + let req = TestRequest::post().uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn req_data_internal_mutability() { + let mut srv = init_service( + App::new() + .wrap_fn(|req, srv| { + let data_before = Rc::new(RefCell::new(42u32)); + req.extensions_mut().insert(data_before); + + srv.call(req).map_ok(|res| { + { + let ext = res.request().extensions(); + let data_after = ext.get::>>().unwrap(); + assert_eq!(*data_after.borrow(), 53u32); + } + + res + }) + }) + .default_service(web::to(|data: ReqData>>| { + assert_eq!(*data.borrow(), 42); + *data.borrow_mut() += 11; + assert_eq!(*data.borrow(), 53); + + HttpResponse::Ok() + })), + ) + .await; + + let req = TestRequest::get().uri("/test").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } +} diff --git a/src/web.rs b/src/web.rs index 1d1174f41..ee895f8e7 100644 --- a/src/web.rs +++ b/src/web.rs @@ -19,6 +19,7 @@ use crate::service::WebService; pub use crate::config::ServiceConfig; pub use crate::data::Data; pub use crate::request::HttpRequest; +pub use crate::request_data::ReqData; pub use crate::types::*; /// Create resource for a specific path. From 41e7cec72f6f92dd795121adce42a594db3bc597 Mon Sep 17 00:00:00 2001 From: Daniel Egger Date: Sat, 24 Oct 2020 21:31:23 +0200 Subject: [PATCH 012/187] Re-export bytes::Buf and bytes::BufMut as well (#1750) Co-authored-by: Daniel Egger Co-authored-by: Rob Ede --- CHANGES.md | 2 ++ src/web.rs | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 9ea3a2094..a74d7490f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -7,10 +7,12 @@ ### Changed * Print non-configured `Data` type when attempting extraction. [#1743] +* Re-export bytes::Buf{Mut} in web module. [#1750] [#1723]: https://github.com/actix/actix-web/pull/1723 [#1743]: https://github.com/actix/actix-web/pull/1743 [#1748]: https://github.com/actix/actix-web/pull/1748 +[#1750]: https://github.com/actix/actix-web/pull/1750 ## 3.1.0 - 2020-09-29 diff --git a/src/web.rs b/src/web.rs index ee895f8e7..bf2158917 100644 --- a/src/web.rs +++ b/src/web.rs @@ -4,7 +4,7 @@ use actix_router::IntoPattern; use std::future::Future; pub use actix_http::Response as HttpResponse; -pub use bytes::{Bytes, BytesMut}; +pub use bytes::{Buf, BufMut, Bytes, BytesMut}; pub use futures_channel::oneshot::Canceled; use crate::error::BlockingError; From 06e5042b94c93aaa8c18b8702eaf6611c895b42f Mon Sep 17 00:00:00 2001 From: Jonas Date: Sat, 24 Oct 2020 22:15:01 +0200 Subject: [PATCH 013/187] use idenity encoding on client if no compression features are enabled (#1737) Co-authored-by: Yuki Okushi Co-authored-by: Rob Ede --- awc/CHANGES.md | 7 +++++++ awc/Cargo.toml | 1 + awc/src/request.rs | 13 +++++++++---- 3 files changed, 17 insertions(+), 4 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 8babba113..0b02b3cfa 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,8 +1,15 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed * Upgrade `base64` to `0.13`. +### Fixed +* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] + +[#1737]: https://github.com/actix/actix-web/pull/1737 + + ## 2.0.0 - 2020-09-11 ### Changed * `Client::build` was renamed to `Client::builder`. diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b8cf53e06..b7d8b0a22 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -44,6 +44,7 @@ actix-rt = "1.0.0" base64 = "0.13" bytes = "0.5.3" +cfg-if = "1.0" derive_more = "0.99.2" futures-core = { version = "0.3.5", default-features = false } log =" 0.4" diff --git a/awc/src/request.rs b/awc/src/request.rs index dcada2c6d..11e1da6a3 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -21,10 +21,15 @@ use crate::frozen::FrozenClientRequest; use crate::sender::{PrepForSendingError, RequestSender, SendClientRequest}; use crate::ClientConfig; -#[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] -const HTTPS_ENCODING: &str = "br, gzip, deflate"; -#[cfg(not(any(feature = "flate2-zlib", feature = "flate2-rust")))] -const HTTPS_ENCODING: &str = "br"; +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"; + } +} /// An HTTP Client request builder /// From 20078fe60394091f45d795b4552581421d0f3953 Mon Sep 17 00:00:00 2001 From: ghizzo01 <36126125+ghizzo01@users.noreply.github.com> Date: Sun, 25 Oct 2020 11:41:44 +0100 Subject: [PATCH 014/187] Bump pin-project to 1.0 (#1733) --- CHANGES.md | 1 + Cargo.toml | 2 +- actix-http/CHANGES.md | 1 + actix-http/Cargo.toml | 2 +- actix-web-actors/CHANGES.md | 2 +- actix-web-actors/Cargo.toml | 2 +- 6 files changed, 6 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a74d7490f..baa462a5d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,7 @@ ### Changed * Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] +* Upgrade `pin-project` to `1.0`. [#1723]: https://github.com/actix/actix-web/pull/1723 [#1743]: https://github.com/actix/actix-web/pull/1743 diff --git a/Cargo.toml b/Cargo.toml index 3960b4d36..5d64cfd91 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,7 +90,7 @@ fxhash = "0.2.1" log = "0.4" mime = "0.3" socket2 = "0.3" -pin-project = "0.4.17" +pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index b72a7801a..990c9c071 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,7 @@ ## Unreleased - 2020-xx-xx * Upgrade `base64` to `0.13`. +* Upgrade `pin-project` to `1.0`. ## 2.0.0 - 2020-09-11 * No significant changes from `2.0.0-beta.4`. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a4c100b92..9d2f7464f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -71,7 +71,7 @@ language-tags = "0.2" log = "0.4" mime = "0.3" percent-encoding = "2.1" -pin-project = "0.4.17" +pin-project = "1.0.0" rand = "0.7" regex = "1.3" serde = "1.0" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 4b9381a33..9df0df159 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,7 +1,7 @@ # Changes ## Unreleased - 2020-xx-xx - +* Upgrade `pin-project` to `1.0`. ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 2f3c63022..920940c40 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -23,7 +23,7 @@ actix-codec = "0.3.0" bytes = "0.5.2" futures-channel = { version = "0.3.5", default-features = false } futures-core = { version = "0.3.5", default-features = false } -pin-project = "0.4.17" +pin-project = "1.0.0" [dev-dependencies] actix-rt = "1.1.1" From 7030bf5fe82dc3c23e61a6f0e7a6b89b53a03e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Augusto=20C=C3=A9sar=20Dias?= Date: Mon, 26 Oct 2020 18:02:45 +0100 Subject: [PATCH 015/187] Adding app_data to ServiceConfig (#1758) Co-authored-by: Rob Ede Co-authored-by: Augusto --- CHANGES.md | 1 + actix-http/src/extensions.rs | 35 +++++++++++++++++++++++++++++++++++ src/app.rs | 1 + src/config.rs | 23 ++++++++++++++++++----- src/scope.rs | 3 +++ 5 files changed, 58 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index baa462a5d..af34c3b49 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Added * Implement `exclude_regex` for Logger middleware. [#1723] * Add request-local data extractor `web::ReqData`. [#1748] +* Add `app_data` to `ServiceConfig`. [#1757] ### Changed * Print non-configured `Data` type when attempting extraction. [#1743] diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 96e01767b..09f1b711f 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -61,6 +61,11 @@ impl Extensions { pub fn clear(&mut self) { self.map.clear(); } + + /// Extends self with the items from another `Extensions`. + pub fn extend(&mut self, other: Extensions) { + self.map.extend(other.map); + } } impl fmt::Debug for Extensions { @@ -178,4 +183,34 @@ mod tests { assert_eq!(extensions.get::(), None); assert_eq!(extensions.get(), Some(&MyType(10))); } + + #[test] + fn test_extend() { + #[derive(Debug, PartialEq)] + struct MyType(i32); + + let mut extensions = Extensions::new(); + + extensions.insert(5i32); + extensions.insert(MyType(10)); + + let mut other = Extensions::new(); + + other.insert(15i32); + other.insert(20u8); + + extensions.extend(other); + + assert_eq!(extensions.get(), Some(&15i32)); + assert_eq!(extensions.get_mut(), Some(&mut 15i32)); + + assert_eq!(extensions.remove::(), Some(15i32)); + assert!(extensions.get::().is_none()); + + assert_eq!(extensions.get::(), None); + assert_eq!(extensions.get(), Some(&MyType(10))); + + assert_eq!(extensions.get(), Some(&20u8)); + assert_eq!(extensions.get_mut(), Some(&mut 20u8)); + } } diff --git a/src/app.rs b/src/app.rs index 6a4b97b69..8dd86f7ec 100644 --- a/src/app.rs +++ b/src/app.rs @@ -183,6 +183,7 @@ where self.data.extend(cfg.data); self.services.extend(cfg.services); self.external.extend(cfg.external); + self.extensions.extend(cfg.extensions); self } diff --git a/src/config.rs b/src/config.rs index f7bebb4c5..03ba82732 100644 --- a/src/config.rs +++ b/src/config.rs @@ -178,6 +178,7 @@ pub struct ServiceConfig { pub(crate) services: Vec>, pub(crate) data: Vec>, pub(crate) external: Vec, + pub(crate) extensions: Extensions, } impl ServiceConfig { @@ -186,6 +187,7 @@ impl ServiceConfig { services: Vec::new(), data: Vec::new(), external: Vec::new(), + extensions: Extensions::new(), } } @@ -198,6 +200,14 @@ impl ServiceConfig { self } + /// Set arbitrary data item. + /// + /// This is same as `App::data()` method. + pub fn app_data(&mut self, ext: U) -> &mut Self { + self.extensions.insert(ext); + self + } + /// Configure route for a specific path. /// /// This is same as `App::route()` method. @@ -254,13 +264,16 @@ mod tests { async fn test_data() { let cfg = |cfg: &mut ServiceConfig| { cfg.data(10usize); + cfg.app_data(15u8); }; - let mut srv = - init_service(App::new().configure(cfg).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let mut srv = init_service(App::new().configure(cfg).service( + web::resource("/").to(|_: web::Data, req: HttpRequest| { + assert_eq!(*req.app_data::().unwrap(), 15u8); + HttpResponse::Ok() + }), + )) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/scope.rs b/src/scope.rs index 1c5d8700b..681d142be 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -209,6 +209,9 @@ where self.data = Some(data); } + self.data + .get_or_insert_with(Extensions::new) + .extend(cfg.extensions); self } From 4519db36b25992d98061d870fe738e0c04e50afb Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Thu, 29 Oct 2020 18:38:49 +0000 Subject: [PATCH 016/187] register fns for custom request-derived logging units (#1749) Co-authored-by: Rob Ede --- CHANGES.md | 2 + src/middleware/logger.rs | 156 +++++++++++++++++++++++++++++++++++---- 2 files changed, 142 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index af34c3b49..fb4fde4f1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Added * Implement `exclude_regex` for Logger middleware. [#1723] * Add request-local data extractor `web::ReqData`. [#1748] +* Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] ### Changed @@ -15,6 +16,7 @@ [#1743]: https://github.com/actix/actix-web/pull/1743 [#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 +[#1749]: https://github.com/actix/actix-web/pull/1749 ## 3.1.0 - 2020-09-29 diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 9a38d345b..b2e5c791f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -34,21 +34,19 @@ use crate::HttpResponse; /// Default `Logger` could be created with `default` method, it uses the /// default format: /// -/// ```ignore -/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T +/// ```plain +/// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` +/// /// ```rust -/// use actix_web::middleware::Logger; -/// use actix_web::App; +/// use actix_web::{middleware::Logger, App}; /// -/// fn main() { -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); +/// std::env::set_var("RUST_LOG", "actix_web=info"); +/// env_logger::init(); /// -/// let app = App::new() -/// .wrap(Logger::default()) -/// .wrap(Logger::new("%a %{User-Agent}i")); -/// } +/// let app = App::new() +/// .wrap(Logger::default()) +/// .wrap(Logger::new("%a %{User-Agent}i")); /// ``` /// /// ## Format @@ -80,6 +78,8 @@ use crate::HttpResponse; /// /// `%{FOO}e` os.environ['FOO'] /// +/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO" +/// /// # Security /// **\*** It is calculated using /// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr) @@ -123,12 +123,52 @@ impl Logger { inner.exclude_regex = regex_set; self } + + /// Register a function that receives a ServiceRequest and returns a String for use in the + /// log line. The label passed as the first argument should match a replacement substring in + /// the logger format like `%{label}xi`. + /// + /// It is convention to print "-" to indicate no output instead of an empty string. + /// + /// # Example + /// ```rust + /// # use actix_web::{http::HeaderValue, middleware::Logger}; + /// # fn parse_jwt_id (_req: Option<&HeaderValue>) -> String { "jwt_uid".to_owned() } + /// Logger::new("example %{JWT_ID}xi") + /// .custom_request_replace("JWT_ID", |req| parse_jwt_id(req.headers().get("Authorization"))); + /// ``` + pub fn custom_request_replace( + mut self, + label: &str, + f: impl Fn(&ServiceRequest) -> String + 'static, + ) -> Self { + let inner = Rc::get_mut(&mut self.0).unwrap(); + + let ft = inner.format.0.iter_mut().find(|ft| { + matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label) + }); + + if let Some(FormatText::CustomRequest(_, request_fn)) = ft { + // replace into None or previously registered fn using same label + request_fn.replace(CustomRequestFn { + inner_fn: Rc::new(f), + }); + } else { + // non-printed request replacement function diagnostic + debug!( + "Attempted to register custom request logging function for nonexistent label: {}", + label + ); + } + + self + } } impl Default for Logger { /// Create `Logger` middleware with format: /// - /// ```ignore + /// ```plain /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T /// ``` fn default() -> Logger { @@ -153,6 +193,17 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { + for unit in &self.0.format.0 { + // missing request replacement function diagnostic + if let FormatText::CustomRequest(label, None) = unit { + debug!( + "No custom request replacement function was registered for label {} in\ + logger format.", + label + ); + } + } + ok(LoggerMiddleware { service, inner: self.0.clone(), @@ -311,7 +362,6 @@ impl MessageBody for StreamLog { /// A formatting style for the `Logger`, consisting of multiple /// `FormatText`s concatenated into one line. #[derive(Clone)] -#[doc(hidden)] struct Format(Vec); impl Default for Format { @@ -327,7 +377,8 @@ impl Format { /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { log::trace!("Access log format: {}", s); - let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe])|[atPrUsbTD]?)").unwrap(); + let fmt = + Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -355,6 +406,7 @@ impl Format { HeaderName::try_from(key.as_str()).unwrap(), ), "e" => FormatText::EnvironHeader(key.as_str().to_owned()), + "xi" => FormatText::CustomRequest(key.as_str().to_owned(), None), _ => unreachable!(), }) } else { @@ -384,7 +436,9 @@ impl Format { /// A string of text to be logged. This is either one of the data /// fields supported by the `Logger`, or a custom `String`. #[doc(hidden)] +#[non_exhaustive] #[derive(Debug, Clone)] +// TODO: remove pub on next breaking change pub enum FormatText { Str(String), Percent, @@ -400,6 +454,26 @@ pub enum FormatText { RequestHeader(HeaderName), ResponseHeader(HeaderName), EnvironHeader(String), + CustomRequest(String, Option), +} + +// TODO: remove pub on next breaking change +#[doc(hidden)] +#[derive(Clone)] +pub struct CustomRequestFn { + inner_fn: Rc String>, +} + +impl CustomRequestFn { + fn call(&self, req: &ServiceRequest) -> String { + (self.inner_fn)(req) + } +} + +impl fmt::Debug for CustomRequestFn { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("custom_request_fn") + } } impl FormatText { @@ -456,7 +530,7 @@ impl FormatText { } fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) { - match *self { + match &*self { FormatText::RequestLine => { *self = if req.query_string().is_empty() { FormatText::Str(format!( @@ -508,11 +582,20 @@ impl FormatText { }; *self = s; } + FormatText::CustomRequest(_, request_fn) => { + let s = match request_fn { + Some(f) => FormatText::Str(f.call(req)), + None => FormatText::Str("-".to_owned()), + }; + + *self = s; + } _ => (), } } } +/// Converter to get a String from something that writes to a Formatter. pub(crate) struct FormatDisplay<'a>( &'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>, ); @@ -530,7 +613,7 @@ mod tests { use super::*; use crate::http::{header, StatusCode}; - use crate::test::TestRequest; + use crate::test::{self, TestRequest}; #[actix_rt::test] async fn test_logger() { @@ -699,4 +782,45 @@ mod tests { println!("{}", s); assert!(s.contains("192.0.2.60")); } + + #[actix_rt::test] + async fn test_custom_closure_log() { + let mut logger = Logger::new("test %{CUSTOM}xi") + .custom_request_replace("CUSTOM", |_req: &ServiceRequest| -> String { + String::from("custom_log") + }); + let mut unit = Rc::get_mut(&mut logger.0).unwrap().format.0[1].clone(); + + let label = match &unit { + FormatText::CustomRequest(label, _) => label, + ft => panic!("expected CustomRequest, found {:?}", ft), + }; + + assert_eq!(label, "CUSTOM"); + + let req = TestRequest::default().to_srv_request(); + let now = OffsetDateTime::now_utc(); + + unit.render_request(now, &req); + + let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now); + + let log_output = FormatDisplay(&render).to_string(); + assert_eq!(log_output, "custom_log"); + } + + #[actix_rt::test] + async fn test_closure_logger_in_middleware() { + let captured = "custom log replacement"; + + let logger = Logger::new("%{CUSTOM}xi") + .custom_request_replace("CUSTOM", move |_req: &ServiceRequest| -> String { + captured.to_owned() + }); + + let mut srv = logger.new_transform(test::ok_service()).await.unwrap(); + + let req = TestRequest::default().to_srv_request(); + srv.call(req).await.unwrap(); + } } From 9963a5ef54119f3a4b791cdd76ae607397334d4d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:03:26 +0000 Subject: [PATCH 017/187] expose on_connect v2 (#1754) Co-authored-by: Mikail Bagishov --- CHANGES.md | 2 + Cargo.toml | 14 ++-- actix-http/CHANGES.md | 7 ++ actix-http/src/builder.rs | 41 +++++++++-- actix-http/src/extensions.rs | 30 +++++++- actix-http/src/h1/dispatcher.rs | 18 ++++- actix-http/src/h1/service.rs | 35 ++++++++-- actix-http/src/h2/dispatcher.rs | 8 +++ actix-http/src/h2/service.rs | 46 ++++++++---- actix-http/src/helpers.rs | 1 + actix-http/src/lib.rs | 4 +- actix-http/src/service.rs | 65 ++++++++++++----- actix-http/tests/test_openssl.rs | 2 + actix-http/tests/test_server.rs | 2 + examples/on_connect.rs | 51 ++++++++++++++ src/server.rs | 116 ++++++++++++++++++++++++++----- 16 files changed, 372 insertions(+), 70 deletions(-) create mode 100644 examples/on_connect.rs diff --git a/CHANGES.md b/CHANGES.md index fb4fde4f1..15d44b75c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -6,6 +6,7 @@ * Add request-local data extractor `web::ReqData`. [#1748] * Add ability to register closure for request middleware logging. [#1749] * Add `app_data` to `ServiceConfig`. [#1757] +* Expose `on_connect` for access to the connection stream before request is handled. [#1754] ### Changed * Print non-configured `Data` type when attempting extraction. [#1743] @@ -16,6 +17,7 @@ [#1743]: https://github.com/actix/actix-web/pull/1743 [#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 +[#1754]: https://github.com/actix/actix-web/pull/1754 [#1749]: https://github.com/actix/actix-web/pull/1749 diff --git a/Cargo.toml b/Cargo.toml index 5d64cfd91..4fafc61c4 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -64,6 +64,14 @@ required-features = ["compress"] name = "test_server" required-features = ["compress"] +[[example]] +name = "on_connect" +required-features = [] + +[[example]] +name = "client" +required-features = ["rustls"] + [dependencies] actix-codec = "0.3.0" actix-service = "1.0.6" @@ -105,7 +113,7 @@ tinyvec = { version = "1", features = ["alloc"] } actix = "0.10.0" actix-http = { version = "2.0.0", features = ["actors"] } rand = "0.7" -env_logger = "0.7" +env_logger = "0.8" serde_derive = "1.0" brotli2 = "0.3.2" flate2 = "1.0.13" @@ -125,10 +133,6 @@ actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } awc = { path = "awc" } -[[example]] -name = "client" -required-features = ["rustls"] - [[bench]] name = "server" harness = false diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 990c9c071..0afb63a6d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,9 +1,16 @@ # Changes ## Unreleased - 2020-xx-xx +### Added +* Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] + +### Changed * Upgrade `base64` to `0.13`. * Upgrade `pin-project` to `1.0`. +[#1754]: https://github.com/actix/actix-web/pull/1754 + + ## 2.0.0 - 2020-09-11 * No significant changes from `2.0.0-beta.4`. diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 271abd43f..b28c69761 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -14,10 +14,11 @@ use crate::helpers::{Data, DataFactory}; use crate::request::Request; use crate::response::Response; use crate::service::HttpService; +use crate::{ConnectCallback, Extensions}; -/// A http service builder +/// A HTTP service builder /// -/// This type can be used to construct an instance of `http service` through a +/// This type can be used to construct an instance of [`HttpService`] through a /// builder-like pattern. pub struct HttpServiceBuilder> { keep_alive: KeepAlive, @@ -27,7 +28,9 @@ pub struct HttpServiceBuilder> { local_addr: Option, expect: X, upgrade: Option, + // DEPRECATED: in favor of on_connect_ext on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, S)>, } @@ -49,6 +52,7 @@ where expect: ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -138,6 +142,7 @@ where expect: expect.into_factory(), upgrade: self.upgrade, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -167,14 +172,16 @@ where expect: self.expect, upgrade: Some(upgrade.into_factory()), on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } /// Set on-connect callback. /// - /// It get called once per connection and result of the call - /// get stored to the request's extensions. + /// Called once per connection. Return value of the call is stored in request extensions. + /// + /// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback. pub fn on_connect(mut self, f: F) -> Self where F: Fn(&T) -> I + 'static, @@ -184,7 +191,20 @@ where self } - /// Finish service configuration and create *http service* for HTTP/1 protocol. + /// Sets the callback to be run on connection establishment. + /// + /// Has mutable access to a data container that will be merged into request extensions. + /// This enables transport layer data (like client certificates) to be accessed in middleware + /// and handlers. + pub fn on_connect_ext(mut self, f: F) -> Self + where + F: Fn(&T, &mut Extensions) + 'static, + { + self.on_connect_ext = Some(Rc::new(f)); + self + } + + /// Finish service configuration and create a HTTP Service for HTTP/1 protocol. pub fn h1(self, service: F) -> H1Service where B: MessageBody, @@ -200,13 +220,15 @@ where self.secure, self.local_addr, ); + H1Service::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) .on_connect(self.on_connect) + .on_connect_ext(self.on_connect_ext) } - /// Finish service configuration and create *http service* for HTTP/2 protocol. + /// Finish service configuration and create a HTTP service for HTTP/2 protocol. pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, @@ -223,7 +245,10 @@ where self.secure, self.local_addr, ); - H2Service::with_config(cfg, service.into_factory()).on_connect(self.on_connect) + + H2Service::with_config(cfg, service.into_factory()) + .on_connect(self.on_connect) + .on_connect_ext(self.on_connect_ext) } /// Finish service configuration and create `HttpService` instance. @@ -243,9 +268,11 @@ where self.secure, self.local_addr, ); + HttpService::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) .on_connect(self.on_connect) + .on_connect_ext(self.on_connect_ext) } } diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 09f1b711f..7dda74731 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,5 +1,5 @@ use std::any::{Any, TypeId}; -use std::fmt; +use std::{fmt, mem}; use fxhash::FxHashMap; @@ -66,6 +66,11 @@ impl Extensions { pub fn extend(&mut self, other: Extensions) { self.map.extend(other.map); } + + /// Sets (or overrides) items from `other` into this map. + pub(crate) fn drain_from(&mut self, other: &mut Self) { + self.map.extend(mem::take(&mut other.map)); + } } impl fmt::Debug for Extensions { @@ -213,4 +218,27 @@ mod tests { assert_eq!(extensions.get(), Some(&20u8)); assert_eq!(extensions.get_mut(), Some(&mut 20u8)); } + + #[test] + fn test_drain_from() { + let mut ext = Extensions::new(); + ext.insert(2isize); + + let mut more_ext = Extensions::new(); + + more_ext.insert(5isize); + more_ext.insert(5usize); + + assert_eq!(ext.get::(), Some(&2isize)); + assert_eq!(ext.get::(), None); + assert_eq!(more_ext.get::(), Some(&5isize)); + assert_eq!(more_ext.get::(), Some(&5usize)); + + ext.drain_from(&mut more_ext); + + assert_eq!(ext.get::(), Some(&5isize)); + assert_eq!(ext.get::(), Some(&5usize)); + assert_eq!(more_ext.get::(), None); + assert_eq!(more_ext.get::(), None); + } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 7c4de9707..ace4144e3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -12,7 +12,6 @@ use bytes::{Buf, BytesMut}; use log::{error, trace}; use pin_project::pin_project; -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; @@ -21,6 +20,10 @@ use crate::helpers::DataFactory; use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::Response; +use crate::{ + body::{Body, BodySize, MessageBody, ResponseBody}, + Extensions, +}; use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus}; @@ -88,6 +91,7 @@ where expect: CloneableService, upgrade: Option>, on_connect: Option>, + on_connect_data: Extensions, flags: Flags, peer_addr: Option, error: Option, @@ -167,7 +171,7 @@ where U: Service), Response = ()>, U::Error: fmt::Display, { - /// Create http/1 dispatcher. + /// Create HTTP/1 dispatcher. pub(crate) fn new( stream: T, config: ServiceConfig, @@ -175,6 +179,7 @@ where expect: CloneableService, upgrade: Option>, on_connect: Option>, + on_connect_data: Extensions, peer_addr: Option, ) -> Self { Dispatcher::with_timeout( @@ -187,6 +192,7 @@ where expect, upgrade, on_connect, + on_connect_data, peer_addr, ) } @@ -202,6 +208,7 @@ where expect: CloneableService, upgrade: Option>, on_connect: Option>, + on_connect_data: Extensions, peer_addr: Option, ) -> Self { let keepalive = config.keep_alive_enabled(); @@ -234,6 +241,7 @@ where expect, upgrade, on_connect, + on_connect_data, flags, peer_addr, ka_expire, @@ -526,11 +534,15 @@ where let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; + // DEPRECATED // set on_connect data if let Some(ref on_connect) = this.on_connect { on_connect.set(&mut req.extensions_mut()); } + // merge on_connect_ext data into request extensions + req.extensions_mut().drain_from(this.on_connect_data); + if pl == MessageType::Stream && this.upgrade.is_some() { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; @@ -927,8 +939,10 @@ mod tests { CloneableService::new(ExpectHandler), None, None, + Extensions::new(), None, ); + match Pin::new(&mut h1).poll(cx) { Poll::Pending => panic!(), Poll::Ready(res) => assert!(res.is_err()), diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 6aafd4089..5008791c0 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -18,6 +18,7 @@ use crate::error::{DispatchError, Error, ParseError}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; +use crate::{ConnectCallback, Extensions}; use super::codec::Codec; use super::dispatcher::Dispatcher; @@ -30,6 +31,7 @@ pub struct H1Service> { expect: X, upgrade: Option, on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -52,6 +54,7 @@ where expect: ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -213,6 +216,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -229,6 +233,7 @@ where srv: self.srv, expect: self.expect, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -241,6 +246,12 @@ where self.on_connect = f; self } + + /// Set on connect callback. + pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { + self.on_connect_ext = f; + self + } } impl ServiceFactory for H1Service @@ -274,6 +285,7 @@ where expect: None, upgrade: None, on_connect: self.on_connect.clone(), + on_connect_ext: self.on_connect_ext.clone(), cfg: Some(self.cfg.clone()), _t: PhantomData, } @@ -303,6 +315,7 @@ where expect: Option, upgrade: Option, on_connect: Option Box>>, + on_connect_ext: Option>>, cfg: Option, _t: PhantomData<(T, B)>, } @@ -352,23 +365,26 @@ where Poll::Ready(result.map(|service| { let this = self.as_mut().project(); + H1ServiceHandler::new( this.cfg.take().unwrap(), service, this.expect.take().unwrap(), this.upgrade.take(), this.on_connect.clone(), + this.on_connect_ext.clone(), ) })) } } -/// `Service` implementation for HTTP1 transport +/// `Service` implementation for HTTP/1 transport pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, on_connect: Option Box>>, + on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } @@ -390,6 +406,7 @@ where expect: X, upgrade: Option, on_connect: Option Box>>, + on_connect_ext: Option>>, ) -> H1ServiceHandler { H1ServiceHandler { srv: CloneableService::new(srv), @@ -397,6 +414,7 @@ where upgrade: upgrade.map(CloneableService::new), cfg, on_connect, + on_connect_ext, _t: PhantomData, } } @@ -462,11 +480,13 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; + let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); + + let mut connect_extensions = Extensions::new(); + if let Some(ref handler) = self.on_connect_ext { + // run on_connect_ext callback, populating connect extensions + handler(&io, &mut connect_extensions); + } Dispatcher::new( io, @@ -474,7 +494,8 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - on_connect, + deprecated_on_connect, + connect_extensions, addr, ) } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index daa651f4d..594339121 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -24,6 +24,7 @@ use crate::message::ResponseHead; use crate::payload::Payload; use crate::request::Request; use crate::response::Response; +use crate::Extensions; const CHUNK_SIZE: usize = 16_384; @@ -36,6 +37,7 @@ where service: CloneableService, connection: Connection, on_connect: Option>, + on_connect_data: Extensions, config: ServiceConfig, peer_addr: Option, ka_expire: Instant, @@ -56,6 +58,7 @@ where service: CloneableService, connection: Connection, on_connect: Option>, + on_connect_data: Extensions, config: ServiceConfig, timeout: Option, peer_addr: Option, @@ -82,6 +85,7 @@ where peer_addr, connection, on_connect, + on_connect_data, ka_expire, ka_timer, _t: PhantomData, @@ -130,11 +134,15 @@ where head.headers = parts.headers.into(); head.peer_addr = this.peer_addr; + // DEPRECATED // set on_connect data if let Some(ref on_connect) = this.on_connect { on_connect.set(&mut req.extensions_mut()); } + // merge on_connect_ext data into request extensions + req.extensions_mut().drain_from(&mut this.on_connect_data); + actix_rt::spawn(ServiceResponse::< S::Future, S::Response, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 6b5620e02..3103127b4 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -2,7 +2,7 @@ use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{net, rc}; +use std::{net, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; @@ -23,6 +23,7 @@ use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; +use crate::{ConnectCallback, Extensions}; use super::dispatcher::Dispatcher; @@ -30,7 +31,8 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -50,19 +52,27 @@ where H2Service { cfg, on_connect: None, + on_connect_ext: None, srv: service.into_factory(), _t: PhantomData, } } /// Set on connect callback. + pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self } + + /// Set on connect callback. + pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { + self.on_connect_ext = f; + self + } } impl H2Service @@ -203,6 +213,7 @@ where fut: self.srv.new_service(()), cfg: Some(self.cfg.clone()), on_connect: self.on_connect.clone(), + on_connect_ext: self.on_connect_ext.clone(), _t: PhantomData, } } @@ -214,7 +225,8 @@ pub struct H2ServiceResponse { #[pin] fut: S::Future, cfg: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -237,6 +249,7 @@ where H2ServiceHandler::new( this.cfg.take().unwrap(), this.on_connect.clone(), + this.on_connect_ext.clone(), service, ) })) @@ -247,7 +260,8 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -261,12 +275,14 @@ where { fn new( cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, srv: S, ) -> H2ServiceHandler { H2ServiceHandler { cfg, on_connect, + on_connect_ext, srv: CloneableService::new(srv), _t: PhantomData, } @@ -296,18 +312,21 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; + let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); + + let mut connect_extensions = Extensions::new(); + if let Some(ref handler) = self.on_connect_ext { + // run on_connect_ext callback, populating connect extensions + handler(&io, &mut connect_extensions); + } H2ServiceHandlerResponse { state: State::Handshake( Some(self.srv.clone()), Some(self.cfg.clone()), addr, - on_connect, + deprecated_on_connect, + Some(connect_extensions), server::handshake(io), ), } @@ -325,6 +344,7 @@ where Option, Option, Option>, + Option, Handshake, ), } @@ -360,6 +380,7 @@ where ref mut config, ref peer_addr, ref mut on_connect, + ref mut on_connect_data, ref mut handshake, ) => match Pin::new(handshake).poll(cx) { Poll::Ready(Ok(conn)) => { @@ -367,6 +388,7 @@ where srv.take().unwrap(), conn, on_connect.take(), + on_connect_data.take().unwrap(), config.take().unwrap(), None, *peer_addr, diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index bbf358b66..ac0e0f118 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -50,6 +50,7 @@ impl<'a> io::Write for Writer<'a> { self.0.extend_from_slice(buf); Ok(buf.len()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index fab91be2b..e57a3727e 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,4 +1,4 @@ -//! Basic http primitives for actix-net framework. +//! Basic HTTP primitives for the Actix ecosystem. #![deny(rust_2018_idioms)] #![allow( @@ -78,3 +78,5 @@ pub enum Protocol { Http1, Http2, } + +type ConnectCallback = dyn Fn(&IO, &mut Extensions); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 9ee579702..75745209c 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,7 +1,7 @@ use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{fmt, net, rc}; +use std::{fmt, net, rc::Rc}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; @@ -20,15 +20,17 @@ use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; -use crate::{h1, h2::Dispatcher, Protocol}; +use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; -/// `ServiceFactory` HTTP1.1/HTTP2 transport implementation +/// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. pub struct HttpService> { srv: S, cfg: ServiceConfig, expect: X, upgrade: Option, - on_connect: Option Box>>, + // DEPRECATED: in favor of on_connect_ext + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -66,6 +68,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -81,6 +84,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect: None, + on_connect_ext: None, _t: PhantomData, } } @@ -113,6 +117,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -138,6 +143,7 @@ where srv: self.srv, expect: self.expect, on_connect: self.on_connect, + on_connect_ext: self.on_connect_ext, _t: PhantomData, } } @@ -145,11 +151,17 @@ where /// Set on connect callback. pub(crate) fn on_connect( mut self, - f: Option Box>>, + f: Option Box>>, ) -> Self { self.on_connect = f; self } + + /// Set connect callback with mutable access to request data container. + pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { + self.on_connect_ext = f; + self + } } impl HttpService @@ -355,6 +367,7 @@ where expect: None, upgrade: None, on_connect: self.on_connect.clone(), + on_connect_ext: self.on_connect_ext.clone(), cfg: self.cfg.clone(), _t: PhantomData, } @@ -378,7 +391,8 @@ pub struct HttpServiceResponse< fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, } @@ -429,6 +443,7 @@ where .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( @@ -437,6 +452,7 @@ where this.expect.take().unwrap(), this.upgrade.take(), this.on_connect.clone(), + this.on_connect_ext.clone(), ) })) } @@ -448,7 +464,8 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, _t: PhantomData<(T, B, X)>, } @@ -469,11 +486,13 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, + on_connect: Option Box>>, + on_connect_ext: Option>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, on_connect, + on_connect_ext, srv: CloneableService::new(srv), expect: CloneableService::new(expect), upgrade: upgrade.map(CloneableService::new), @@ -543,11 +562,12 @@ where } fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { - let on_connect = if let Some(ref on_connect) = self.on_connect { - Some(on_connect(&io)) - } else { - None - }; + let mut connect_extensions = Extensions::new(); + + let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); + if let Some(ref handler) = self.on_connect_ext { + handler(&io, &mut connect_extensions); + } match proto { Protocol::Http2 => HttpServiceHandlerResponse { @@ -555,10 +575,12 @@ where server::handshake(io), self.cfg.clone(), self.srv.clone(), - on_connect, + deprecated_on_connect, + connect_extensions, peer_addr, ))), }, + Protocol::Http1 => HttpServiceHandlerResponse { state: State::H1(h1::Dispatcher::new( io, @@ -566,7 +588,8 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - on_connect, + deprecated_on_connect, + connect_extensions, peer_addr, )), }, @@ -595,6 +618,7 @@ where ServiceConfig, CloneableService, Option>, + Extensions, Option, )>, ), @@ -670,9 +694,16 @@ where } else { panic!() }; - let (_, cfg, srv, on_connect, peer_addr) = data.take().unwrap(); + let (_, cfg, srv, on_connect, on_connect_data, peer_addr) = + data.take().unwrap(); self.set(State::H2(Dispatcher::new( - srv, conn, on_connect, cfg, None, peer_addr, + srv, + conn, + on_connect, + on_connect_data, + cfg, + None, + peer_addr, ))); self.poll(cx) } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 795deacdc..05f01d240 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -411,8 +411,10 @@ async fn test_h2_on_connect() { let srv = test_server(move || { HttpService::build() .on_connect(|_| 10usize) + .on_connect_ext(|_, data| data.insert(20isize)) .h2(|req: Request| { assert!(req.extensions().contains::()); + assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) }) .openssl(ssl_acceptor()) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 0375b6f66..de6368fda 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -663,8 +663,10 @@ async fn test_h1_on_connect() { let srv = test_server(|| { HttpService::build() .on_connect(|_| 10usize) + .on_connect_ext(|_, data| data.insert(20isize)) .h1(|req: Request| { assert!(req.extensions().contains::()); + assert!(req.extensions().contains::()); future::ok::<_, ()>(Response::Ok().finish()) }) .tcp() diff --git a/examples/on_connect.rs b/examples/on_connect.rs new file mode 100644 index 000000000..bdad7e67e --- /dev/null +++ b/examples/on_connect.rs @@ -0,0 +1,51 @@ +//! This example shows how to use `actix_web::HttpServer::on_connect` to access a lower-level socket +//! properties and pass them to a handler through request-local data. +//! +//! For an example of extracting a client TLS certificate, see: +//! + +use std::{any::Any, env, io, net::SocketAddr}; + +use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer}; + +#[derive(Debug, Clone)] +struct ConnectionInfo { + bind: SocketAddr, + peer: SocketAddr, + ttl: Option, +} + +async fn route_whoami(conn_info: web::ReqData) -> String { + format!( + "Here is some info about your connection:\n\n{:#?}", + conn_info + ) +} + +fn get_conn_info(connection: &dyn Any, data: &mut Extensions) { + if let Some(sock) = connection.downcast_ref::() { + data.insert(ConnectionInfo { + bind: sock.local_addr().unwrap(), + peer: sock.peer_addr().unwrap(), + ttl: sock.ttl().ok(), + }); + } else { + unreachable!("connection should only be plaintext since no TLS is set up"); + } +} + +#[actix_web::main] +async fn main() -> io::Result<()> { + if env::var("RUST_LOG").is_err() { + env::set_var("RUST_LOG", "info"); + } + + env_logger::init(); + + HttpServer::new(|| App::new().default_service(web::to(route_whoami))) + .on_connect(get_conn_info) + .bind(("127.0.0.1", 8080))? + .workers(1) + .run() + .await +} diff --git a/src/server.rs b/src/server.rs index 2b86f7416..3badb6e8d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,8 +1,14 @@ -use std::marker::PhantomData; -use std::sync::{Arc, Mutex}; -use std::{fmt, io, net}; +use std::{ + any::Any, + fmt, io, + marker::PhantomData, + net, + sync::{Arc, Mutex}, +}; -use actix_http::{body::MessageBody, Error, HttpService, KeepAlive, Request, Response}; +use actix_http::{ + body::MessageBody, Error, Extensions, HttpService, KeepAlive, Request, Response, +}; use actix_server::{Server, ServerBuilder}; use actix_service::{map_config, IntoServiceFactory, Service, ServiceFactory}; @@ -64,6 +70,7 @@ where backlog: i32, sockets: Vec, builder: ServerBuilder, + on_connect_fn: Option>, _t: PhantomData<(S, B)>, } @@ -91,6 +98,32 @@ where backlog: 1024, sockets: Vec::new(), builder: ServerBuilder::default(), + on_connect_fn: None, + _t: PhantomData, + } + } + + /// Sets function that will be called once before each connection is handled. + /// It will receive a `&std::any::Any`, which contains underlying connection type and an + /// [Extensions] container so that request-local data can be passed to middleware and handlers. + /// + /// For example: + /// - `actix_tls::openssl::SslStream` when using openssl. + /// - `actix_tls::rustls::TlsStream` when using rustls. + /// - `actix_web::rt::net::TcpStream` when no encryption is used. + /// + /// See `on_connect` example for additional details. + pub fn on_connect(self, f: CB) -> HttpServer + where + CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, + { + HttpServer { + factory: self.factory, + config: self.config, + backlog: self.backlog, + sockets: self.sockets, + builder: self.builder, + on_connect_fn: Some(Arc::new(f)), _t: PhantomData, } } @@ -240,6 +273,7 @@ where addr, scheme: "http", }); + let on_connect_fn = self.on_connect_fn.clone(); self.builder = self.builder.listen( format!("actix-web-service-{}", addr), @@ -252,11 +286,20 @@ where c.host.clone().unwrap_or_else(|| format!("{}", addr)), ); - HttpService::build() + let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .local_addr(addr) - .finish(map_config(factory(), move |_| cfg.clone())) + .local_addr(addr); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| cfg.clone())) .tcp() }, )?; @@ -289,6 +332,8 @@ where scheme: "https", }); + let on_connect_fn = self.on_connect_fn.clone(); + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, @@ -299,11 +344,21 @@ where addr, c.host.clone().unwrap_or_else(|| format!("{}", addr)), ); - HttpService::build() + + let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) - .finish(map_config(factory(), move |_| cfg.clone())) + .client_disconnect(c.client_shutdown); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| cfg.clone())) .openssl(acceptor.clone()) }, )?; @@ -336,6 +391,8 @@ where scheme: "https", }); + let on_connect_fn = self.on_connect_fn.clone(); + self.builder = self.builder.listen( format!("actix-web-service-{}", addr), lst, @@ -346,11 +403,21 @@ where addr, c.host.clone().unwrap_or_else(|| format!("{}", addr)), ); - HttpService::build() + + let svc = HttpService::build() .keep_alive(c.keep_alive) .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown) - .finish(map_config(factory(), move |_| cfg.clone())) + .client_disconnect(c.client_shutdown); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| cfg.clone())) .rustls(config.clone()) }, )?; @@ -441,7 +508,7 @@ where } #[cfg(unix)] - /// Start listening for unix domain connections on existing listener. + /// Start listening for unix domain (UDS) connections on existing listener. pub fn listen_uds( mut self, lst: std::os::unix::net::UnixListener, @@ -460,6 +527,7 @@ where }); let addr = format!("actix-web-service-{:?}", lst.local_addr()?); + let on_connect_fn = self.on_connect_fn.clone(); self.builder = self.builder.listen_uds(addr, lst, move || { let c = cfg.lock().unwrap(); @@ -468,11 +536,23 @@ where socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); + pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), + { + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; + + svc.finish(map_config(factory(), move |_| config.clone())) + }, ) })?; Ok(self) From 4cb833616ad7c619367695068e0515dda37bf41b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:10:05 +0000 Subject: [PATCH 018/187] deprecate builder if-x methods (#1760) --- actix-http/CHANGES.md | 4 ++++ actix-http/src/response.rs | 10 ++++++---- awc/CHANGES.md | 2 ++ awc/src/request.rs | 38 ++++++++++++++++++++++++-------------- 4 files changed, 36 insertions(+), 18 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 0afb63a6d..8533cea55 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,10 @@ ### Changed * Upgrade `base64` to `0.13`. * Upgrade `pin-project` to `1.0`. +* Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] + +[#1760]: https://github.com/actix/actix-web/pull/1760 + [#1754]: https://github.com/actix/actix-web/pull/1754 diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 2def67168..df2f5be50 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -554,8 +554,9 @@ impl ResponseBuilder { self } - /// This method calls provided closure with builder reference if value is - /// true. + /// This method calls provided closure with builder reference if value is `true`. + #[doc(hidden)] + #[deprecated = "Use an if statement."] pub fn if_true(&mut self, value: bool, f: F) -> &mut Self where F: FnOnce(&mut ResponseBuilder), @@ -566,8 +567,9 @@ impl ResponseBuilder { self } - /// This method calls provided closure with builder reference if value is - /// Some. + /// This method calls provided closure with builder reference if value is `Some`. + #[doc(hidden)] + #[deprecated = "Use an if-let construction."] pub fn if_some(&mut self, value: Option, f: F) -> &mut Self where F: FnOnce(T, &mut ResponseBuilder), diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 0b02b3cfa..0eabe61e9 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,11 +3,13 @@ ## Unreleased - 2020-xx-xx ### Changed * Upgrade `base64` to `0.13`. +* Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed * Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 +[#1760]: https://github.com/actix/actix-web/pull/1760 ## 2.0.0 - 2020-09-11 diff --git a/awc/src/request.rs b/awc/src/request.rs index 11e1da6a3..1e49aae3c 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -354,8 +354,9 @@ impl ClientRequest { self } - /// This method calls provided closure with builder reference if - /// value is `true`. + /// This method calls provided closure with builder reference if value is `true`. + #[doc(hidden)] + #[deprecated = "Use an if statement."] pub fn if_true(self, value: bool, f: F) -> Self where F: FnOnce(ClientRequest) -> ClientRequest, @@ -367,8 +368,9 @@ impl ClientRequest { } } - /// This method calls provided closure with builder reference if - /// value is `Some`. + /// This method calls provided closure with builder reference if value is `Some`. + #[doc(hidden)] + #[deprecated = "Use an if-let construction."] pub fn if_some(self, value: Option, f: F) -> Self where F: FnOnce(T, ClientRequest) -> ClientRequest, @@ -601,20 +603,27 @@ mod tests { #[actix_rt::test] async fn test_basics() { - let mut req = Client::new() + let req = Client::new() .put("/") .version(Version::HTTP_2) .set(header::Date(SystemTime::now().into())) .content_type("plain/text") - .if_true(true, |req| req.header(header::SERVER, "awc")) - .if_true(false, |req| req.header(header::EXPECT, "awc")) - .if_some(Some("server"), |val, req| { - req.header(header::USER_AGENT, val) - }) - .if_some(Option::<&str>::None, |_, req| { - req.header(header::ALLOW, "1") - }) - .content_length(100); + .header(header::SERVER, "awc"); + + let req = if let Some(val) = Some("server") { + req.header(header::USER_AGENT, val) + } else { + req + }; + + let req = if let Some(_val) = Option::<&str>::None { + req.header(header::ALLOW, "1") + } else { + req + }; + + let mut req = req.content_length(100); + assert!(req.headers().contains_key(header::CONTENT_TYPE)); assert!(req.headers().contains_key(header::DATE)); assert!(req.headers().contains_key(header::SERVER)); @@ -622,6 +631,7 @@ mod tests { assert!(!req.headers().contains_key(header::ALLOW)); assert!(!req.headers().contains_key(header::EXPECT)); assert_eq!(req.head.version, Version::HTTP_2); + let _ = req.headers_mut(); let _ = req.send_body(""); } From 798d744eefca690af7c61e9b4e0e501a6e9ad89f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:19:56 +0000 Subject: [PATCH 019/187] prepare http release 2.1.0 --- actix-http/CHANGES.md | 11 +++++++---- actix-http/Cargo.toml | 4 ++-- actix-http/src/lib.rs | 4 +++- 3 files changed, 12 insertions(+), 7 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 8533cea55..bb5a962f5 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,18 +1,21 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.1.0 - 2020-10-30 ### Added * Added more flexible `on_connect_ext` methods for on-connect handling. [#1754] ### Changed -* Upgrade `base64` to `0.13`. -* Upgrade `pin-project` to `1.0`. +* Upgrade `base64` to `0.13`. [#1744] +* Upgrade `pin-project` to `1.0`. [#1733] * Deprecate `ResponseBuilder::{if_some, if_true}`. [#1760] [#1760]: https://github.com/actix/actix-web/pull/1760 - - [#1754]: https://github.com/actix/actix-web/pull/1754 +[#1733]: https://github.com/actix/actix-web/pull/1733 +[#1744]: https://github.com/actix/actix-web/pull/1744 ## 2.0.0 - 2020-09-11 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9d2f7464f..a2d55460d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-http" -version = "2.0.0" +version = "2.1.0" authors = ["Nikolay Kim "] -description = "Actix HTTP primitives" +description = "HTTP primitives for the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "futures"] homepage = "https://actix.rs" diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index e57a3727e..89d64fb77 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,4 +1,4 @@ -//! Basic HTTP primitives for the Actix ecosystem. +//! HTTP primitives for the Actix ecosystem. #![deny(rust_2018_idioms)] #![allow( @@ -8,6 +8,8 @@ clippy::borrow_interior_mutable_const )] #![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42). +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #[macro_use] extern crate log; From 156c97cef2e480ab4a17e2233ef11c9add1e92ba Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 02:50:53 +0000 Subject: [PATCH 020/187] prepare awc release 2.0.1 --- actix-http/README.md | 20 ++++++++++++-------- awc/CHANGES.md | 9 +++++++-- awc/Cargo.toml | 4 ++-- awc/README.md | 21 +++++++++++++-------- awc/src/lib.rs | 18 ++++++++++-------- 5 files changed, 44 insertions(+), 28 deletions(-) diff --git a/actix-http/README.md b/actix-http/README.md index 96fc54d2e..e536276ca 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -1,14 +1,18 @@ -# Actix http [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http)](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) +# actix-http -Actix http +> HTTP primitives for the Actix ecosystem. -## Documentation & community resources +[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.1.0)](https://docs.rs/actix-http/2.1.0) +![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) +[![Dependency Status](https://deps.rs/crate/actix-http/2.1.0/status.svg)](https://deps.rs/crate/actix-http/2.1.0) +[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-http/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http](https://crates.io/crates/actix-http) -* Minimum supported Rust version: 1.40 or later +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-http/2.1.0) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.42.0 ## Example diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 0eabe61e9..a5090e7c9 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,15 +1,20 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.0.1 - 2020-xx-xx ### Changed -* Upgrade `base64` to `0.13`. +* Upgrade `base64` to `0.13`. [#1744] * Deprecate `ClientRequest::{if_some, if_true}`. [#1760] ### Fixed -* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature is enabled [#1737] +* Use `Accept-Encoding: identity` instead of `Accept-Encoding: br` when no compression feature + is enabled [#1737] [#1737]: https://github.com/actix/actix-web/pull/1737 [#1760]: https://github.com/actix/actix-web/pull/1760 +[#1744]: https://github.com/actix/actix-web/pull/1744 ## 2.0.0 - 2020-09-11 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b7d8b0a22..049c8e664 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "awc" -version = "2.0.0" +version = "2.0.1" authors = ["Nikolay Kim "] -description = "Async HTTP client library that uses the Actix runtime." +description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" keywords = ["actix", "http", "framework", "async", "web"] homepage = "https://actix.rs" diff --git a/awc/README.md b/awc/README.md index 2b6309c1d..fb2468b4a 100644 --- a/awc/README.md +++ b/awc/README.md @@ -1,14 +1,19 @@ -# Actix http client [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/awc)](https://crates.io/crates/awc) [![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) +# awc (Actix Web Client) -An HTTP Client +> Async HTTP and WebSocket client library. -## Documentation & community resources +[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) +[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.1)](https://docs.rs/awc/2.0.1) +![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) +[![Dependency Status](https://deps.rs/crate/awc/2.0.1/status.svg)](https://deps.rs/crate/awc/2.0.1) +[![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) -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/awc/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [awc](https://crates.io/crates/awc) -* Minimum supported Rust version: 1.40 or later +## Documentation & Resources + +- [API Documentation](https://docs.rs/awc/2.0.1) +- [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.42.0 ## Example diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 45c52092a..fb6ed086a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,11 +1,4 @@ -#![deny(rust_2018_idioms)] -#![allow( - clippy::type_complexity, - clippy::borrow_interior_mutable_const, - clippy::needless_doctest_main -)] - -//! `awc` is a HTTP and WebSocket client library built using the Actix ecosystem. +//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. //! //! ## Making a GET request //! @@ -91,6 +84,15 @@ //! # } //! ``` +#![deny(rust_2018_idioms)] +#![allow( + clippy::type_complexity, + clippy::borrow_interior_mutable_const, + clippy::needless_doctest_main +)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] + use std::cell::RefCell; use std::convert::TryFrom; use std::rc::Rc; From 42f51eb962af48b85b944fe64d24f4a81ad4650e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 30 Oct 2020 03:15:22 +0000 Subject: [PATCH 021/187] prepare web release 3.2.0 --- CHANGES.md | 4 ++++ Cargo.toml | 10 +++++----- README.md | 6 ++++-- awc/CHANGES.md | 2 +- 4 files changed, 14 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 15d44b75c..cc41f839b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 3.2.0 - 2020-10-30 ### Added * Implement `exclude_regex` for Logger middleware. [#1723] * Add request-local data extractor `web::ReqData`. [#1748] @@ -9,6 +12,7 @@ * Expose `on_connect` for access to the connection stream before request is handled. [#1754] ### Changed +* Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. * Print non-configured `Data` type when attempting extraction. [#1743] * Re-export bytes::Buf{Mut} in web module. [#1750] * Upgrade `pin-project` to `1.0`. diff --git a/Cargo.toml b/Cargo.toml index 4fafc61c4..33ec52fae 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-web" -version = "3.1.0" +version = "3.2.0" authors = ["Nikolay Kim "] -description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust." +description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" @@ -84,8 +84,8 @@ actix-macros = "0.1.0" actix-threadpool = "0.3.1" actix-tls = "2.0.0" -actix-web-codegen = "0.3.0" -actix-http = "2.0.0" +actix-web-codegen = "0.4.0" +actix-http = "2.1.0" awc = { version = "2.0.0", default-features = false } bytes = "0.5.3" @@ -111,7 +111,7 @@ tinyvec = { version = "1", features = ["alloc"] } [dev-dependencies] actix = "0.10.0" -actix-http = { version = "2.0.0", features = ["actors"] } +actix-http = { version = "2.1.0", features = ["actors"] } rand = "0.7" env_logger = "0.8" serde_derive = "1.0" diff --git a/README.md b/README.md index 3e3ce8bf1..4a0dae4f8 100644 --- a/README.md +++ b/README.md @@ -5,15 +5,17 @@

-[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg)](https://docs.rs/actix-web) +[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.2.0)](https://docs.rs/actix-web/3.2.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) +[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/2.2.0)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![Download](https://img.shields.io/crates/d/actix-web.svg)](https://crates.io/crates/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

diff --git a/awc/CHANGES.md b/awc/CHANGES.md index a5090e7c9..5e87362f0 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,7 +3,7 @@ ## Unreleased - 2020-xx-xx -## 2.0.1 - 2020-xx-xx +## 2.0.1 - 2020-10-30 ### Changed * Upgrade `base64` to `0.13`. [#1744] * Deprecate `ClientRequest::{if_some, if_true}`. [#1760] From 22b451cf2d3c76872bb337482387c5aa9c8f8cb1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 31 Oct 2020 02:39:54 +0000 Subject: [PATCH 022/187] fix deps.rs badge --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a0dae4f8..b11a8ee7c 100644 --- a/README.md +++ b/README.md @@ -9,7 +9,7 @@ [![Documentation](https://docs.rs/actix-web/badge.svg?version=3.2.0)](https://docs.rs/actix-web/3.2.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/2.2.0) +[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/3.2.0)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) From 5135c1e3a08c140876f5470c5042961d31e3ed19 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 31 Oct 2020 12:06:51 +0900 Subject: [PATCH 023/187] Update CoC contact information --- CODE_OF_CONDUCT.md | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md index 599b28c0d..ae97b3240 100644 --- a/CODE_OF_CONDUCT.md +++ b/CODE_OF_CONDUCT.md @@ -34,10 +34,13 @@ This Code of Conduct applies both within project spaces and in public spaces whe ## Enforcement -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. +Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at robjtede@icloud.com ([@robjtede]) or huyuumi@neet.club ([@JohnTitor]). The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. +[@robjtede]: https://github.com/robjtede +[@JohnTitor]: https://github.com/JohnTitor + ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] From b6385c2b4e00d3fda44fbac0533653ded8135f5b Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Sat, 31 Oct 2020 12:12:19 +0900 Subject: [PATCH 024/187] Remove CoC on actix-http as duplicated --- actix-http/CODE_OF_CONDUCT.md | 46 ----------------------------------- 1 file changed, 46 deletions(-) delete mode 100644 actix-http/CODE_OF_CONDUCT.md diff --git a/actix-http/CODE_OF_CONDUCT.md b/actix-http/CODE_OF_CONDUCT.md deleted file mode 100644 index 599b28c0d..000000000 --- a/actix-http/CODE_OF_CONDUCT.md +++ /dev/null @@ -1,46 +0,0 @@ -# Contributor Covenant Code of Conduct - -## Our Pledge - -In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. - -## Our Standards - -Examples of behavior that contributes to creating a positive environment include: - -* Using welcoming and inclusive language -* Being respectful of differing viewpoints and experiences -* Gracefully accepting constructive criticism -* Focusing on what is best for the community -* Showing empathy towards other community members - -Examples of unacceptable behavior by participants include: - -* The use of sexualized language or imagery and unwelcome sexual attention or advances -* Trolling, insulting/derogatory comments, and personal or political attacks -* Public or private harassment -* Publishing others' private information, such as a physical or electronic address, without explicit permission -* Other conduct which could reasonably be considered inappropriate in a professional setting - -## Our Responsibilities - -Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. - -Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. - -## Scope - -This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. - -## Enforcement - -Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at fafhrd91@gmail.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. - -Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. - -## Attribution - -This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] - -[homepage]: http://contributor-covenant.org -[version]: http://contributor-covenant.org/version/1/4/ From 5468c3c410e408b21e4cfbd4034b72935b24cdcf Mon Sep 17 00:00:00 2001 From: Sebastian Mayr Date: Mon, 2 Nov 2020 03:44:14 -0500 Subject: [PATCH 025/187] Drop content length headers from 101 responses (#1767) Co-authored-by: Sebastian Mayr --- actix-http/CHANGES.md | 4 ++++ actix-http/src/h1/encoder.rs | 39 ++++++++++++++++++++++++++++++------ actix-web-actors/src/ws.rs | 9 ++++----- 3 files changed, 41 insertions(+), 11 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index bb5a962f5..edb1409b2 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] + +[#1767]: https://github.com/actix/actix-web/pull/1767 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index e16b4c3dc..b7648eff1 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -64,14 +64,17 @@ pub(crate) trait MessageType: Sized { // Content length if let Some(status) = self.status() { match status { - StatusCode::NO_CONTENT - | StatusCode::CONTINUE - | StatusCode::PROCESSING => length = BodySize::None, - StatusCode::SWITCHING_PROTOCOLS => { + StatusCode::CONTINUE + | StatusCode::SWITCHING_PROTOCOLS + | StatusCode::PROCESSING + | StatusCode::NO_CONTENT => { + // skip content-length and transfer-encoding headers + // See https://tools.ietf.org/html/rfc7230#section-3.3.1 + // and https://tools.ietf.org/html/rfc7230#section-3.3.2 skip_len = true; - length = BodySize::Stream; + length = BodySize::None } - _ => (), + _ => {} } } match length { @@ -676,4 +679,28 @@ mod tests { assert!(data.contains("authorization: another authorization\r\n")); assert!(data.contains("date: date\r\n")); } + + #[test] + fn test_no_content_length() { + let mut bytes = BytesMut::with_capacity(2048); + + let mut res: Response<()> = + Response::new(StatusCode::SWITCHING_PROTOCOLS).into_body::<()>(); + res.headers_mut() + .insert(DATE, HeaderValue::from_static(&"")); + res.headers_mut() + .insert(CONTENT_LENGTH, HeaderValue::from_static(&"0")); + + let _ = res.encode_headers( + &mut bytes, + Version::HTTP_11, + BodySize::Stream, + ConnectionType::Upgrade, + &ServiceConfig::default(), + ); + let data = + String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + assert!(!data.contains("content-length: 0\r\n")); + assert!(!data.contains("transfer-encoding: chunked\r\n")); + } } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 3f5972532..8fd03f6a1 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -164,7 +164,6 @@ pub fn handshake_with_protocols( let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .header(header::TRANSFER_ENCODING, "chunked") .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) .take(); @@ -664,10 +663,10 @@ mod tests { ) .to_http_request(); - assert_eq!( - StatusCode::SWITCHING_PROTOCOLS, - handshake(&req).unwrap().finish().status() - ); + let resp = handshake(&req).unwrap().finish(); + assert_eq!(StatusCode::SWITCHING_PROTOCOLS, resp.status()); + assert_eq!(None, resp.headers().get(&header::CONTENT_LENGTH)); + assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING)); let req = TestRequest::default() .header( From 61b65aa64ad9b881e9f21d2dc0ff7d554c1937c3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 2 Nov 2020 09:23:18 +0000 Subject: [PATCH 026/187] add common 1xx http response builders (#1768) Co-authored-by: Yuki Okushi --- actix-http/CHANGES.md | 4 ++ actix-http/src/httpcodes.rs | 104 +++++++++++++++++++----------------- 2 files changed, 59 insertions(+), 49 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index edb1409b2..2f4e44142 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,10 +1,14 @@ # Changes ## Unreleased - 2020-xx-xx +### Added +* HttpResponse builders for 1xx status codes. [#1768] + ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] [#1767]: https://github.com/actix/actix-web/pull/1767 +[#1768]: https://github.com/actix/actix-web/pull/1768 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/httpcodes.rs index 0c7f23fc8..f421d8e23 100644 --- a/actix-http/src/httpcodes.rs +++ b/actix-http/src/httpcodes.rs @@ -1,10 +1,12 @@ -//! Basic http responses +//! Status code based HTTP response builders. + #![allow(non_upper_case_globals)] + use http::StatusCode; use crate::response::{Response, ResponseBuilder}; -macro_rules! STATIC_RESP { +macro_rules! static_resp { ($name:ident, $status:expr) => { #[allow(non_snake_case, missing_docs)] pub fn $name() -> ResponseBuilder { @@ -14,63 +16,67 @@ macro_rules! STATIC_RESP { } impl Response { - STATIC_RESP!(Ok, StatusCode::OK); - STATIC_RESP!(Created, StatusCode::CREATED); - STATIC_RESP!(Accepted, StatusCode::ACCEPTED); - STATIC_RESP!( + static_resp!(Continue, StatusCode::CONTINUE); + static_resp!(SwitchingProtocols, StatusCode::SWITCHING_PROTOCOLS); + static_resp!(Processing, StatusCode::PROCESSING); + + static_resp!(Ok, StatusCode::OK); + static_resp!(Created, StatusCode::CREATED); + static_resp!(Accepted, StatusCode::ACCEPTED); + static_resp!( NonAuthoritativeInformation, StatusCode::NON_AUTHORITATIVE_INFORMATION ); - STATIC_RESP!(NoContent, StatusCode::NO_CONTENT); - STATIC_RESP!(ResetContent, StatusCode::RESET_CONTENT); - STATIC_RESP!(PartialContent, StatusCode::PARTIAL_CONTENT); - STATIC_RESP!(MultiStatus, StatusCode::MULTI_STATUS); - STATIC_RESP!(AlreadyReported, StatusCode::ALREADY_REPORTED); + static_resp!(NoContent, StatusCode::NO_CONTENT); + static_resp!(ResetContent, StatusCode::RESET_CONTENT); + static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT); + static_resp!(MultiStatus, StatusCode::MULTI_STATUS); + static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED); - STATIC_RESP!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); - STATIC_RESP!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); - STATIC_RESP!(Found, StatusCode::FOUND); - STATIC_RESP!(SeeOther, StatusCode::SEE_OTHER); - STATIC_RESP!(NotModified, StatusCode::NOT_MODIFIED); - STATIC_RESP!(UseProxy, StatusCode::USE_PROXY); - STATIC_RESP!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); - STATIC_RESP!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); + static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES); + static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY); + static_resp!(Found, StatusCode::FOUND); + static_resp!(SeeOther, StatusCode::SEE_OTHER); + static_resp!(NotModified, StatusCode::NOT_MODIFIED); + static_resp!(UseProxy, StatusCode::USE_PROXY); + static_resp!(TemporaryRedirect, StatusCode::TEMPORARY_REDIRECT); + static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT); - STATIC_RESP!(BadRequest, StatusCode::BAD_REQUEST); - STATIC_RESP!(NotFound, StatusCode::NOT_FOUND); - STATIC_RESP!(Unauthorized, StatusCode::UNAUTHORIZED); - STATIC_RESP!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); - STATIC_RESP!(Forbidden, StatusCode::FORBIDDEN); - STATIC_RESP!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); - STATIC_RESP!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); - STATIC_RESP!( + static_resp!(BadRequest, StatusCode::BAD_REQUEST); + static_resp!(NotFound, StatusCode::NOT_FOUND); + static_resp!(Unauthorized, StatusCode::UNAUTHORIZED); + static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED); + static_resp!(Forbidden, StatusCode::FORBIDDEN); + static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED); + static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE); + static_resp!( ProxyAuthenticationRequired, StatusCode::PROXY_AUTHENTICATION_REQUIRED ); - STATIC_RESP!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); - STATIC_RESP!(Conflict, StatusCode::CONFLICT); - STATIC_RESP!(Gone, StatusCode::GONE); - STATIC_RESP!(LengthRequired, StatusCode::LENGTH_REQUIRED); - STATIC_RESP!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); - STATIC_RESP!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); - STATIC_RESP!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); - STATIC_RESP!(UriTooLong, StatusCode::URI_TOO_LONG); - STATIC_RESP!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); - STATIC_RESP!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); - STATIC_RESP!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); - STATIC_RESP!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); - STATIC_RESP!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); + static_resp!(RequestTimeout, StatusCode::REQUEST_TIMEOUT); + static_resp!(Conflict, StatusCode::CONFLICT); + static_resp!(Gone, StatusCode::GONE); + static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED); + static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED); + static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED); + static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE); + static_resp!(UriTooLong, StatusCode::URI_TOO_LONG); + static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE); + static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE); + static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); + static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); + static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); - STATIC_RESP!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); - STATIC_RESP!(NotImplemented, StatusCode::NOT_IMPLEMENTED); - STATIC_RESP!(BadGateway, StatusCode::BAD_GATEWAY); - STATIC_RESP!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); - STATIC_RESP!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); - STATIC_RESP!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); - STATIC_RESP!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); - STATIC_RESP!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); - STATIC_RESP!(LoopDetected, StatusCode::LOOP_DETECTED); + static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); + static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); + static_resp!(BadGateway, StatusCode::BAD_GATEWAY); + static_resp!(ServiceUnavailable, StatusCode::SERVICE_UNAVAILABLE); + static_resp!(GatewayTimeout, StatusCode::GATEWAY_TIMEOUT); + static_resp!(VersionNotSupported, StatusCode::HTTP_VERSION_NOT_SUPPORTED); + static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES); + static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE); + static_resp!(LoopDetected, StatusCode::LOOP_DETECTED); } #[cfg(test)] From ceac97bb8d11053002987a3ba735fd265a9d65ea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 4 Nov 2020 15:08:12 +0000 Subject: [PATCH 027/187] Update config.yml --- .github/ISSUE_TEMPLATE/config.yml | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml index 3e62958d8..d8c6d66ca 100644 --- a/.github/ISSUE_TEMPLATE/config.yml +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -1,8 +1,15 @@ blank_issues_enabled: true contact_links: - - name: Gitter channel (actix-web) + - name: GitHub Discussions + url: https://github.com/actix/actix-web/discussions + about: Actix Web Q&A + - name: Gitter chat (actix-web) url: https://gitter.im/actix/actix-web - about: Please ask and answer questions about the actix-web here. - - name: Gitter channel (actix) + about: Actix Web Q&A + - name: Gitter chat (actix) url: https://gitter.im/actix/actix - about: Please ask and answer questions about the actix here. + about: Actix (actor framework) Q&A + - name: Actix Discord + url: https://discord.gg/NWpN5mmg3x + about: Actix developer discussion and community chat + From 9b6a089b3675d07805ce4b184b6a317418c7e5da Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 5 Nov 2020 06:20:01 +0800 Subject: [PATCH 028/187] fix awc doc example (#1772) * fix awc readme example Co-authored-by: Rob Ede --- awc/README.md | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/awc/README.md b/awc/README.md index fb2468b4a..cbe299aaf 100644 --- a/awc/README.md +++ b/awc/README.md @@ -16,23 +16,21 @@ - Minimum Supported Rust Version (MSRV): 1.42.0 ## Example - ```rust use actix_rt::System; use awc::Client; -use futures::future::{Future, lazy}; fn main() { - System::new("test").block_on(lazy(|| { - let mut client = Client::default(); + System::new("test").block_on(async { + let client = Client::default(); - client.get("http://www.rust-lang.org") // <- Create request builder - .header("User-Agent", "Actix-web") - .send() // <- Send http request - .and_then(|response| { // <- server http response - println!("Response: {:?}", response); - Ok(()) - }) - })); + let res = client + .get("http://www.rust-lang.org") // <- Create request builder + .header("User-Agent", "Actix-web") + .send() // <- Send http request + .await; + + println!("Response: {:?}", res); // <- server http response + }); } ``` From 4bfd5c278177847623463594edb0187186c304a4 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Fri, 6 Nov 2020 01:36:15 +0900 Subject: [PATCH 029/187] Upgrade `serde_urlencoded` to `0.7` (#1773) --- CHANGES.md | 2 ++ Cargo.toml | 2 +- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 3 ++- awc/Cargo.toml | 2 +- test-server/CHANGES.md | 1 + test-server/Cargo.toml | 2 +- 8 files changed, 12 insertions(+), 5 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index cc41f839b..9277d5bd2 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed +* Upgrade `serde_urlencoded` to `0.7`. ## 3.2.0 - 2020-10-30 diff --git a/Cargo.toml b/Cargo.toml index 33ec52fae..da511888a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -102,7 +102,7 @@ pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { package = "openssl", version = "0.10", optional = true } diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 2f4e44142..eeed4e14b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,9 @@ ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] +### Changed +* Upgrade `serde_urlencoded` to `0.7`. + [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a2d55460d..31495e395 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -78,7 +78,7 @@ serde = "1.0" serde_json = "1.0" sha-1 = "0.9" slab = "0.4" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } # compression diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 5e87362f0..e184dfbd1 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx - +### Changed +* Upgrade `serde_urlencoded` to `0.7`. ## 2.0.1 - 2020-10-30 ### Changed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 049c8e664..d92996fb9 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -53,7 +53,7 @@ percent-encoding = "2.1" rand = "0.7" serde = "1.0" serde_json = "1.0" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" open-ssl = { version = "0.10", package = "openssl", optional = true } rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index e3a59e5bf..845b6e2dc 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -4,6 +4,7 @@ * add ability to set address for `TestServer` [#1645] * Upgrade `base64` to `0.13`. +* Upgrade `serde_urlencoded` to `0.7`. [#1645]: https://github.com/actix/actix-web/pull/1645 diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 3ee6b8a30..87db93469 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -47,7 +47,7 @@ socket2 = "0.3" serde = "1.0" serde_json = "1.0" slab = "0.4" -serde_urlencoded = "0.6.1" +serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } open-ssl = { version = "0.10", package = "openssl", optional = true } From e5b86d189c1326f6260ee1d8c4b5af14d93afdff Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Thu, 5 Nov 2020 18:46:17 +0100 Subject: [PATCH 030/187] Fix typo in request_data.rs (#1774) --- src/request_data.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/request_data.rs b/src/request_data.rs index c01930418..285154884 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -50,7 +50,7 @@ use crate::{dev::Payload, FromRequest, HttpRequest}; pub struct ReqData(T); impl ReqData { - /// Consumes the `ReqData`, returning it's wrapped data. + /// Consumes the `ReqData`, returning its wrapped data. pub fn into_inner(self) -> T { self.0 } From 9b42333fac0c37dd2a68a3f7cec66b23e344a63b Mon Sep 17 00:00:00 2001 From: Pouya Mobasher Behrouz Date: Fri, 6 Nov 2020 17:04:42 +0330 Subject: [PATCH 031/187] Fix typo in Query extractor docs (#1777) --- src/types/query.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/query.rs b/src/types/query.rs index f9440e1b4..7eded49c5 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -39,7 +39,7 @@ use crate::request::HttpRequest; /// } /// /// // Use `Query` extractor for query information (and destructure it within the signature). -/// // This handler gets called only if the request's query string contains a `username` field. +/// // This handler gets called only if the request's query string contains `id` and `response_type` fields. /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. /// async fn index(web::Query(info): web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) @@ -117,7 +117,7 @@ impl fmt::Display for Query { /// } /// /// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `username` field +/// // This handler get called only if request's query contains `id` and `response_type` fields. /// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` /// async fn index(info: web::Query) -> String { /// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) From 49e945c88f9582eb1bf7b52b22975b3aef2a3200 Mon Sep 17 00:00:00 2001 From: Aravinth Date: Mon, 9 Nov 2020 19:31:36 +0530 Subject: [PATCH 032/187] switching to nightly for intra-doc links (#1783) --- .github/workflows/upload-doc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 99fa663cc..cdf05a979 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -16,7 +16,7 @@ jobs: - name: Install Rust uses: actions-rs/toolchain@v1 with: - toolchain: stable-x86_64-unknown-linux-gnu + toolchain: nightly-x86_64-unknown-linux-gnu profile: minimal override: true From a929209967f87577387c16831427bb7801677cc1 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Wed, 11 Nov 2020 05:24:38 +0530 Subject: [PATCH 033/187] `actix-files` intra-doc migration (#1785) --- actix-files/src/named.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index dacb51136..a9b95bad1 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -193,7 +193,7 @@ impl NamedFile { /// image, and video content types, and `attachment` otherwise, and /// the filename is taken from the path provided in the `open` method /// after converting it to UTF-8 using. - /// [to_string_lossy](https://doc.rust-lang.org/std/ffi/struct.OsStr.html#method.to_string_lossy). + /// [`std::ffi::OsStr::to_string_lossy`] #[inline] pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { self.content_disposition = cd; From 4100c50c703798027203310502d3e9a860b9a032 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 20 Nov 2020 18:02:41 +0000 Subject: [PATCH 034/187] add either extractor (#1788) --- CHANGES.md | 5 + src/error.rs | 1 + src/lib.rs | 3 +- src/responder.rs | 76 ------------ src/types/either.rs | 274 ++++++++++++++++++++++++++++++++++++++++++++ src/types/mod.rs | 2 + 6 files changed, 284 insertions(+), 77 deletions(-) create mode 100644 src/types/either.rs diff --git a/CHANGES.md b/CHANGES.md index 9277d5bd2..b928712d0 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,9 +1,14 @@ # Changes ## Unreleased - 2020-xx-xx +### Added +* Add `Either` extractor helper. [#1788] + ### Changed * Upgrade `serde_urlencoded` to `0.7`. +[#1788]: https://github.com/actix/actix-web/pull/1788 + ## 3.2.0 - 2020-10-30 ### Added diff --git a/src/error.rs b/src/error.rs index 659ba05fd..60af8fa11 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,5 @@ //! Error and Result module + pub use actix_http::error::*; use derive_more::{Display, From}; use serde_json::error::Error as JsonError; diff --git a/src/lib.rs b/src/lib.rs index 088444e05..13e02c098 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -102,10 +102,11 @@ pub use crate::app::App; pub use crate::extract::FromRequest; pub use crate::request::HttpRequest; pub use crate::resource::Resource; -pub use crate::responder::{Either, Responder}; +pub use crate::responder::Responder; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; +pub use crate::types::{Either, EitherExtractError}; pub mod dev { //! The `actix-web` prelude for library developers diff --git a/src/responder.rs b/src/responder.rs index fc80831b8..d1c22323f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -332,82 +332,6 @@ impl Future for CustomResponderFut { } } -/// Combines two different responder types into a single type -/// -/// ```rust -/// use actix_web::{Either, Error, HttpResponse}; -/// -/// type RegisterResult = Either>; -/// -/// fn index() -> RegisterResult { -/// if is_a_variant() { -/// // <- choose left variant -/// Either::A(HttpResponse::BadRequest().body("Bad data")) -/// } else { -/// Either::B( -/// // <- Right variant -/// Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!")) -/// ) -/// } -/// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} -/// ``` -#[derive(Debug, PartialEq)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), -} - -impl Responder for Either -where - A: Responder, - B: Responder, -{ - type Error = Error; - type Future = EitherResponder; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - match self { - Either::A(a) => EitherResponder::A(a.respond_to(req)), - Either::B(b) => EitherResponder::B(b.respond_to(req)), - } - } -} - -#[pin_project(project = EitherResponderProj)] -pub enum EitherResponder -where - A: Responder, - B: Responder, -{ - A(#[pin] A::Future), - B(#[pin] B::Future), -} - -impl Future for EitherResponder -where - A: Responder, - B: Responder, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { - EitherResponderProj::A(fut) => { - Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) - } - EitherResponderProj::B(fut) => { - Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) - } - } - } -} - impl Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, diff --git a/src/types/either.rs b/src/types/either.rs new file mode 100644 index 000000000..9f1d81a0b --- /dev/null +++ b/src/types/either.rs @@ -0,0 +1,274 @@ +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::{Error, Response}; +use bytes::Bytes; +use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt}; +use pin_project::pin_project; + +use crate::{dev, request::HttpRequest, FromRequest, Responder}; + +/// Combines two different responder types into a single type +/// +/// ```rust +/// use actix_web::{Either, Error, HttpResponse}; +/// +/// type RegisterResult = Either>; +/// +/// fn index() -> RegisterResult { +/// if is_a_variant() { +/// // <- choose left variant +/// Either::A(HttpResponse::BadRequest().body("Bad data")) +/// } else { +/// Either::B( +/// // <- Right variant +/// Ok(HttpResponse::Ok() +/// .content_type("text/html") +/// .body("Hello!")) +/// ) +/// } +/// } +/// # fn is_a_variant() -> bool { true } +/// # fn main() {} +/// ``` +#[derive(Debug, PartialEq)] +pub enum Either { + /// First branch of the type + A(A), + /// Second branch of the type + B(B), +} + +#[cfg(test)] +impl Either { + pub(self) fn unwrap_left(self) -> A { + match self { + Either::A(data) => data, + Either::B(_) => { + panic!("Cannot unwrap left branch. Either contains a right branch.") + } + } + } + + pub(self) fn unwrap_right(self) -> B { + match self { + Either::A(_) => { + panic!("Cannot unwrap right branch. Either contains a left branch.") + } + Either::B(data) => data, + } + } +} + +impl Responder for Either +where + A: Responder, + B: Responder, +{ + type Error = Error; + type Future = EitherResponder; + + fn respond_to(self, req: &HttpRequest) -> Self::Future { + match self { + Either::A(a) => EitherResponder::A(a.respond_to(req)), + Either::B(b) => EitherResponder::B(b.respond_to(req)), + } + } +} + +#[pin_project(project = EitherResponderProj)] +pub enum EitherResponder +where + A: Responder, + B: Responder, +{ + A(#[pin] A::Future), + B(#[pin] B::Future), +} + +impl Future for EitherResponder +where + A: Responder, + B: Responder, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.project() { + EitherResponderProj::A(fut) => { + Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) + } + EitherResponderProj::B(fut) => { + Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) + } + } + } +} + +/// A composite error resulting from failure to extract an `Either`. +/// +/// The implementation of `Into` will return the payload buffering error or the +/// error from the primary extractor. To access the fallback error, use a match clause. +#[derive(Debug)] +pub enum EitherExtractError { + /// Error from payload buffering, such as exceeding payload max size limit. + Bytes(Error), + + /// Error from primary extractor. + Extract(A, B), +} + +impl Into for EitherExtractError +where + A: Into, + B: Into, +{ + fn into(self) -> Error { + match self { + EitherExtractError::Bytes(err) => err, + EitherExtractError::Extract(a_err, _b_err) => a_err.into(), + } + } +} + +/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for +/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. +/// +/// It is important to note that this extractor, by necessity, buffers the entire request payload +/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit. +impl FromRequest for Either +where + A: FromRequest + 'static, + B: FromRequest + 'static, +{ + type Error = EitherExtractError; + type Future = LocalBoxFuture<'static, Result>; + type Config = (); + + fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { + let req2 = req.clone(); + + Bytes::from_request(req, payload) + .map_err(EitherExtractError::Bytes) + .and_then(|bytes| bytes_to_a_or_b(req2, bytes)) + .boxed_local() + } +} + +async fn bytes_to_a_or_b( + req: HttpRequest, + bytes: Bytes, +) -> Result, EitherExtractError> +where + A: FromRequest + 'static, + B: FromRequest + 'static, +{ + let fallback = bytes.clone(); + let a_err; + + let mut pl = payload_from_bytes(bytes); + match A::from_request(&req, &mut pl).await { + Ok(a_data) => return Ok(Either::A(a_data)), + // store A's error for returning if B also fails + Err(err) => a_err = err, + }; + + let mut pl = payload_from_bytes(fallback); + match B::from_request(&req, &mut pl).await { + Ok(b_data) => return Ok(Either::B(b_data)), + Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)), + } +} + +fn payload_from_bytes(bytes: Bytes) -> dev::Payload { + let (_, mut h1_payload) = actix_http::h1::Payload::create(true); + h1_payload.unread_data(bytes); + dev::Payload::from(h1_payload) +} + +#[cfg(test)] +mod tests { + use serde::{Deserialize, Serialize}; + + use super::*; + use crate::{ + test::TestRequest, + web::{Form, Json}, + }; + + #[derive(Debug, Clone, Serialize, Deserialize)] + struct TestForm { + hello: String, + } + + #[actix_rt::test] + async fn test_either_extract_first_try() { + let (req, mut pl) = TestRequest::default() + .set_form(&TestForm { + hello: "world".to_owned(), + }) + .to_http_parts(); + + let form = Either::, Json>::from_request(&req, &mut pl) + .await + .unwrap() + .unwrap_left() + .into_inner(); + assert_eq!(&form.hello, "world"); + } + + #[actix_rt::test] + async fn test_either_extract_fallback() { + let (req, mut pl) = TestRequest::default() + .set_json(&TestForm { + hello: "world".to_owned(), + }) + .to_http_parts(); + + let form = Either::, Json>::from_request(&req, &mut pl) + .await + .unwrap() + .unwrap_right() + .into_inner(); + assert_eq!(&form.hello, "world"); + } + + #[actix_rt::test] + async fn test_either_extract_recursive_fallback() { + let (req, mut pl) = TestRequest::default() + .set_payload(Bytes::from_static(b"!@$%^&*()")) + .to_http_parts(); + + let payload = + Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_right(); + assert_eq!(&payload.as_ref(), &b"!@$%^&*()"); + } + + #[actix_rt::test] + async fn test_either_extract_recursive_fallback_inner() { + let (req, mut pl) = TestRequest::default() + .set_json(&TestForm { + hello: "world".to_owned(), + }) + .to_http_parts(); + + let form = + Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_left() + .unwrap_right() + .into_inner(); + assert_eq!(&form.hello, "world"); + } +} diff --git a/src/types/mod.rs b/src/types/mod.rs index b32711e2a..cedf86dd2 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,6 @@ //! Helper types +mod either; pub(crate) mod form; pub(crate) mod json; mod path; @@ -7,6 +8,7 @@ pub(crate) mod payload; mod query; pub(crate) mod readlines; +pub use self::either::{Either, EitherExtractError}; pub use self::form::{Form, FormConfig}; pub use self::json::{Json, JsonConfig}; pub use self::path::{Path, PathConfig}; From 2f11ef089bed1b03f5312b80068e2c50deda325b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 00:29:13 +0000 Subject: [PATCH 035/187] fix rustdoc uploads --- .github/workflows/upload-doc.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index cdf05a979..ba87a5637 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -30,7 +30,7 @@ jobs: run: echo "" > target/doc/index.html - name: Deploy to GitHub Pages - uses: JamesIves/github-pages-deploy-action@3.5.8 + uses: JamesIves/github-pages-deploy-action@3.7.1 with: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} BRANCH: gh-pages From 70f4747a2391ce870c0a33383a2ddc38c7bb1e99 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 10:08:57 +0000 Subject: [PATCH 036/187] add method for getting accept type preference (#1793) --- actix-http/CHANGES.md | 2 + actix-http/src/header/common/accept.rs | 141 ++++++++++++++++++- actix-http/src/header/shared/quality_item.rs | 4 +- 3 files changed, 138 insertions(+), 9 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index eeed4e14b..cafaa5e09 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2020-xx-xx ### Added * HttpResponse builders for 1xx status codes. [#1768] +* `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] @@ -12,6 +13,7 @@ [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 +[#1793]: https://github.com/actix/actix-web/pull/1793 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/header/common/accept.rs b/actix-http/src/header/common/accept.rs index d52eba241..da26b0261 100644 --- a/actix-http/src/header/common/accept.rs +++ b/actix-http/src/header/common/accept.rs @@ -1,3 +1,5 @@ +use std::cmp::Ordering; + use mime::Mime; use crate::header::{qitem, QualityItem}; @@ -7,7 +9,7 @@ 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 - /// response media types that are acceptable. Accept header fields can + /// response media types that are acceptable. Accept header fields can /// be used to indicate that the request is specifically limited to a /// small set of desired types, as in the case of a request for an /// in-line image @@ -97,14 +99,14 @@ header! { test_header!( test1, vec![b"audio/*; q=0.2, audio/basic"], - Some(HeaderField(vec![ + Some(Accept(vec![ QualityItem::new("audio/*".parse().unwrap(), q(200)), qitem("audio/basic".parse().unwrap()), ]))); test_header!( test2, vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"], - Some(HeaderField(vec![ + Some(Accept(vec![ QualityItem::new(mime::TEXT_PLAIN, q(500)), qitem(mime::TEXT_HTML), QualityItem::new( @@ -138,23 +140,148 @@ header! { } impl Accept { - /// A constructor to easily create `Accept: */*`. + /// Construct `Accept: */*`. pub fn star() -> Accept { Accept(vec![qitem(mime::STAR_STAR)]) } - /// A constructor to easily create `Accept: application/json`. + /// Construct `Accept: application/json`. pub fn json() -> Accept { Accept(vec![qitem(mime::APPLICATION_JSON)]) } - /// A constructor to easily create `Accept: text/*`. + /// Construct `Accept: text/*`. pub fn text() -> Accept { Accept(vec![qitem(mime::TEXT_STAR)]) } - /// A constructor to easily create `Accept: image/*`. + /// Construct `Accept: image/*`. pub fn image() -> Accept { Accept(vec![qitem(mime::IMAGE_STAR)]) } + + /// Construct `Accept: text/html`. + pub fn html() -> Accept { + Accept(vec![qitem(mime::TEXT_HTML)]) + } + + /// Returns a sorted list of mime types from highest to lowest preference, accounting for + /// [q-factor weighting] and specificity. + /// + /// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2 + pub fn mime_precedence(&self) -> Vec { + let mut types = self.0.clone(); + + // use stable sort so items with equal q-factor and specificity retain listed order + types.sort_by(|a, b| { + // sort by q-factor descending + b.quality.cmp(&a.quality).then_with(|| { + // use specificity rules on mime types with + // same q-factor (eg. text/html > text/* > */*) + + // subtypes are not comparable if main type is star, so return + match (a.item.type_(), b.item.type_()) { + (mime::STAR, mime::STAR) => return Ordering::Equal, + + // a is sorted after b + (mime::STAR, _) => return Ordering::Greater, + + // a is sorted before b + (_, mime::STAR) => return Ordering::Less, + + _ => {} + } + + // in both these match expressions, the returned ordering appears + // inverted because sort is high-to-low ("descending") precedence + match (a.item.subtype(), b.item.subtype()) { + (mime::STAR, mime::STAR) => Ordering::Equal, + + // a is sorted after b + (mime::STAR, _) => Ordering::Greater, + + // a is sorted before b + (_, mime::STAR) => Ordering::Less, + + _ => Ordering::Equal, + } + }) + }); + + types.into_iter().map(|qitem| qitem.item).collect() + } + + /// Extracts the most preferable mime type, accounting for [q-factor weighting]. + /// + /// If no q-factors are provided, the first mime type is chosen. Note that items without + /// q-factors are given the maximum preference value. + /// + /// Returns `None` if contained list is empty. + /// + /// [q-factor weighting]: https://tools.ietf.org/html/rfc7231#section-5.3.2 + pub fn mime_preference(&self) -> Option { + let types = self.mime_precedence(); + types.first().cloned() + } +} + +#[cfg(test)] +mod tests { + use super::*; + use crate::header::q; + + #[test] + fn test_mime_precedence() { + let test = Accept(vec![]); + assert!(test.mime_precedence().is_empty()); + + let test = Accept(vec![qitem(mime::APPLICATION_JSON)]); + assert_eq!(test.mime_precedence(), vec!(mime::APPLICATION_JSON)); + + let test = Accept(vec![ + qitem(mime::TEXT_HTML), + "application/xhtml+xml".parse().unwrap(), + QualityItem::new("application/xml".parse().unwrap(), q(0.9)), + QualityItem::new(mime::STAR_STAR, q(0.8)), + ]); + assert_eq!( + test.mime_precedence(), + vec![ + mime::TEXT_HTML, + "application/xhtml+xml".parse().unwrap(), + "application/xml".parse().unwrap(), + mime::STAR_STAR, + ] + ); + + let test = Accept(vec![ + qitem(mime::STAR_STAR), + qitem(mime::IMAGE_STAR), + qitem(mime::IMAGE_PNG), + ]); + assert_eq!( + test.mime_precedence(), + vec![mime::IMAGE_PNG, mime::IMAGE_STAR, mime::STAR_STAR] + ); + } + + #[test] + fn test_mime_preference() { + let test = Accept(vec![ + qitem(mime::TEXT_HTML), + "application/xhtml+xml".parse().unwrap(), + QualityItem::new("application/xml".parse().unwrap(), q(0.9)), + QualityItem::new(mime::STAR_STAR, q(0.8)), + ]); + assert_eq!(test.mime_preference(), Some(mime::TEXT_HTML)); + + let test = Accept(vec![ + QualityItem::new("video/*".parse().unwrap(), q(0.8)), + qitem(mime::IMAGE_PNG), + QualityItem::new(mime::STAR_STAR, q(0.5)), + qitem(mime::IMAGE_SVG), + QualityItem::new(mime::IMAGE_STAR, q(0.8)), + ]); + assert_eq!(test.mime_preference(), Some(mime::IMAGE_PNG)); + } } diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 98230dec1..f4331a18e 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -18,7 +18,7 @@ use self::internal::IntoQuality; /// /// [RFC7231 Section 5.3.1](https://tools.ietf.org/html/rfc7231#section-5.3.1) /// gives more information on quality values in HTTP header fields. -#[derive(Copy, Clone, Debug, Eq, Ord, PartialEq, PartialOrd)] +#[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); impl Default for Quality { @@ -121,7 +121,7 @@ fn from_f32(f: f32) -> Quality { /// Convenience function to wrap a value in a `QualityItem` /// Sets `q` to the default 1.0 pub fn qitem(item: T) -> QualityItem { - QualityItem::new(item, Default::default()) + QualityItem::new(item, Quality::default()) } /// Convenience function to create a `Quality` from a float or integer. From 5af46775b8d89f3bc005e725a9918f316e6459f9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 11:37:05 +0000 Subject: [PATCH 037/187] refactor quality and use TryFrom instead of custom trait (#1797) --- actix-http/CHANGES.md | 2 + actix-http/src/header/mod.rs | 4 +- actix-http/src/header/shared/charset.rs | 4 +- actix-http/src/header/shared/quality_item.rs | 182 +++++++++++-------- 4 files changed, 106 insertions(+), 86 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index cafaa5e09..a25f44e36 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,7 @@ ### Added * HttpResponse builders for 1xx status codes. [#1768] * `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] +* `TryFrom` and `TryFrom` for `http::header::Quality`. [#1797] ### Fixed * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] @@ -14,6 +15,7 @@ [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 [#1793]: https://github.com/actix/actix-web/pull/1793 +[#1797]: https://github.com/actix/actix-web/pull/1797 ## 2.1.0 - 2020-10-30 diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 46fb31a62..0f87516eb 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -370,9 +370,7 @@ impl fmt::Display for ExtendedValue { } /// Percent encode a sequence of bytes with a character set defined in -/// [https://tools.ietf.org/html/rfc5987#section-3.2][url] -/// -/// [url]: https://tools.ietf.org/html/rfc5987#section-3.2 +/// pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); fmt::Display::fmt(&encoded, f) diff --git a/actix-http/src/header/shared/charset.rs b/actix-http/src/header/shared/charset.rs index 00e7309d4..36bdbf7e2 100644 --- a/actix-http/src/header/shared/charset.rs +++ b/actix-http/src/header/shared/charset.rs @@ -7,9 +7,7 @@ use self::Charset::*; /// /// The string representation is normalized to upper case. /// -/// See [http://www.iana.org/assignments/character-sets/character-sets.xhtml][url]. -/// -/// [url]: http://www.iana.org/assignments/character-sets/character-sets.xhtml +/// See . #[derive(Clone, Debug, PartialEq)] #[allow(non_camel_case_types)] pub enum Charset { diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index f4331a18e..01a3b988a 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -1,10 +1,17 @@ -use std::{cmp, fmt, str}; +use std::{ + cmp, + convert::{TryFrom, TryInto}, + fmt, str, +}; -use self::internal::IntoQuality; +use derive_more::{Display, Error}; + +const MAX_QUALITY: u16 = 1000; +const MAX_FLOAT_QUALITY: f32 = 1.0; /// Represents a quality used in quality values. /// -/// Can be created with the `q` function. +/// Can be created with the [`q`] function. /// /// # Implementation notes /// @@ -21,9 +28,51 @@ use self::internal::IntoQuality; #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct Quality(u16); +impl Quality { + /// # Panics + /// Panics in debug mode when value is not in the range 0.0 <= n <= 1.0. + fn from_f32(value: f32) -> Self { + // Check that `value` is within range should be done before calling this method. + // Just in case, this debug_assert should catch if we were forgetful. + debug_assert!( + (0.0f32..=1.0f32).contains(&value), + "q value must be between 0.0 and 1.0" + ); + + Quality((value * MAX_QUALITY as f32) as u16) + } +} + impl Default for Quality { fn default() -> Quality { - Quality(1000) + Quality(MAX_QUALITY) + } +} + +#[derive(Debug, Clone, Display, Error)] +pub struct QualityOutOfBounds; + +impl TryFrom for Quality { + type Error = QualityOutOfBounds; + + fn try_from(value: u16) -> Result { + if (0..=MAX_QUALITY).contains(&value) { + Ok(Quality(value)) + } else { + Err(QualityOutOfBounds) + } + } +} + +impl TryFrom for Quality { + type Error = QualityOutOfBounds; + + fn try_from(value: f32) -> Result { + if (0.0..=MAX_FLOAT_QUALITY).contains(&value) { + Ok(Quality::from_f32(value)) + } else { + Err(QualityOutOfBounds) + } } } @@ -55,8 +104,9 @@ impl cmp::PartialOrd for QualityItem { impl fmt::Display for QualityItem { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { fmt::Display::fmt(&self.item, f)?; + match self.quality.0 { - 1000 => Ok(()), + MAX_QUALITY => Ok(()), 0 => f.write_str("; q=0"), x => write!(f, "; q=0.{}", format!("{:03}", x).trim_end_matches('0')), } @@ -66,56 +116,61 @@ impl fmt::Display for QualityItem { impl str::FromStr for QualityItem { type Err = crate::error::ParseError; - fn from_str(s: &str) -> Result, crate::error::ParseError> { - if !s.is_ascii() { + fn from_str(qitem_str: &str) -> Result, crate::error::ParseError> { + if !qitem_str.is_ascii() { return Err(crate::error::ParseError::Header); } + // Set defaults used if parsing fails. - let mut raw_item = s; + let mut raw_item = qitem_str; let mut quality = 1f32; - let parts: Vec<&str> = s.rsplitn(2, ';').map(|x| x.trim()).collect(); + let parts: Vec<_> = qitem_str.rsplitn(2, ';').map(str::trim).collect(); + if parts.len() == 2 { + // example for item with q-factor: + // + // gzip; q=0.65 + // ^^^^^^ parts[0] + // ^^ start + // ^^^^ q_val + // ^^^^ parts[1] + if parts[0].len() < 2 { + // Can't possibly be an attribute since an attribute needs at least a name followed + // by an equals sign. And bare identifiers are forbidden. return Err(crate::error::ParseError::Header); } + let start = &parts[0][0..2]; + if start == "q=" || start == "Q=" { - let q_part = &parts[0][2..parts[0].len()]; - if q_part.len() > 5 { + let q_val = &parts[0][2..]; + if q_val.len() > 5 { + // longer than 5 indicates an over-precise q-factor return Err(crate::error::ParseError::Header); } - match q_part.parse::() { - Ok(q_value) => { - if 0f32 <= q_value && q_value <= 1f32 { - quality = q_value; - raw_item = parts[1]; - } else { - return Err(crate::error::ParseError::Header); - } - } - Err(_) => return Err(crate::error::ParseError::Header), + + let q_value = q_val + .parse::() + .map_err(|_| crate::error::ParseError::Header)?; + + if (0f32..=1f32).contains(&q_value) { + quality = q_value; + raw_item = parts[1]; + } else { + return Err(crate::error::ParseError::Header); } } } - match raw_item.parse::() { - // we already checked above that the quality is within range - Ok(item) => Ok(QualityItem::new(item, from_f32(quality))), - Err(_) => Err(crate::error::ParseError::Header), - } - } -} -#[inline] -fn from_f32(f: f32) -> Quality { - // this function is only used internally. A check that `f` is within range - // should be done before calling this method. Just in case, this - // debug_assert should catch if we were forgetful - debug_assert!( - f >= 0f32 && f <= 1f32, - "q value must be between 0.0 and 1.0" - ); - Quality((f * 1000f32) as u16) + let item = raw_item + .parse::() + .map_err(|_| crate::error::ParseError::Header)?; + + // we already checked above that the quality is within range + Ok(QualityItem::new(item, Quality::from_f32(quality))) + } } /// Convenience function to wrap a value in a `QualityItem` @@ -127,44 +182,13 @@ pub fn qitem(item: T) -> QualityItem { /// Convenience function to create a `Quality` from a float or integer. /// /// Implemented for `u16` and `f32`. Panics if value is out of range. -pub fn q(val: T) -> Quality { - val.into_quality() -} - -mod internal { - use super::Quality; - - // TryFrom is probably better, but it's not stable. For now, we want to - // keep the functionality of the `q` function, while allowing it to be - // generic over `f32` and `u16`. - // - // `q` would panic before, so keep that behavior. `TryFrom` can be - // introduced later for a non-panicking conversion. - - pub trait IntoQuality: Sealed + Sized { - fn into_quality(self) -> Quality; - } - - impl IntoQuality for f32 { - fn into_quality(self) -> Quality { - assert!( - self >= 0f32 && self <= 1f32, - "float must be between 0.0 and 1.0" - ); - super::from_f32(self) - } - } - - impl IntoQuality for u16 { - fn into_quality(self) -> Quality { - assert!(self <= 1000, "u16 must be between 0 and 1000"); - Quality(self) - } - } - - pub trait Sealed {} - impl Sealed for u16 {} - impl Sealed for f32 {} +pub fn q(val: T) -> Quality +where + T: TryInto, + T::Error: fmt::Debug, +{ + // TODO: on next breaking change, handle unwrap differently + val.try_into().unwrap() } #[cfg(test)] @@ -270,15 +294,13 @@ mod tests { } #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + #[should_panic] fn test_quality_invalid() { q(-1.0); } #[test] - #[should_panic] // FIXME - 32-bit msvc unwinding broken - #[cfg_attr(all(target_arch = "x86", target_env = "msvc"), ignore)] + #[should_panic] fn test_quality_invalid2() { q(2.0); } From f1a9b45437c1f2c8e89b3a54b854398c09c7e21e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 20:23:09 +0000 Subject: [PATCH 038/187] improve docs for Files::new --- actix-files/CHANGES.md | 4 ++++ actix-files/src/files.rs | 26 +++++++++++++++++++------- 2 files changed, 23 insertions(+), 7 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 978d1c69d..c4d56010f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2020-xx-xx +## 0.4.1 - 2020-11-24 +* Clarify order of parameters in `Files::new` and improve docs. + + ## 0.4.0 - 2020-10-06 * Add `Files::prefer_utf8` option that adds UTF-8 charset on certain response types. [#1714] diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 5a783e2dd..a99b4699e 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -65,13 +65,25 @@ impl Clone for Files { } impl Files { - /// Create new `Files` instance for specified base directory. + /// Create new `Files` instance for a specified base directory. /// - /// `File` uses `ThreadPool` for blocking filesystem operations. - /// By default pool with 5x threads of available cpus is used. - /// Pool size can be changed by setting ACTIX_THREADPOOL environment variable. - pub fn new>(path: &str, dir: T) -> Files { - let orig_dir = dir.into(); + /// # Argument Order + /// The first argument (`mount_path`) is the root URL at which the static files are served. + /// For example, `/assets` will serve files at `example.com/assets/...`. + /// + /// The second argument (`serve_from`) is the location on disk at which files are loaded. + /// This can be a relative path. For example, `./` would serve files from the current + /// working directory. + /// + /// # Implementation Notes + /// If the mount path is set as the root path `/`, services registered after this one will + /// be inaccessible. Register more specific handlers and services first. + /// + /// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a + /// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed + /// by setting ACTIX_THREADPOOL environment variable. + pub fn new>(mount_path: &str, serve_from: T) -> Files { + let orig_dir = serve_from.into(); let dir = match orig_dir.canonicalize() { Ok(canon_dir) => canon_dir, Err(_) => { @@ -81,7 +93,7 @@ impl Files { }; Files { - path: path.to_string(), + path: mount_path.to_owned(), directory: dir, index: None, show_index: false, From 31057beccabbaf82ce8198769dbb940efe7c4a36 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 24 Nov 2020 20:33:23 +0000 Subject: [PATCH 039/187] prepare actix-files release 0.4.1 --- actix-files/Cargo.toml | 8 ++++---- actix-files/README.md | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index c829887ba..f7d32f8ec 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.4.0" +version = "0.4.1" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -21,14 +21,14 @@ actix-web = { version = "3.0.0", default-features = false } actix-service = "1.0.6" bitflags = "1" bytes = "0.5.3" -futures-core = { version = "0.3.5", default-features = false } -futures-util = { version = "0.3.5", default-features = false } +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false } derive_more = "0.99.2" log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" -v_htmlescape = "0.10" +v_htmlescape = "0.11" [dev-dependencies] actix-rt = "1.0.0" diff --git a/actix-files/README.md b/actix-files/README.md index d31439361..685e5dbe5 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -2,12 +2,12 @@ > Static file serving for Actix Web -[![crates.io](https://meritbadge.herokuapp.com/actix-web)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg)](https://docs.rs/actix-files) +[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.4.1)](https://docs.rs/actix-files/0.4.1) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.4.0/status.svg)](https://deps.rs/crate/actix-files/0.4.0) +[![dependency status](https://deps.rs/crate/actix-files/0.4.1/status.svg)](https://deps.rs/crate/actix-files/0.4.1) [![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) From efc317d3b0cfaa19a5c5adfe0c78cbb54f43a0a0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 25 Nov 2020 00:07:56 +0000 Subject: [PATCH 040/187] prepare actix-http and awc releases --- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 2 +- actix-http/README.md | 6 +++--- awc/CHANGES.md | 8 +++++++- awc/README.md | 6 +++--- 5 files changed, 19 insertions(+), 9 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index a25f44e36..c602ab2e1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.2.0 - 2020-11-25 ### Added * HttpResponse builders for 1xx status codes. [#1768] * `Accept::mime_precedence` and `Accept::mime_preference`. [#1793] @@ -10,8 +13,9 @@ * Started dropping `transfer-encoding: chunked` and `Content-Length` for 1XX and 204 responses. [#1767] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] +[#1773]: https://github.com/actix/actix-web/pull/1773 [#1767]: https://github.com/actix/actix-web/pull/1767 [#1768]: https://github.com/actix/actix-web/pull/1768 [#1793]: https://github.com/actix/actix-web/pull/1793 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 31495e395..7375c6eb3 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "2.1.0" +version = "2.2.0" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" diff --git a/actix-http/README.md b/actix-http/README.md index e536276ca..9103cd184 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,14 +3,14 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.1.0)](https://docs.rs/actix-http/2.1.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.0)](https://docs.rs/actix-http/2.2.0) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) -[![Dependency Status](https://deps.rs/crate/actix-http/2.1.0/status.svg)](https://deps.rs/crate/actix-http/2.1.0) +[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources -- [API Documentation](https://docs.rs/actix-http/2.1.0) +- [API Documentation](https://docs.rs/actix-http) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.42.0 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e184dfbd1..3745079cd 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.0.2 - 2020-11-25 ### Changed -* Upgrade `serde_urlencoded` to `0.7`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] + +[#1773]: https://github.com/actix/actix-web/pull/1773 + ## 2.0.1 - 2020-10-30 ### Changed diff --git a/awc/README.md b/awc/README.md index cbe299aaf..d14dd82dd 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,14 +3,14 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.1)](https://docs.rs/awc/2.0.1) +[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.2)](https://docs.rs/awc/2.0.2) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/2.0.1/status.svg)](https://deps.rs/crate/awc/2.0.1) +[![Dependency Status](https://deps.rs/crate/awc/2.0.2/status.svg)](https://deps.rs/crate/awc/2.0.2) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources -- [API Documentation](https://docs.rs/awc/2.0.1) +- [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Minimum Supported Rust Version (MSRV): 1.42.0 From e72b787ba701bc6db9a2ade9b498f801e6b1b90d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 25 Nov 2020 00:53:48 +0000 Subject: [PATCH 041/187] prepare actix-web and actix-http-test releases --- CHANGES.md | 6 ++++- Cargo.toml | 2 +- README.md | 4 +-- awc/Cargo.toml | 2 +- test-server/CHANGES.md | 55 ++++++++++++++++-------------------------- test-server/Cargo.toml | 4 +-- test-server/README.md | 20 +++++++++------ test-server/src/lib.rs | 5 ++++ 8 files changed, 50 insertions(+), 48 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index b928712d0..1364d6a9b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,16 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 3.3.0 - 2020-11-25 ### Added * Add `Either` extractor helper. [#1788] ### Changed -* Upgrade `serde_urlencoded` to `0.7`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] +[#1773]: https://github.com/actix/actix-web/pull/1773 [#1788]: https://github.com/actix/actix-web/pull/1788 diff --git a/Cargo.toml b/Cargo.toml index da511888a..3a3a92da9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.2.0" +version = "3.3.0" authors = ["Nikolay Kim "] description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index b11a8ee7c..c1d857f81 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=3.2.0)](https://docs.rs/actix-web/3.2.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.0)](https://docs.rs/actix-web/3.3.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.2.0/status.svg)](https://deps.rs/crate/actix-web/3.2.0) +[![Dependency Status](https://deps.rs/crate/actix-web/3.3.0/status.svg)](https://deps.rs/crate/actix-web/3.3.0)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d92996fb9..d5b632a51 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "2.0.1" +version = "2.0.2" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" diff --git a/test-server/CHANGES.md b/test-server/CHANGES.md index 845b6e2dc..835b75ddc 100644 --- a/test-server/CHANGES.md +++ b/test-server/CHANGES.md @@ -2,15 +2,20 @@ ## Unreleased - 2020-xx-xx -* add ability to set address for `TestServer` [#1645] -* Upgrade `base64` to `0.13`. -* Upgrade `serde_urlencoded` to `0.7`. +## 2.1.0 - 2020-11-25 +* Add ability to set address for `TestServer`. [#1645] +* Upgrade `base64` to `0.13`. +* Upgrade `serde_urlencoded` to `0.7`. [#1773] + +[#1773]: https://github.com/actix/actix-web/pull/1773 [#1645]: https://github.com/actix/actix-web/pull/1645 + ## 2.0.0 - 2020-09-11 * Update actix-codec and actix-utils dependencies. + ## 2.0.0-alpha.1 - 2020-05-23 * Update the `time` dependency to 0.2.7 * Update `actix-connect` dependency to 2.0.0-alpha.2 @@ -20,74 +25,56 @@ * Update `base64` dependency to 0.12 * Update `env_logger` dependency to 0.7 -## [1.0.0] - 2019-12-13 - -### Changed - +## 1.0.0 - 2019-12-13 * Replaced `TestServer::start()` with `test_server()` -## [1.0.0-alpha.3] - 2019-12-07 - -### Changed - +## 1.0.0-alpha.3 - 2019-12-07 * Migrate to `std::future` -## [0.2.5] - 2019-09-17 - -### Changed - +## 0.2.5 - 2019-09-17 * Update serde_urlencoded to "0.6.1" * Increase TestServerRuntime timeouts from 500ms to 3000ms - -### Fixed - * Do not override current `System` -## [0.2.4] - 2019-07-18 - +## 0.2.4 - 2019-07-18 * Update actix-server to 0.6 -## [0.2.3] - 2019-07-16 +## 0.2.3 - 2019-07-16 * Add `delete`, `options`, `patch` methods to `TestServerRunner` -## [0.2.2] - 2019-06-16 +## 0.2.2 - 2019-06-16 * Add .put() and .sput() methods -## [0.2.1] - 2019-06-05 +## 0.2.1 - 2019-06-05 * Add license files -## [0.2.0] - 2019-05-12 +## 0.2.0 - 2019-05-12 * Update awc and actix-http deps -## [0.1.1] - 2019-04-24 +## 0.1.1 - 2019-04-24 * Always make new connection for http client -## [0.1.0] - 2019-04-16 - +## 0.1.0 - 2019-04-16 * No changes -## [0.1.0-alpha.3] - 2019-04-02 - +## 0.1.0-alpha.3 - 2019-04-02 * Request functions accept path #743 -## [0.1.0-alpha.2] - 2019-03-29 - +## 0.1.0-alpha.2 - 2019-03-29 * Added TestServerRuntime::load_body() method - * Update actix-http and awc libraries -## [0.1.0-alpha.1] - 2019-03-28 - +## 0.1.0-alpha.1 - 2019-03-28 * Initial impl diff --git a/test-server/Cargo.toml b/test-server/Cargo.toml index 87db93469..8b23bef1c 100644 --- a/test-server/Cargo.toml +++ b/test-server/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-http-test" -version = "2.0.0" +version = "2.1.0" authors = ["Nikolay Kim "] -description = "Actix HTTP test server" +description = "Various helpers for Actix applications to use during testing" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" diff --git a/test-server/README.md b/test-server/README.md index db0791db7..c847c8515 100644 --- a/test-server/README.md +++ b/test-server/README.md @@ -1,9 +1,15 @@ -# Actix http test server [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-http-test)](https://crates.io/crates/actix-http-test) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# actix-http-test -## Documentation & community resources +> Various helpers for Actix applications to use during testing. -* [User Guide](https://actix.rs/docs/) -* [API Documentation](https://docs.rs/actix-http-test/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-http-test](https://crates.io/crates/actix-http-test) -* Minimum supported Rust version: 1.40 or later +[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=2.1.0)](https://docs.rs/actix-http-test/2.1.0) +![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test) +[![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0) +[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-http-test) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.42.0 diff --git a/test-server/src/lib.rs b/test-server/src/lib.rs index 4159c8d86..f881dfb4c 100644 --- a/test-server/src/lib.rs +++ b/test-server/src/lib.rs @@ -1,4 +1,9 @@ //! Various helpers for Actix applications to use during testing. + +#![deny(rust_2018_idioms)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] + use std::sync::mpsc; use std::{net, thread, time}; From fe6ad816cc730e545cf27e26d68ba26ddc20afe6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 25 Nov 2020 00:54:00 +0000 Subject: [PATCH 042/187] update dotgraphs --- docs/graphs/web-focus.dot | 42 ++++++++++++++++++++------------------- docs/graphs/web-only.dot | 2 ++ 2 files changed, 24 insertions(+), 20 deletions(-) diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 7abd51268..bcae36616 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -2,29 +2,31 @@ digraph { subgraph cluster_web { label="actix/actix-web" "awc" - "actix-web" - "actix-files" - "actix-http" - "actix-multipart" - "actix-web-actors" - "actix-web-codegen" + "web" + "files" + "http" + "multipart" + "web-actors" + "codegen" + "http-test" } - "actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "actix-testing" "actix-macros" "actix-threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" } - "awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" } - "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } - "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } - "actix-http" -> { "actix-service" "actix-codec" "actix-connect" "actix-utils" "actix-rt" "actix-threadpool" } - "actix-http" -> { "actix" "actix-tls" }[color=blue] // optional - "actix-files" -> { "actix-web" "actix-http" } + "web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" } + "awc" -> { "codec" "service" "http" "rt" } + "web-actors" -> { "actix" "web" "http" "codec" } + "multipart" -> { "web" "service" "utils" } + "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } + "http" -> { "actix" "tls" }[color=blue] // optional + "files" -> { "web" "http" } + "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } // net - "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } - "actix-tracing" -> { "actix-service" } - "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } - "actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" } - "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } - "actix-rt" -> { "actix-macros" "actix-threadpool" } - "actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } + "utils" -> { "service" "rt" "codec" } + "tracing" -> { "service" } + "tls" -> { "service" "codec" "utils" } + "testing" -> { "rt" "macros" "server" "service" } + "server" -> { "service" "rt" "codec" "utils" } + "rt" -> { "macros" "threadpool" } + "connect" -> { "service" "codec" "utils" "rt" } } diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 6e41fdc27..ee653e33b 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -8,6 +8,7 @@ digraph { "actix-multipart" "actix-web-actors" "actix-web-codegen" + "actix-http-test" } "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } @@ -16,4 +17,5 @@ digraph { "actix-multipart" -> { "actix-web" } "actix-http" -> { "actix" }[color=blue] // optional "actix-files" -> { "actix-web" "actix-http" } + "actix-http-test" -> { "awc" } } From 0b5b463cfa951d96ec6b0167964ef613b0d2b091 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 29 Nov 2020 16:33:45 +0000 Subject: [PATCH 043/187] prepare web and awc releases closes #1799 --- Cargo.toml | 10 +++++----- README.md | 6 +++--- awc/Cargo.toml | 4 ++-- awc/README.md | 4 ++-- docs/graphs/web-focus.dot | 2 +- docs/graphs/web-only.dot | 4 ++-- src/lib.rs | 2 +- 7 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3a3a92da9..bdd87184c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-web" -version = "3.3.0" +version = "3.3.1" authors = ["Nikolay Kim "] -description = "Actix web is a powerful, pragmatic, and extremely fast web framework for Rust" +description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" @@ -85,11 +85,11 @@ actix-threadpool = "0.3.1" actix-tls = "2.0.0" actix-web-codegen = "0.4.0" -actix-http = "2.1.0" -awc = { version = "2.0.0", default-features = false } +actix-http = "2.2.0" +awc = { version = "2.0.3", default-features = false } bytes = "0.5.3" -derive_more = "0.99.2" +derive_more = "0.99.5" encoding_rs = "0.8" futures-channel = { version = "0.3.5", default-features = false } futures-core = { version = "0.3.5", default-features = false } diff --git a/README.md b/README.md index c1d857f81..f67da03c1 100644 --- a/README.md +++ b/README.md @@ -1,15 +1,15 @@

Actix web

- Actix web is a powerful, pragmatic, and extremely fast web framework for Rust + Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust

[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) -[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.0)](https://docs.rs/actix-web/3.3.0) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.1)](https://docs.rs/actix-web/3.3.1) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.3.0/status.svg)](https://deps.rs/crate/actix-web/3.3.0) +[![Dependency Status](https://deps.rs/crate/actix-web/3.3.1/status.svg)](https://deps.rs/crate/actix-web/3.3.1)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/Cargo.toml b/awc/Cargo.toml index d5b632a51..3c1963d6b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "2.0.2" +version = "2.0.3" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" @@ -39,7 +39,7 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.3.0" actix-service = "1.0.6" -actix-http = "2.0.0" +actix-http = "2.2.0" actix-rt = "1.0.0" base64 = "0.13" diff --git a/awc/README.md b/awc/README.md index d14dd82dd..b97d4fa00 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=2.0.2)](https://docs.rs/awc/2.0.2) +[![Documentation](https://docs.rs/awc/badge.svg?version=2.0.3)](https://docs.rs/awc/2.0.3) ![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/2.0.2/status.svg)](https://deps.rs/crate/awc/2.0.2) +[![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3) [![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/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index bcae36616..17228fe62 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -17,7 +17,7 @@ digraph { "multipart" -> { "web" "service" "utils" } "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } "http" -> { "actix" "tls" }[color=blue] // optional - "files" -> { "web" "http" } + "files" -> { "web" } "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } // net diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index ee653e33b..9e1bb2805 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -11,11 +11,11 @@ digraph { "actix-http-test" } - "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } + "actix-web" -> { "actix-web-codegen" "actix-http" "awc" } "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } "actix-http" -> { "actix" }[color=blue] // optional - "actix-files" -> { "actix-web" "actix-http" } + "actix-files" -> { "actix-web" } "actix-http-test" -> { "awc" } } diff --git a/src/lib.rs b/src/lib.rs index 13e02c098..a8fc50d83 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,4 +1,4 @@ -//! Actix web is a powerful, pragmatic, and extremely fast web framework for Rust. +//! Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust. //! //! ## Example //! From ea8bf361041b35aa281835540f1e8aba9679aceb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 29 Nov 2020 16:35:35 +0000 Subject: [PATCH 044/187] update web and awc changelogs --- CHANGES.md | 2 ++ awc/CHANGES.md | 2 ++ 2 files changed, 4 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index 1364d6a9b..f30049c9d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 3.3.0 - 2020-11-25 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3745079cd..dfd8e3704 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 2.0.2 - 2020-11-25 From 32d59ca904bceaf7638a25c0ba367a9560dfc3b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Linus=20F=C3=A4rnstrand?= Date: Mon, 30 Nov 2020 20:18:02 +0100 Subject: [PATCH 045/187] Upgrade socket2 dependency (#1803) Upgrades to a version not making invalid assumptions about the memory layout of std::net::SocketAddr --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index bdd87184c..f8da9d90c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -97,7 +97,7 @@ futures-util = { version = "0.3.5", default-features = false } fxhash = "0.2.1" log = "0.4" mime = "0.3" -socket2 = "0.3" +socket2 = "0.3.16" pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } From 7981e0068a8d201e2f669772a0e81e5a73523688 Mon Sep 17 00:00:00 2001 From: Maciej Hirsz <1096222+maciejhirsz@users.noreply.github.com> Date: Tue, 1 Dec 2020 02:22:15 +0100 Subject: [PATCH 046/187] Remove a panic in normalize middleware (#1762) Co-authored-by: Yuki Okushi --- CHANGES.md | 1 + src/middleware/normalize.rs | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f30049c9d..1175aa54a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,6 +3,7 @@ ## Unreleased - 2020-xx-xx ### Fixed * Ensure `actix-http` dependency uses same `serde_urlencoded`. +* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. ## 3.3.0 - 2020-11-25 diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index e0ecd90dc..ac8ad71d5 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -137,9 +137,9 @@ where // so the change can not be deduced from the length comparison if path != original_path { let mut parts = head.uri.clone().into_parts(); - let pq = parts.path_and_query.as_ref().unwrap(); + let query = parts.path_and_query.as_ref().and_then(|pq| pq.query()); - let path = if let Some(q) = pq.query() { + let path = if let Some(q) = query { Bytes::from(format!("{}?{}", path, q)) } else { Bytes::copy_from_slice(path.as_bytes()) From 1f70ef155d76c4b96e8b7fdc01d338bf6b9b2185 Mon Sep 17 00:00:00 2001 From: Joshua Parkin Date: Tue, 1 Dec 2020 13:39:41 +0000 Subject: [PATCH 047/187] Fix match_pattern() returning None for scope with resource of empty path (#1798) * fix match_pattern function not returning pattern where scope has resource of path "" * remove print in test * make comparison on existing else if block * add fix to changelog --- CHANGES.md | 3 +++ src/request.rs | 36 ++++++++++++++++++++++++++++++++++++ src/rmap.rs | 2 +- 3 files changed, 40 insertions(+), 1 deletion(-) diff --git a/CHANGES.md b/CHANGES.md index 1175aa54a..edf4380fb 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,9 @@ ### Fixed * Ensure `actix-http` dependency uses same `serde_urlencoded`. * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. +* Fix match_pattern() returning None for scope with resource of empty path. [#1798] + +[#1798]: https://github.com/actix/actix-web/pull/1798 ## 3.3.0 - 2020-11-25 diff --git a/src/request.rs b/src/request.rs index a1b42f926..bd4bbbf58 100644 --- a/src/request.rs +++ b/src/request.rs @@ -675,4 +675,40 @@ mod tests { let res = call_service(&mut srv, req).await; assert_eq!(res.status(), StatusCode::OK); } + + #[actix_rt::test] + async fn extract_path_pattern_complex() { + let mut srv = init_service( + App::new() + .service(web::scope("/user").service(web::scope("/{id}").service( + web::resource("").to(move |req: HttpRequest| { + assert_eq!(req.match_pattern(), Some("/user/{id}".to_owned())); + + HttpResponse::Ok().finish() + }), + ))) + .service(web::resource("/").to(move |req: HttpRequest| { + assert_eq!(req.match_pattern(), Some("/".to_owned())); + + HttpResponse::Ok().finish() + })) + .default_service(web::to(move |req: HttpRequest| { + assert!(req.match_pattern().is_none()); + HttpResponse::Ok().finish() + })), + ) + .await; + + let req = TestRequest::get().uri("/user/test").to_request(); + let res = call_service(&mut srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let req = TestRequest::get().uri("/").to_request(); + let res = call_service(&mut srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + + let req = TestRequest::get().uri("/not-exist").to_request(); + let res = call_service(&mut srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + } } diff --git a/src/rmap.rs b/src/rmap.rs index 05c1f3f15..6827a11b2 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -86,7 +86,7 @@ impl ResourceMap { if let Some(plen) = pattern.is_prefix_match(path) { return rmap.has_resource(&path[plen..]); } - } else if pattern.is_match(path) { + } else if pattern.is_match(path) || pattern.pattern() == "" && path == "/" { return true; } } From 24d525d9784c61b1956f14841e9c78395037b0d6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 1 Dec 2020 22:22:46 +0000 Subject: [PATCH 048/187] prepare web 3.3.2 release --- CHANGES.md | 17 +++++++++++++---- Cargo.toml | 2 +- README.md | 4 ++-- awc/CHANGES.md | 3 +++ 4 files changed, 19 insertions(+), 7 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index edf4380fb..d9984224f 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,12 +1,21 @@ # Changes ## Unreleased - 2020-xx-xx -### Fixed -* Ensure `actix-http` dependency uses same `serde_urlencoded`. -* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. -* Fix match_pattern() returning None for scope with resource of empty path. [#1798] + +## 3.3.2 - 2020-12-01 +### Fixed +* Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] +* Fix `match_pattern()` returning `None` for scope with empty path resource. [#1798] +* Increase minimum `socket2` version. [#1803] + +[#1762]: https://github.com/actix/actix-web/pull/1762 [#1798]: https://github.com/actix/actix-web/pull/1798 +[#1803]: https://github.com/actix/actix-web/pull/1803 + + +## 3.3.1 - 2020-11-29 +* Ensure `actix-http` dependency uses same `serde_urlencoded`. ## 3.3.0 - 2020-11-25 diff --git a/Cargo.toml b/Cargo.toml index f8da9d90c..a7b571123 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.3.1" +version = "3.3.2" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" diff --git a/README.md b/README.md index f67da03c1..b9f2b7594 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=3.3.1)](https://docs.rs/actix-web/3.3.1) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.2)](https://docs.rs/actix-web/3.3.2) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.3.1/status.svg)](https://deps.rs/crate/actix-web/3.3.1) +[![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index dfd8e3704..7ca415336 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 2.0.3 - 2020-11-29 ### Fixed * Ensure `actix-http` dependency uses same `serde_urlencoded`. From d0c6ca7671d8e77a78432cf38cb4fb570f582f39 Mon Sep 17 00:00:00 2001 From: Arniu Tseng Date: Thu, 3 Dec 2020 01:23:30 +0800 Subject: [PATCH 049/187] test-server => actix-http-test (#1807) --- Cargo.toml | 6 +++--- {test-server => actix-http-test}/CHANGES.md | 0 {test-server => actix-http-test}/Cargo.toml | 0 {test-server => actix-http-test}/LICENSE-APACHE | 0 {test-server => actix-http-test}/LICENSE-MIT | 0 {test-server => actix-http-test}/README.md | 0 {test-server => actix-http-test}/src/lib.rs | 0 7 files changed, 3 insertions(+), 3 deletions(-) rename {test-server => actix-http-test}/CHANGES.md (100%) rename {test-server => actix-http-test}/Cargo.toml (100%) rename {test-server => actix-http-test}/LICENSE-APACHE (100%) rename {test-server => actix-http-test}/LICENSE-MIT (100%) rename {test-server => actix-http-test}/README.md (100%) rename {test-server => actix-http-test}/src/lib.rs (100%) diff --git a/Cargo.toml b/Cargo.toml index a7b571123..31c4cca7e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ members = [ "actix-multipart", "actix-web-actors", "actix-web-codegen", - "test-server", + "actix-http-test", ] [features] @@ -127,10 +127,10 @@ codegen-units = 1 [patch.crates-io] actix-web = { path = "." } actix-http = { path = "actix-http" } -actix-http-test = { path = "test-server" } +actix-http-test = { path = "actix-http-test" } actix-web-codegen = { path = "actix-web-codegen" } -actix-files = { path = "actix-files" } actix-multipart = { path = "actix-multipart" } +actix-files = { path = "actix-files" } awc = { path = "awc" } [[bench]] diff --git a/test-server/CHANGES.md b/actix-http-test/CHANGES.md similarity index 100% rename from test-server/CHANGES.md rename to actix-http-test/CHANGES.md diff --git a/test-server/Cargo.toml b/actix-http-test/Cargo.toml similarity index 100% rename from test-server/Cargo.toml rename to actix-http-test/Cargo.toml diff --git a/test-server/LICENSE-APACHE b/actix-http-test/LICENSE-APACHE similarity index 100% rename from test-server/LICENSE-APACHE rename to actix-http-test/LICENSE-APACHE diff --git a/test-server/LICENSE-MIT b/actix-http-test/LICENSE-MIT similarity index 100% rename from test-server/LICENSE-MIT rename to actix-http-test/LICENSE-MIT diff --git a/test-server/README.md b/actix-http-test/README.md similarity index 100% rename from test-server/README.md rename to actix-http-test/README.md diff --git a/test-server/src/lib.rs b/actix-http-test/src/lib.rs similarity index 100% rename from test-server/src/lib.rs rename to actix-http-test/src/lib.rs From b75a9b7a20b8cdc24842d38c7aa406e2b49f90c7 Mon Sep 17 00:00:00 2001 From: Petar Dambovaliev Date: Fri, 4 Dec 2020 20:57:56 +0100 Subject: [PATCH 050/187] add error to message in test helper func (#1812) --- CHANGES.md | 5 +++++ src/test.rs | 5 +++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index d9984224f..87c021b1e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2020-xx-xx +### Fixed +* added the actual parsing error to `test::read_body_json` [#1812] + +[#1812]: https://github.com/actix/actix-web/pull/1812 + ## 3.3.2 - 2020-12-01 diff --git a/src/test.rs b/src/test.rs index ee51b71ee..cff6c3e51 100644 --- a/src/test.rs +++ b/src/test.rs @@ -269,8 +269,9 @@ where { let body = read_body(res).await; - serde_json::from_slice(&body) - .unwrap_or_else(|_| panic!("read_response_json failed during deserialization")) + serde_json::from_slice(&body).unwrap_or_else(|e| { + panic!("read_response_json failed during deserialization: {}", e) + }) } pub async fn load_stream(mut stream: S) -> Result From ff79c33fd41ce9511e0bfb4835c688a2d5ac2a5d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 6 Dec 2020 19:42:15 +0800 Subject: [PATCH 051/187] remove a box (#1814) --- src/route.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/route.rs b/src/route.rs index e9e9d1f5d..8cc1edfc2 100644 --- a/src/route.rs +++ b/src/route.rs @@ -135,7 +135,7 @@ impl Service for RouteService { } fn call(&mut self, req: ServiceRequest) -> Self::Future { - self.service.call(req).boxed_local() + self.service.call(req) } } From 7a3776b770405c13eeb0dd5bf95f9c545910b53a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 9 Dec 2020 18:47:59 +0800 Subject: [PATCH 052/187] remove two unused generics on BoxedRouteFuture types. (#1820) --- src/route.rs | 42 +++++++++++++++++------------------------- 1 file changed, 17 insertions(+), 25 deletions(-) diff --git a/src/route.rs b/src/route.rs index 8cc1edfc2..45efd9e3c 100644 --- a/src/route.rs +++ b/src/route.rs @@ -16,24 +16,24 @@ use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; -type BoxedRouteService = Box< +type BoxedRouteService = Box< dyn Service< - Request = Req, - Response = Res, + Request = ServiceRequest, + Response = ServiceResponse, Error = Error, - Future = LocalBoxFuture<'static, Result>, + Future = LocalBoxFuture<'static, Result>, >, >; -type BoxedRouteNewService = Box< +type BoxedRouteNewService = Box< dyn ServiceFactory< Config = (), - Request = Req, - Response = Res, + Request = ServiceRequest, + Response = ServiceResponse, Error = Error, InitError = (), - Service = BoxedRouteService, - Future = LocalBoxFuture<'static, Result, ()>>, + Service = BoxedRouteService, + Future = LocalBoxFuture<'static, Result>, >, >; @@ -42,7 +42,7 @@ type BoxedRouteNewService = Box< /// Route uses builder-like pattern for configuration. /// If handler is not explicitly set, default *404 Not Found* handler is used. pub struct Route { - service: BoxedRouteNewService, + service: BoxedRouteNewService, guards: Rc>>, } @@ -80,15 +80,8 @@ impl ServiceFactory for Route { } } -type RouteFuture = LocalBoxFuture< - 'static, - Result, ()>, ->; - -#[pin_project::pin_project] pub struct CreateRouteService { - #[pin] - fut: RouteFuture, + fut: LocalBoxFuture<'static, Result>, guards: Rc>>, } @@ -96,9 +89,9 @@ impl Future for CreateRouteService { type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); + let this = self.get_mut(); - match this.fut.poll(cx)? { + match this.fut.as_mut().poll(cx)? { Poll::Ready(service) => Poll::Ready(Ok(RouteService { service, guards: this.guards.clone(), @@ -109,7 +102,7 @@ impl Future for CreateRouteService { } pub struct RouteService { - service: BoxedRouteService, + service: BoxedRouteService, guards: Rc>>, } @@ -275,12 +268,12 @@ where T::Service: 'static, ::Future: 'static, { - type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; + type Config = (); + type Service = BoxedRouteService; type InitError = (); - type Service = BoxedRouteService; type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { @@ -288,8 +281,7 @@ where .new_service(()) .map(|result| match result { Ok(service) => { - let service: BoxedRouteService<_, _> = - Box::new(RouteServiceWrapper { service }); + let service = Box::new(RouteServiceWrapper { service }) as _; Ok(service) } Err(_) => Err(()), From ae63eb8bb2576eeda9d261a358b954c3cd002fb3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Dec 2020 11:22:19 +0000 Subject: [PATCH 053/187] fix clippy warnings (#1806) * fix clippy warnings * prevent CI fail status caused by codecov --- actix-http/src/header/common/content_disposition.rs | 3 +-- actix-http/src/header/shared/entity.rs | 8 +++++--- awc/src/ws.rs | 11 ++++++++--- codecov.yml | 11 ++++++++++- src/middleware/compress.rs | 5 +---- src/middleware/condition.rs | 1 + src/middleware/errhandlers.rs | 1 + src/types/payload.rs | 7 ++++--- 8 files changed, 31 insertions(+), 16 deletions(-) diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 37da830ca..826cfef63 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -550,8 +550,7 @@ impl fmt::Display for ContentDisposition { write!(f, "{}", self.disposition)?; self.parameters .iter() - .map(|param| write!(f, "; {}", param)) - .collect() + .try_for_each(|param| write!(f, "; {}", param)) } } diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs index 3525a19c6..344cfb864 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/actix-http/src/header/shared/entity.rs @@ -7,10 +7,12 @@ use crate::header::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer}; /// 1. `%x21`, or /// 2. in the range `%x23` to `%x7E`, or /// 3. above `%x80` +fn entity_validate_char(c: u8) -> bool { + c == 0x21 || (0x23..=0x7e).contains(&c) || (c >= 0x80) +} + fn check_slice_validity(slice: &str) -> bool { - slice - .bytes() - .all(|c| c == b'\x21' || (c >= b'\x23' && c <= b'\x7e') | (c >= b'\x80')) + slice.bytes().all(entity_validate_char) } /// An entity tag, defined in [RFC7232](https://tools.ietf.org/html/rfc7232#section-2.3) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 57e80bd46..aa474697b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -70,9 +70,14 @@ impl WebsocketsRequest { >::Error: Into, { let mut err = None; - let mut head = RequestHead::default(); - head.method = Method::GET; - head.version = Version::HTTP_11; + + #[allow(clippy::field_reassign_with_default)] + let mut head = { + let mut head = RequestHead::default(); + head.method = Method::GET; + head.version = Version::HTTP_11; + head + }; match Uri::try_from(uri) { Ok(uri) => head.uri = uri, diff --git a/codecov.yml b/codecov.yml index 90cdfab47..102e8969d 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,4 +1,13 @@ -ignore: # ignore codecoverage on following paths +coverage: + status: + project: + default: + threshold: 10% # make CI green + patch: + default: + threshold: 10% # make CI green + +ignore: # ignore code coverage on following paths - "**/tests" - "test-server" - "**/benches" diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index fe3ba841c..7575d7455 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -192,10 +192,7 @@ impl AcceptEncoding { }; let quality = match parts.len() { 1 => encoding.quality(), - _ => match f64::from_str(parts[1]) { - Ok(q) => q, - Err(_) => 0.0, - }, + _ => f64::from_str(parts[1]).unwrap_or(0.0), }; Some(AcceptEncoding { encoding, quality }) } diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index ab1c69746..9061c7458 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -105,6 +105,7 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 93a5d3f22..c0cb9594e 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -154,6 +154,7 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; + #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/types/payload.rs b/src/types/payload.rs index 4ff5ef4b4..fdc1f8c8a 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -241,9 +241,10 @@ pub struct PayloadConfig { impl PayloadConfig { /// Create `PayloadConfig` instance and set max size of payload. pub fn new(limit: usize) -> Self { - let mut cfg = Self::default(); - cfg.limit = limit; - cfg + Self { + limit, + ..Default::default() + } } /// Change max size of payload. By default max size is 256Kb From 542db82282ab0455a28bd006d147c5510035f634 Mon Sep 17 00:00:00 2001 From: Juan Aguilar Date: Sat, 12 Dec 2020 21:07:06 +0100 Subject: [PATCH 054/187] Simplify wake up of task (#1826) --- actix-http/src/h1/payload.rs | 4 +--- actix-multipart/src/server.rs | 4 +--- 2 files changed, 2 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 6a348810c..d4cfee146 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -182,9 +182,7 @@ impl Inner { self.len += data.len(); self.items.push_back(data); self.need_read = self.len < MAX_BUFFER_SIZE; - if let Some(task) = self.task.take() { - task.wake() - } + self.task.wake(); } #[cfg(test)] diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index b9ebf97cc..b476f1791 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -725,9 +725,7 @@ impl Drop for Safety { if Rc::strong_count(&self.payload) != self.level { self.clean.set(true); } - if let Some(task) = self.task.take() { - task.wake() - } + self.task.wake(); } } From fabc68659bee4f9a04e8c0ac59a0e20c4e3804c1 Mon Sep 17 00:00:00 2001 From: Aravinth Manivannan Date: Sun, 13 Dec 2020 18:58:39 +0530 Subject: [PATCH 055/187] Intradoc links conversion (#1827) * switching to nightly for intra-doc links * actix-files intra-doc conversion * more specific Result * intradoc conversion complete * rm blank comments and readme doc link fixes * macros and broken links --- actix-http-test/src/lib.rs | 2 +- actix-http/src/error.rs | 2 +- actix-http/src/header/common/mod.rs | 2 +- actix-http/src/header/map.rs | 4 ---- actix-web-codegen/README.md | 2 +- actix-web-codegen/src/lib.rs | 24 +++++++++++------------- awc/src/ws.rs | 2 +- src/config.rs | 2 +- src/info.rs | 2 +- src/lib.rs | 8 ++++---- src/middleware/logger.rs | 3 +-- src/server.rs | 2 +- src/service.rs | 6 +++--- src/types/form.rs | 2 +- src/types/json.rs | 6 +++--- src/types/path.rs | 2 +- src/types/payload.rs | 4 ++-- src/types/query.rs | 2 +- 18 files changed, 35 insertions(+), 42 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index f881dfb4c..3ab3f8a0d 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -53,7 +53,7 @@ pub async fn test_server>(factory: F) -> TestServer test_server_with_addr(tcp, factory).await } -/// Start [`test server`](./fn.test_server.html) on a concrete Address +/// Start [`test server`](test_server()) on a concrete Address pub async fn test_server_with_addr>( tcp: net::TcpListener, factory: F, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index e93c077af..0ebd4c05c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -25,7 +25,7 @@ pub use crate::cookie::ParseError as CookieParseError; use crate::helpers::Writer; use crate::response::{Response, ResponseBuilder}; -/// A specialized [`Result`](https://doc.rust-lang.org/std/result/enum.Result.html) +/// A specialized [`std::result::Result`] /// for actix web operations /// /// This typedef is generally used to avoid writing out diff --git a/actix-http/src/header/common/mod.rs b/actix-http/src/header/common/mod.rs index 83489b864..c3d18613c 100644 --- a/actix-http/src/header/common/mod.rs +++ b/actix-http/src/header/common/mod.rs @@ -3,7 +3,7 @@ //! ## Mime //! //! Several header fields use MIME values for their contents. Keeping with the -//! strongly-typed theme, the [mime](https://docs.rs/mime) crate +//! strongly-typed theme, the [mime] crate //! is used, such as `ContentType(pub Mime)`. #![cfg_attr(rustfmt, rustfmt_skip)] diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 36c050b8f..6ab3509f7 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -8,8 +8,6 @@ use http::header::{HeaderName, HeaderValue}; /// A set of HTTP headers /// /// `HeaderMap` is an multi-map of [`HeaderName`] to values. -/// -/// [`HeaderName`]: struct.HeaderName.html #[derive(Debug, Clone)] pub struct HeaderMap { pub(crate) inner: FxHashMap, @@ -141,8 +139,6 @@ impl HeaderMap { /// The returned view does not incur any allocations and allows iterating /// the values associated with the key. See [`GetAll`] for more details. /// Returns `None` if there are no values associated with the key. - /// - /// [`GetAll`]: struct.GetAll.html pub fn get_all(&self, name: N) -> GetAll<'_> { GetAll { idx: 0, diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 6eca847b8..283591e86 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -3,7 +3,7 @@ > Helper and convenience macros for Actix Web [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index af2bc7f18..50e5be712 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -8,7 +8,7 @@ //! are re-exported. //! //! # Runtime Setup -//! Used for setting up the actix async runtime. See [main] macro docs. +//! Used for setting up the actix async runtime. See [macro@main] macro docs. //! //! ```rust //! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps @@ -34,7 +34,7 @@ //! //! # Multiple Method Handlers //! Similar to the single method handler macro but takes one or more arguments for the HTTP methods -//! it should respond to. See [route] macro docs. +//! it should respond to. See [macro@route] macro docs. //! //! ```rust //! # use actix_web::HttpResponse; @@ -46,17 +46,15 @@ //! ``` //! //! [actix-web attributes docs]: https://docs.rs/actix-web/*/actix_web/#attributes -//! [main]: attr.main.html -//! [route]: attr.route.html -//! [GET]: attr.get.html -//! [POST]: attr.post.html -//! [PUT]: attr.put.html -//! [DELETE]: attr.delete.html -//! [HEAD]: attr.head.html -//! [CONNECT]: attr.connect.html -//! [OPTIONS]: attr.options.html -//! [TRACE]: attr.trace.html -//! [PATCH]: attr.patch.html +//! [GET]: macro@get +//! [POST]: macro@post +//! [PUT]: macro@put +//! [HEAD]: macro@head +//! [CONNECT]: macro@macro@connect +//! [OPTIONS]: macro@options +//! [TRACE]: macro@trace +//! [PATCH]: macro@patch +//! [DELETE]: macro@delete #![recursion_limit = "512"] diff --git a/awc/src/ws.rs b/awc/src/ws.rs index aa474697b..dd43d08b3 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -1,6 +1,6 @@ //! Websockets client //! -//! Type definitions required to use [`awc::Client`](../struct.Client.html) as a WebSocket client. +//! Type definitions required to use [`awc::Client`](super::Client) as a WebSocket client. //! //! # Example //! diff --git a/src/config.rs b/src/config.rs index 03ba82732..01959daa1 100644 --- a/src/config.rs +++ b/src/config.rs @@ -141,7 +141,7 @@ impl AppConfig { /// Server host name. /// /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](./struct.ConnectionInfo.html#method.host) + /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) /// documentation for more information. /// /// By default host name is set to a "localhost" value. diff --git a/src/info.rs b/src/info.rs index 1d9b402a7..975604041 100644 --- a/src/info.rs +++ b/src/info.rs @@ -174,7 +174,7 @@ impl ConnectionInfo { /// Do not use this function for security purposes, unless you can ensure the Forwarded and /// X-Forwarded-For headers cannot be spoofed by the client. If you want the client's socket /// address explicitly, use - /// [`HttpRequest::peer_addr()`](../web/struct.HttpRequest.html#method.peer_addr) instead. + /// [`HttpRequest::peer_addr()`](super::web::HttpRequest::peer_addr()) instead. #[inline] pub fn realip_remote_addr(&self) -> Option<&str> { if let Some(ref r) = self.realip_remote_addr { diff --git a/src/lib.rs b/src/lib.rs index a8fc50d83..b8346d966 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -29,16 +29,16 @@ //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! -//! * [App](struct.App.html): This struct represents an Actix web application and is used to +//! * [App]: This struct represents an Actix web application and is used to //! configure routes and other common application settings. //! -//! * [HttpServer](struct.HttpServer.html): This struct represents an HTTP server instance and is +//! * [HttpServer]: This struct represents an HTTP server instance and is //! used to instantiate and configure servers. //! -//! * [web](web/index.html): This module provides essential types for route registration as well as +//! * [web]: This module provides essential types for route registration as well as //! common utilities for request handlers. //! -//! * [HttpRequest](struct.HttpRequest.html) and [HttpResponse](struct.HttpResponse.html): These +//! * [HttpRequest] and [HttpResponse]: These //! structs represent HTTP requests and responses and expose methods for creating, inspecting, //! and otherwise utilizing them. //! diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index b2e5c791f..563cb6c32 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -82,11 +82,10 @@ use crate::HttpResponse; /// /// # Security /// **\*** It is calculated using -/// [`ConnectionInfo::realip_remote_addr()`](../dev/struct.ConnectionInfo.html#method.realip_remote_addr) +/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) /// /// If you use this value ensure that all requests come from trusted hosts, since it is trivial /// for the remote client to simulate being another client. -/// pub struct Logger(Rc); struct Inner { diff --git a/src/server.rs b/src/server.rs index 3badb6e8d..be97e8a0d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -213,7 +213,7 @@ where /// Set server host name. /// /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](./dev/struct.ConnectionInfo.html#method.host) + /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) /// documentation for more information. /// /// By default host name is set to a "localhost" value. diff --git a/src/service.rs b/src/service.rs index a861ba38c..189ba5554 100644 --- a/src/service.rs +++ b/src/service.rs @@ -195,13 +195,13 @@ impl ServiceRequest { self.0.match_info() } - /// Counterpart to [`HttpRequest::match_name`](../struct.HttpRequest.html#method.match_name). + /// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()). #[inline] pub fn match_name(&self) -> Option<&str> { self.0.match_name() } - /// Counterpart to [`HttpRequest::match_pattern`](../struct.HttpRequest.html#method.match_pattern). + /// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()). #[inline] pub fn match_pattern(&self) -> Option { self.0.match_pattern() @@ -225,7 +225,7 @@ impl ServiceRequest { self.0.app_config() } - /// Counterpart to [`HttpRequest::app_data`](../struct.HttpRequest.html#method.app_data). + /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). pub fn app_data(&self) -> Option<&T> { for container in (self.0).0.app_data.iter().rev() { if let Some(data) = container.get::() { diff --git a/src/types/form.rs b/src/types/form.rs index 2a7101287..82ea73216 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -35,7 +35,7 @@ use crate::{responder::Responder, web}; /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**FormConfig**](struct.FormConfig.html) allows to configure extraction +/// [**FormConfig**](FormConfig) allows to configure extraction /// process. /// /// ### Example diff --git a/src/types/json.rs b/src/types/json.rs index 081a022e8..83c9f21b0 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -31,7 +31,7 @@ use crate::{responder::Responder, web}; /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// [**JsonConfig**](JsonConfig) allows to configure extraction /// process. /// /// ## Example @@ -142,7 +142,7 @@ impl Responder for Json { /// To extract typed information from request's body, the type `T` must /// implement the `Deserialize` trait from *serde*. /// -/// [**JsonConfig**](struct.JsonConfig.html) allows to configure extraction +/// [**JsonConfig**](JsonConfig) allows to configure extraction /// process. /// /// ## Example @@ -306,7 +306,7 @@ impl Default for JsonConfig { /// Returns error: /// /// * content type is not `application/json` -/// (unless specified in [`JsonConfig`](struct.JsonConfig.html)) +/// (unless specified in [`JsonConfig`]) /// * content length is greater than 256k pub struct JsonBody { limit: usize, diff --git a/src/types/path.rs b/src/types/path.rs index dbb5f3ee0..640ff4346 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -15,7 +15,7 @@ use crate::FromRequest; #[derive(PartialEq, Eq, PartialOrd, Ord)] /// Extract typed information from the request's path. /// -/// [**PathConfig**](struct.PathConfig.html) allows to configure extraction process. +/// [**PathConfig**](PathConfig) allows to configure extraction process. /// /// ## Example /// diff --git a/src/types/payload.rs b/src/types/payload.rs index fdc1f8c8a..acb8b9a82 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -111,7 +111,7 @@ impl FromRequest for Payload { /// /// Loads request's payload and construct Bytes instance. /// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// [**PayloadConfig**](PayloadConfig) allows to configure /// extraction process. /// /// ## Example @@ -159,7 +159,7 @@ impl FromRequest for Bytes { /// /// Text extractor automatically decode body according to the request's charset. /// -/// [**PayloadConfig**](struct.PayloadConfig.html) allows to configure +/// [**PayloadConfig**](PayloadConfig) allows to configure /// extraction process. /// /// ## Example diff --git a/src/types/query.rs b/src/types/query.rs index 7eded49c5..27df220fc 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -18,7 +18,7 @@ use crate::request::HttpRequest; /// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. /// Attempts to do so will *fail at runtime*. /// -/// [**QueryConfig**](struct.QueryConfig.html) allows to configure extraction process. +/// [**QueryConfig**](QueryConfig) allows to configure extraction process. /// /// ## Example /// From d7ce6484457be229c4c9b4fcd3581d599b7ff9f1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Dec 2020 02:34:10 +0800 Subject: [PATCH 056/187] remove boxed future for Option and Result extract type (#1829) * remove boxed future for Option and Result extract type * use ready macro * fix fmt --- src/extract.rs | 81 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 59 insertions(+), 22 deletions(-) diff --git a/src/extract.rs b/src/extract.rs index df9c34cb3..5916b1bc5 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -4,7 +4,8 @@ use std::pin::Pin; use std::task::{Context, Poll}; use actix_http::error::Error; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; +use futures_util::future::{ready, Ready}; +use futures_util::ready; use crate::dev::Payload; use crate::request::HttpRequest; @@ -95,21 +96,41 @@ where T: FromRequest, T::Future: 'static, { - type Config = T::Config; type Error = Error; - type Future = LocalBoxFuture<'static, Result, Error>>; + type Future = FromRequestOptFuture; + type Config = T::Config; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - T::from_request(req, payload) - .then(|r| match r { - Ok(v) => ok(Some(v)), - Err(e) => { - log::debug!("Error for Option extractor: {}", e.into()); - ok(None) - } - }) - .boxed_local() + FromRequestOptFuture { + fut: T::from_request(req, payload), + } + } +} + +#[pin_project::pin_project] +pub struct FromRequestOptFuture { + #[pin] + fut: Fut, +} + +impl Future for FromRequestOptFuture +where + Fut: Future>, + E: Into, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let res = ready!(this.fut.poll(cx)); + match res { + Ok(t) => Poll::Ready(Ok(Some(t))), + Err(e) => { + log::debug!("Error for Option extractor: {}", e.into()); + Poll::Ready(Ok(None)) + } + } } } @@ -165,29 +186,45 @@ where T::Error: 'static, T::Future: 'static, { - type Config = T::Config; type Error = Error; - type Future = LocalBoxFuture<'static, Result, Error>>; + type Future = FromRequestResFuture; + type Config = T::Config; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - T::from_request(req, payload) - .then(|res| match res { - Ok(v) => ok(Ok(v)), - Err(e) => ok(Err(e)), - }) - .boxed_local() + FromRequestResFuture { + fut: T::from_request(req, payload), + } + } +} + +#[pin_project::pin_project] +pub struct FromRequestResFuture { + #[pin] + fut: Fut, +} + +impl Future for FromRequestResFuture +where + Fut: Future>, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let res = ready!(this.fut.poll(cx)); + Poll::Ready(Ok(res)) } } #[doc(hidden)] impl FromRequest for () { - type Config = (); type Error = Error; type Future = Ready>; + type Config = (); fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future { - ok(()) + ready(Ok(())) } } From 1a361273e7bf15fb43b8f7a10334a0edae4fde0a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 16 Dec 2020 22:40:26 +0000 Subject: [PATCH 057/187] optimize bytes and string payload extractors (#1831) --- src/types/payload.rs | 72 ++++++++++++++++++++++++++------------------ 1 file changed, 43 insertions(+), 29 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index acb8b9a82..fd4d3e945 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -7,10 +7,15 @@ use std::task::{Context, Poll}; use actix_http::error::{Error, ErrorBadRequest, PayloadError}; use actix_http::HttpMessage; use bytes::{Bytes, BytesMut}; -use encoding_rs::UTF_8; +use encoding_rs::{Encoding, UTF_8}; use futures_core::stream::Stream; -use futures_util::future::{err, ok, Either, FutureExt, LocalBoxFuture, Ready}; -use futures_util::StreamExt; +use futures_util::{ + future::{ + err, ok, Either, ErrInto, FutureExt as _, LocalBoxFuture, Ready, + TryFutureExt as _, + }, + stream::StreamExt as _, +}; use mime::Mime; use crate::extract::FromRequest; @@ -135,10 +140,7 @@ impl FromRequest for Payload { impl FromRequest for Bytes { type Config = PayloadConfig; type Error = Error; - type Future = Either< - LocalBoxFuture<'static, Result>, - Ready>, - >; + type Future = Either, Ready>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -151,7 +153,7 @@ impl FromRequest for Bytes { let limit = cfg.limit; let fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left(async move { Ok(fut.await?) }.boxed_local()) + Either::Left(fut.err_into()) } } @@ -185,10 +187,7 @@ impl FromRequest for Bytes { impl FromRequest for String { type Config = PayloadConfig; type Error = Error; - type Future = Either< - LocalBoxFuture<'static, Result>, - Ready>, - >; + type Future = Either>>; #[inline] fn from_request(req: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { @@ -205,25 +204,40 @@ impl FromRequest for String { Err(e) => return Either::Right(err(e.into())), }; let limit = cfg.limit; - let fut = HttpMessageBody::new(req, payload).limit(limit); + let body_fut = HttpMessageBody::new(req, payload).limit(limit); - Either::Left( - async move { - let body = fut.await?; + Either::Left(StringExtractFut { body_fut, encoding }) + } +} - if encoding == UTF_8 { - Ok(str::from_utf8(body.as_ref()) - .map_err(|_| ErrorBadRequest("Can not decode body"))? - .to_owned()) - } else { - Ok(encoding - .decode_without_bom_handling_and_without_replacement(&body) - .map(|s| s.into_owned()) - .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) - } - } - .boxed_local(), - ) +pub struct StringExtractFut { + body_fut: HttpMessageBody, + encoding: &'static Encoding, +} + +impl<'a> Future for StringExtractFut { + type Output = Result; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let encoding = self.encoding; + + Pin::new(&mut self.body_fut).poll(cx).map(|out| { + let body = out?; + bytes_to_string(body, encoding) + }) + } +} + +fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result { + if encoding == UTF_8 { + Ok(str::from_utf8(body.as_ref()) + .map_err(|_| ErrorBadRequest("Can not decode body"))? + .to_owned()) + } else { + Ok(encoding + .decode_without_bom_handling_and_without_replacement(&body) + .map(|s| s.into_owned()) + .ok_or_else(|| ErrorBadRequest("Can not decode body"))?) } } From 97f615c245a9ad86a129a94e50a600f2006da306 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Dec 2020 07:34:33 +0800 Subject: [PATCH 058/187] remove boxed futures on Json extract type (#1832) --- src/types/json.rs | 211 ++++++++++++++++++++++++++++------------------ 1 file changed, 128 insertions(+), 83 deletions(-) diff --git a/src/types/json.rs b/src/types/json.rs index 83c9f21b0..95613a0ce 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -1,14 +1,16 @@ //! Json extractor/responder use std::future::Future; +use std::marker::PhantomData; use std::pin::Pin; use std::sync::Arc; use std::task::{Context, Poll}; use std::{fmt, ops}; use bytes::BytesMut; -use futures_util::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; -use futures_util::StreamExt; +use futures_util::future::{ready, Ready}; +use futures_util::ready; +use futures_util::stream::Stream; use serde::de::DeserializeOwned; use serde::Serialize; @@ -127,12 +129,12 @@ impl Responder for Json { fn respond_to(self, _: &HttpRequest) -> Self::Future { let body = match serde_json::to_string(&self.0) { Ok(body) => body, - Err(e) => return err(e.into()), + Err(e) => return ready(Err(e.into())), }; - ok(Response::build(StatusCode::OK) + ready(Ok(Response::build(StatusCode::OK) .content_type("application/json") - .body(body)) + .body(body))) } } @@ -173,37 +175,64 @@ where T: DeserializeOwned + 'static, { type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = JsonExtractFut; type Config = JsonConfig; #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { - let req2 = req.clone(); let config = JsonConfig::from_req(req); let limit = config.limit; let ctype = config.content_type.clone(); let err_handler = config.err_handler.clone(); - JsonBody::new(req, payload, ctype) - .limit(limit) - .map(move |res| match res { - Err(e) => { - log::debug!( - "Failed to deserialize Json from payload. \ - Request path: {}", - req2.path() - ); + JsonExtractFut { + req: Some(req.clone()), + fut: JsonBody::new(req, payload, ctype).limit(limit), + err_handler, + } + } +} - if let Some(err) = err_handler { - Err((*err)(e, &req2)) - } else { - Err(e.into()) - } +type JsonErrorHandler = + Option Error + Send + Sync>>; + +pub struct JsonExtractFut { + req: Option, + fut: JsonBody, + err_handler: JsonErrorHandler, +} + +impl Future for JsonExtractFut +where + T: DeserializeOwned + 'static, +{ + type Output = Result, Error>; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + let res = ready!(Pin::new(&mut this.fut).poll(cx)); + + let res = match res { + Err(e) => { + let req = this.req.take().unwrap(); + log::debug!( + "Failed to deserialize Json from payload. \ + Request path: {}", + req.path() + ); + + if let Some(err) = this.err_handler.as_ref() { + Err((*err)(e, &req)) + } else { + Err(e.into()) } - Ok(data) => Ok(Json(data)), - }) - .boxed_local() + } + Ok(data) => Ok(Json(data)), + }; + + Poll::Ready(res) } } @@ -248,8 +277,7 @@ where #[derive(Clone)] pub struct JsonConfig { limit: usize, - err_handler: - Option Error + Send + Sync>>, + err_handler: JsonErrorHandler, content_type: Option bool + Send + Sync>>, } @@ -308,17 +336,22 @@ impl Default for JsonConfig { /// * content type is not `application/json` /// (unless specified in [`JsonConfig`]) /// * content length is greater than 256k -pub struct JsonBody { - limit: usize, - length: Option, - #[cfg(feature = "compress")] - stream: Option>, - #[cfg(not(feature = "compress"))] - stream: Option, - err: Option, - fut: Option>>, +pub enum JsonBody { + Error(Option), + Body { + limit: usize, + length: Option, + #[cfg(feature = "compress")] + payload: Decompress, + #[cfg(not(feature = "compress"))] + payload: Payload, + buf: BytesMut, + _res: PhantomData, + }, } +impl Unpin for JsonBody {} + impl JsonBody where U: DeserializeOwned + 'static, @@ -340,39 +373,58 @@ where }; if !json { - return JsonBody { - limit: 262_144, - length: None, - stream: None, - fut: None, - err: Some(JsonPayloadError::ContentType), - }; + return JsonBody::Error(Some(JsonPayloadError::ContentType)); } - let len = req + let length = req .headers() .get(&CONTENT_LENGTH) .and_then(|l| l.to_str().ok()) .and_then(|s| s.parse::().ok()); + // Notice the content_length is not checked against limit of json config here. + // As the internal usage always call JsonBody::limit after JsonBody::new. + // And limit check to return an error variant of JsonBody happens there. + #[cfg(feature = "compress")] let payload = Decompress::from_headers(payload.take(), req.headers()); #[cfg(not(feature = "compress"))] let payload = payload.take(); - JsonBody { + JsonBody::Body { limit: 262_144, - length: len, - stream: Some(payload), - fut: None, - err: None, + length, + payload, + buf: BytesMut::with_capacity(8192), + _res: PhantomData, } } /// Change max size of payload. By default max size is 256Kb - pub fn limit(mut self, limit: usize) -> Self { - self.limit = limit; - self + pub fn limit(self, limit: usize) -> Self { + match self { + JsonBody::Body { + length, + payload, + buf, + .. + } => { + if let Some(len) = length { + if len > limit { + return JsonBody::Error(Some(JsonPayloadError::Overflow)); + } + } + + JsonBody::Body { + limit, + length, + payload, + buf, + _res: PhantomData, + } + } + JsonBody::Error(e) => JsonBody::Error(e), + } } } @@ -382,41 +434,34 @@ where { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); - } + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - let limit = self.limit; - if let Some(len) = self.length.take() { - if len > limit { - return Poll::Ready(Err(JsonPayloadError::Overflow)); - } - } - let mut stream = self.stream.take().unwrap(); - - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if (body.len() + chunk.len()) > limit { - return Err(JsonPayloadError::Overflow); - } else { - body.extend_from_slice(&chunk); + match this { + JsonBody::Body { + limit, + buf, + payload, + .. + } => loop { + let res = ready!(Pin::new(&mut *payload).poll_next(cx)); + match res { + Some(chunk) => { + let chunk = chunk?; + if (buf.len() + chunk.len()) > *limit { + return Poll::Ready(Err(JsonPayloadError::Overflow)); + } else { + buf.extend_from_slice(&chunk); + } + } + None => { + let json = serde_json::from_slice::(&buf)?; + return Poll::Ready(Ok(json)); } } - Ok(serde_json::from_slice::(&body)?) - } - .boxed_local(), - ); - - self.poll(cx) + }, + JsonBody::Error(e) => Poll::Ready(Err(e.take().unwrap())), + } } } From 2a5215c1d6cce12ff3f4bdc4e7ac73190d4aa9e0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 17 Dec 2020 19:40:49 +0800 Subject: [PATCH 059/187] Remove boxed future from HttpMessage (#1834) --- src/types/payload.rs | 109 ++++++++++++++++++------------------------- 1 file changed, 46 insertions(+), 63 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index fd4d3e945..9228b37aa 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -10,11 +10,8 @@ use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; use futures_core::stream::Stream; use futures_util::{ - future::{ - err, ok, Either, ErrInto, FutureExt as _, LocalBoxFuture, Ready, - TryFutureExt as _, - }, - stream::StreamExt as _, + future::{err, ok, Either, ErrInto, Ready, TryFutureExt as _}, + ready, }; use mime::Mime; @@ -305,10 +302,12 @@ impl PayloadConfig { // Allow shared refs to default. const DEFAULT_CONFIG: PayloadConfig = PayloadConfig { - limit: 262_144, // 2^18 bytes (~256kB) + limit: DEFAULT_CONFIG_LIMIT, mimetype: None, }; +const DEFAULT_CONFIG_LIMIT: usize = 262_144; // 2^18 bytes (~256kB) + impl Default for PayloadConfig { fn default() -> Self { DEFAULT_CONFIG.clone() @@ -326,99 +325,83 @@ pub struct HttpMessageBody { limit: usize, length: Option, #[cfg(feature = "compress")] - stream: Option>, + stream: dev::Decompress, #[cfg(not(feature = "compress"))] - stream: Option, + stream: dev::Payload, + buf: BytesMut, err: Option, - fut: Option>>, } impl HttpMessageBody { /// Create `MessageBody` for request. #[allow(clippy::borrow_interior_mutable_const)] pub fn new(req: &HttpRequest, payload: &mut dev::Payload) -> HttpMessageBody { - let mut len = None; + let mut length = None; + let mut err = None; + if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { - if let Ok(s) = l.to_str() { - if let Ok(l) = s.parse::() { - len = Some(l) - } else { - return Self::err(PayloadError::UnknownLength); - } - } else { - return Self::err(PayloadError::UnknownLength); + match l.to_str() { + Ok(s) => match s.parse::() { + Ok(l) if l > DEFAULT_CONFIG_LIMIT => { + err = Some(PayloadError::Overflow) + } + Ok(l) => length = Some(l), + Err(_) => err = Some(PayloadError::UnknownLength), + }, + Err(_) => err = Some(PayloadError::UnknownLength), } } #[cfg(feature = "compress")] - let stream = Some(dev::Decompress::from_headers(payload.take(), req.headers())); + let stream = dev::Decompress::from_headers(payload.take(), req.headers()); #[cfg(not(feature = "compress"))] - let stream = Some(payload.take()); + let stream = payload.take(); HttpMessageBody { stream, - limit: 262_144, - length: len, - fut: None, - err: None, + limit: DEFAULT_CONFIG_LIMIT, + length, + buf: BytesMut::with_capacity(8192), + err, } } /// Change max size of payload. By default max size is 256Kb pub fn limit(mut self, limit: usize) -> Self { + if let Some(l) = self.length { + if l > limit { + self.err = Some(PayloadError::Overflow); + } + } self.limit = limit; self } - - fn err(e: PayloadError) -> Self { - HttpMessageBody { - stream: None, - limit: 262_144, - fut: None, - err: Some(e), - length: None, - } - } } impl Future for HttpMessageBody { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - if let Some(ref mut fut) = self.fut { - return Pin::new(fut).poll(cx); + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + + if let Some(e) = this.err.take() { + return Poll::Ready(Err(e)); } - if let Some(err) = self.err.take() { - return Poll::Ready(Err(err)); - } - - if let Some(len) = self.length.take() { - if len > self.limit { - return Poll::Ready(Err(PayloadError::Overflow)); - } - } - - // future - let limit = self.limit; - let mut stream = self.stream.take().unwrap(); - self.fut = Some( - async move { - let mut body = BytesMut::with_capacity(8192); - - while let Some(item) = stream.next().await { - let chunk = item?; - if body.len() + chunk.len() > limit { - return Err(PayloadError::Overflow); + loop { + let res = ready!(Pin::new(&mut this.stream).poll_next(cx)); + match res { + Some(chunk) => { + let chunk = chunk?; + if this.buf.len() + chunk.len() > this.limit { + return Poll::Ready(Err(PayloadError::Overflow)); } else { - body.extend_from_slice(&chunk); + this.buf.extend_from_slice(&chunk); } } - Ok(body.freeze()) + None => return Poll::Ready(Ok(this.buf.split().freeze())), } - .boxed_local(), - ); - self.poll(cx) + } } } From c7b4c6edfa0a3e7e6c6618584ec189ed5b3d99bd Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Thu, 17 Dec 2020 21:38:52 +0900 Subject: [PATCH 060/187] Disable PR comment from codecov --- codecov.yml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/codecov.yml b/codecov.yml index 102e8969d..e6bc40203 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,3 +1,5 @@ +comment: false + coverage: status: project: From a4dbaa8ed11651b33125d39d8cee0f90a3bfed61 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 19 Dec 2020 07:08:59 +0800 Subject: [PATCH 061/187] remove boxed future in DefaultHeaders middleware (#1838) --- src/middleware/defaultheaders.rs | 72 +++++++++++++++++++++----------- 1 file changed, 48 insertions(+), 24 deletions(-) diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index 6d43aba95..a6f1a4336 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -1,10 +1,14 @@ //! Middleware for setting default response headers use std::convert::TryFrom; +use std::future::Future; +use std::marker::PhantomData; +use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; +use futures_util::future::{ready, Ready}; +use futures_util::ready; use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; use crate::http::{Error as HttpError, HeaderMap}; @@ -97,15 +101,15 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = DefaultHeadersMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(DefaultHeadersMiddleware { + ready(Ok(DefaultHeadersMiddleware { service, inner: self.inner.clone(), - }) + })) } } @@ -122,36 +126,56 @@ where type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = DefaultHeaderFuture; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); let fut = self.service.call(req); - async move { - let mut res = fut.await?; - - // set response headers - for (key, value) in inner.headers.iter() { - if !res.headers().contains_key(key) { - res.headers_mut().insert(key.clone(), value.clone()); - } - } - // default content-type - if inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { - res.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } - Ok(res) + DefaultHeaderFuture { + fut, + inner, + _body: PhantomData, } - .boxed_local() + } +} + +#[pin_project::pin_project] +pub struct DefaultHeaderFuture { + #[pin] + fut: S::Future, + inner: Rc, + _body: PhantomData, +} + +impl Future for DefaultHeaderFuture +where + S: Service, Error = Error>, +{ + type Output = ::Output; + + #[allow(clippy::borrow_interior_mutable_const)] + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.project(); + let mut res = ready!(this.fut.poll(cx))?; + // set response headers + for (key, value) in this.inner.headers.iter() { + if !res.headers().contains_key(key) { + res.headers_mut().insert(key.clone(), value.clone()); + } + } + // default content-type + if this.inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { + res.headers_mut().insert( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); + } + Poll::Ready(Ok(res)) } } From 79de04d8625deff301fe76fdf8649543f2976ae5 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 20 Dec 2020 00:33:34 +0800 Subject: [PATCH 062/187] optimise Extract service (#1841) --- src/handler.rs | 110 +++++++++++++++++++++---------------------------- 1 file changed, 47 insertions(+), 63 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 669512ab3..db6c5ce0a 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -90,26 +90,20 @@ where } fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - HandlerServiceResponse { - fut: self.hnd.call(param), - fut2: None, - req: Some(req), - } + let fut = self.hnd.call(param); + HandlerServiceResponse::Future(fut, Some(req)) } } #[doc(hidden)] -#[pin_project] -pub struct HandlerServiceResponse +#[pin_project(project = HandlerProj)] +pub enum HandlerServiceResponse where T: Future, R: Responder, { - #[pin] - fut: T, - #[pin] - fut2: Option, - req: Option, + Future(#[pin] T, Option), + Responder(#[pin] R::Future, Option), } impl Future for HandlerServiceResponse @@ -120,28 +114,26 @@ where type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(fut) = this.fut2.as_pin_mut() { - return match fut.poll(cx) { - Poll::Ready(Ok(res)) => { - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) + loop { + match self.as_mut().project() { + HandlerProj::Future(fut, req) => { + let res = ready!(fut.poll(cx)); + let fut = res.respond_to(req.as_ref().unwrap()); + let state = HandlerServiceResponse::Responder(fut, req.take()); + self.as_mut().set(state); } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(this.req.take().unwrap(), res))) + HandlerProj::Responder(fut, req) => { + let res = ready!(fut.poll(cx)); + let req = req.take().unwrap(); + return match res { + Ok(res) => Poll::Ready(Ok(ServiceResponse::new(req, res))), + Err(e) => { + let res: Response = e.into().into(); + Poll::Ready(Ok(ServiceResponse::new(req, res))) + } + }; } - }; - } - - match this.fut.poll(cx) { - Poll::Ready(res) => { - let fut = res.respond_to(this.req.as_ref().unwrap()); - self.as_mut().project().fut2.set(Some(fut)); - self.poll(cx) } - Poll::Pending => Poll::Pending, } } } @@ -169,12 +161,12 @@ where Error = Infallible, > + Clone, { - type Config = (); type Request = ServiceRequest; type Response = ServiceResponse; type Error = (Error, ServiceRequest); - type InitError = (); + type Config = (); type Service = ExtractService; + type InitError = (); type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { @@ -210,24 +202,14 @@ where fn call(&mut self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); - - ExtractResponse { - fut, - req, - fut_s: None, - service: self.service.clone(), - } + ExtractResponse::Future(fut, Some(req), self.service.clone()) } } -#[pin_project] -pub struct ExtractResponse { - req: HttpRequest, - service: S, - #[pin] - fut: T::Future, - #[pin] - fut_s: Option, +#[pin_project(project = ExtractProj)] +pub enum ExtractResponse { + Future(#[pin] T::Future, Option, S), + Response(#[pin] S::Future), } impl Future for ExtractResponse @@ -241,21 +223,23 @@ where type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(fut) = this.fut_s.as_pin_mut() { - return fut.poll(cx).map_err(|_| panic!()); - } - - match ready!(this.fut.poll(cx)) { - Err(e) => { - let req = ServiceRequest::new(this.req.clone()); - Poll::Ready(Err((e.into(), req))) - } - Ok(item) => { - let fut = Some(this.service.call((item, this.req.clone()))); - self.as_mut().project().fut_s.set(fut); - self.poll(cx) + loop { + match self.as_mut().project() { + ExtractProj::Future(fut, req, srv) => { + let res = ready!(fut.poll(cx)); + let req = req.take().unwrap(); + match res { + Err(e) => { + let req = ServiceRequest::new(req); + return Poll::Ready(Err((e.into(), req))); + } + Ok(item) => { + let fut = srv.call((item, req)); + self.as_mut().set(ExtractResponse::Response(fut)); + } + } + } + ExtractProj::Response(fut) => return fut.poll(cx).map_err(|_| panic!()), } } } From 6cbf27508af7e8c38d2fe174fdb26062b35340ed Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 20 Dec 2020 10:20:29 +0800 Subject: [PATCH 063/187] simplify ExtractService's return type (#1842) --- src/handler.rs | 8 ++++---- src/route.rs | 32 ++++++-------------------------- 2 files changed, 10 insertions(+), 30 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index db6c5ce0a..0dc06b3ce 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -163,7 +163,7 @@ where { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = (Error, ServiceRequest); + type Error = Error; type Config = (); type Service = ExtractService; type InitError = (); @@ -192,7 +192,7 @@ where { type Request = ServiceRequest; type Response = ServiceResponse; - type Error = (Error, ServiceRequest); + type Error = Error; type Future = ExtractResponse; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { @@ -220,7 +220,7 @@ where Error = Infallible, >, { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { @@ -231,7 +231,7 @@ where match res { Err(e) => { let req = ServiceRequest::new(req); - return Poll::Ready(Err((e.into(), req))); + return Poll::Ready(Ok(req.error_response(e.into()))); } Ok(item) => { let fut = srv.call((item, req)); diff --git a/src/route.rs b/src/route.rs index 45efd9e3c..f8ef458f9 100644 --- a/src/route.rs +++ b/src/route.rs @@ -234,7 +234,7 @@ impl Route { struct RouteNewService where - T: ServiceFactory, + T: ServiceFactory, { service: T, } @@ -245,7 +245,7 @@ where Config = (), Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest), + Error = Error, >, T::Future: 'static, T::Service: 'static, @@ -262,7 +262,7 @@ where Config = (), Request = ServiceRequest, Response = ServiceResponse, - Error = (Error, ServiceRequest), + Error = Error, >, T::Future: 'static, T::Service: 'static, @@ -297,11 +297,7 @@ struct RouteServiceWrapper { impl Service for RouteServiceWrapper where T::Future: 'static, - T: Service< - Request = ServiceRequest, - Response = ServiceResponse, - Error = (Error, ServiceRequest), - >, + T: Service, { type Request = ServiceRequest; type Response = ServiceResponse; @@ -309,27 +305,11 @@ where type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx).map_err(|(e, _)| e) + self.service.poll_ready(cx) } fn call(&mut self, req: ServiceRequest) -> Self::Future { - // let mut fut = self.service.call(req); - self.service - .call(req) - .map(|res| match res { - Ok(res) => Ok(res), - Err((err, req)) => Ok(req.error_response(err)), - }) - .boxed_local() - - // match fut.poll() { - // Poll::Ready(Ok(res)) => Either::Left(ok(res)), - // Poll::Ready(Err((e, req))) => Either::Left(ok(req.error_response(e))), - // Poll::Pending => Either::Right(Box::new(fut.then(|res| match res { - // Ok(res) => Ok(res), - // Err((err, req)) => Ok(req.error_response(err)), - // }))), - // } + Box::pin(self.service.call(req)) } } From 95ccf1c9bc09e24f11126c6e48927d8e5ea3cf9c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 22 Dec 2020 00:42:20 +0800 Subject: [PATCH 064/187] replace actix_utils::oneshot with futures_channle::oneshot (#1844) --- actix-http/src/client/pool.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 08abc6277..a8687dbeb 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -9,8 +9,9 @@ use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{delay_for, Delay}; use actix_service::Service; -use actix_utils::{oneshot, task::LocalWaker}; +use actix_utils::task::LocalWaker; use bytes::Bytes; +use futures_channel::oneshot; use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; use fxhash::FxHashMap; use h2::client::{Connection, SendRequest}; From 4dccd092f36bab2856a65bd7a0edf06727f2d841 Mon Sep 17 00:00:00 2001 From: Logan Magee Date: Tue, 22 Dec 2020 14:45:31 -0900 Subject: [PATCH 065/187] Bump rand from 0.7.x to 0.8.x (#1845) --- CHANGES.md | 3 +++ Cargo.toml | 2 +- actix-http/CHANGES.md | 3 ++- actix-http/Cargo.toml | 2 +- awc/CHANGES.md | 2 ++ awc/Cargo.toml | 2 +- awc/tests/test_client.rs | 2 ++ tests/test_server.rs | 5 +++++ 8 files changed, 17 insertions(+), 4 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 87c021b1e..ee9b9308d 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed +* Bumped `rand` to `0.8` + ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/Cargo.toml b/Cargo.toml index 31c4cca7e..6ed327f56 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -112,7 +112,7 @@ tinyvec = { version = "1", features = ["alloc"] } [dev-dependencies] actix = "0.10.0" actix-http = { version = "2.1.0", features = ["actors"] } -rand = "0.7" +rand = "0.8" env_logger = "0.8" serde_derive = "1.0" brotli2 = "0.3.2" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c602ab2e1..81577688d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx - +### Changed +* Bumped `rand` to `0.8` ## 2.2.0 - 2020-11-25 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7375c6eb3..7cf344487 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -72,7 +72,7 @@ log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project = "1.0.0" -rand = "0.7" +rand = "0.8" regex = "1.3" serde = "1.0" serde_json = "1.0" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 7ca415336..e4f801bbe 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,8 @@ # Changes ## Unreleased - 2020-xx-xx +### Changed +* Bumped `rand` to `0.8` ## 2.0.3 - 2020-11-29 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 3c1963d6b..2e92526d2 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -50,7 +50,7 @@ futures-core = { version = "0.3.5", default-features = false } log =" 0.4" mime = "0.3" percent-encoding = "2.1" -rand = "0.7" +rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a9552d0d5..0024c6652 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -480,6 +480,7 @@ async fn test_client_gzip_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(100_000) + .map(char::from) .collect::(); let srv = test::start(|| { @@ -529,6 +530,7 @@ async fn test_client_brotli_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&rand::distributions::Alphanumeric) .take(70_000) + .map(char::from) .collect::(); let srv = test::start(|| { diff --git a/tests/test_server.rs b/tests/test_server.rs index f8a9ab86d..c6c316f0d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -248,6 +248,7 @@ async fn test_body_gzip_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(70_000) + .map(char::from) .collect::(); let srv_data = data.clone(); @@ -529,6 +530,7 @@ async fn test_reading_gzip_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(60_000) + .map(char::from) .collect::(); let srv = test::start_with(test::config().h1(), || { @@ -614,6 +616,7 @@ async fn test_reading_deflate_encoding_large_random() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(160_000) + .map(char::from) .collect::(); let srv = test::start_with(test::config().h1(), || { @@ -672,6 +675,7 @@ async fn test_brotli_encoding_large() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(320_000) + .map(char::from) .collect::(); let srv = test::start_with(test::config().h1(), || { @@ -753,6 +757,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() { let data = rand::thread_rng() .sample_iter(&Alphanumeric) .take(160_000) + .map(char::from) .collect::(); // load ssl keys From 05f104c24012b0c518b6bace0bc551821c94724f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 23 Dec 2020 00:19:20 +0000 Subject: [PATCH 066/187] improve NormalizePath docs (#1839) --- src/middleware/normalize.rs | 82 +++++++++++++++++++++++++------------ 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ac8ad71d5..ad9f51079 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,10 +1,11 @@ -//! `Middleware` to normalize request's URI +//! For middleware documentation, see [`NormalizePath`]. + use std::task::{Context, Poll}; use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; use bytes::Bytes; -use futures_util::future::{ok, Ready}; +use futures_util::future::{ready, Ready}; use regex::Regex; use crate::service::{ServiceRequest, ServiceResponse}; @@ -17,10 +18,12 @@ pub enum TrailingSlash { /// Always add a trailing slash to the end of the path. /// This will require all routes to end in a trailing slash for them to be accessible. Always, + /// Only merge any present multiple trailing slashes. /// - /// Note: This option provides the best compatibility with the v2 version of this middlware. + /// Note: This option provides the best compatibility with the v2 version of this middleware. MergeOnly, + /// Trim trailing slashes from the end of the path. Trim, } @@ -32,28 +35,53 @@ impl Default for TrailingSlash { } #[derive(Default, Clone, Copy)] -/// `Middleware` to normalize request's URI in place +/// Middleware to normalize a request's path so that routes can be matched less strictly. /// -/// Performs following: -/// -/// - Merges multiple slashes into one. +/// # Normalization Steps +/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always +/// becomes `/path/one`.) /// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing -/// slashes as-is, depending on the supplied `TrailingSlash` variant. +/// slashes as-is, depending on which [`TrailingSlash`] variant is supplied +/// to [`new`](NormalizePath::new()). /// +/// # Default Behavior +/// The default constructor chooses to strip trailing slashes from the end +/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without +/// trailing slashes or else they will be inaccessible. +/// +/// # Example /// ```rust -/// use actix_web::{web, http, middleware, App, HttpResponse}; +/// use actix_web::{web, middleware, App}; /// -/// # fn main() { +/// # #[actix_rt::test] +/// # async fn normalize() { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed())) -/// ); +/// .route("/test", web::get().to(|| async { "test" })) +/// .route("/unmatchable/", web::get().to(|| async { "unmatchable" })); +/// +/// use actix_web::http::StatusCode; +/// use actix_web::test::{call_service, init_service, TestRequest}; +/// +/// let mut app = init_service(app).await; +/// +/// let req = TestRequest::with_uri("/test").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::OK); +/// +/// let req = TestRequest::with_uri("/test/").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::OK); +/// +/// let req = TestRequest::with_uri("/unmatchable").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::NOT_FOUND); +/// +/// let req = TestRequest::with_uri("/unmatchable/").to_request(); +/// let res = call_service(&mut app, req).await; +/// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// # } /// ``` - pub struct NormalizePath(TrailingSlash); impl NormalizePath { @@ -76,11 +104,11 @@ where type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { - ok(NormalizePathNormalization { + ready(Ok(NormalizePathNormalization { service, merge_slash: Regex::new("//+").unwrap(), trailing_slash_behavior: self.0, - }) + })) } } @@ -160,9 +188,11 @@ mod tests { use actix_service::IntoService; use super::*; - use crate::dev::ServiceRequest; - use crate::test::{call_service, init_service, TestRequest}; - use crate::{web, App, HttpResponse}; + use crate::{ + dev::ServiceRequest, + test::{call_service, init_service, TestRequest}, + web, App, HttpResponse, + }; #[actix_rt::test] async fn test_wrap() { @@ -244,7 +274,7 @@ mod tests { } #[actix_rt::test] - async fn keep_trailing_slash_unchange() { + async fn keep_trailing_slash_unchanged() { let mut app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::MergeOnly)) @@ -279,7 +309,7 @@ mod tests { async fn test_in_place_normalization() { let srv = |req: ServiceRequest| { assert_eq!("/v1/something/", req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) + ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; let mut normalize = NormalizePath::default() @@ -310,7 +340,7 @@ mod tests { let srv = |req: ServiceRequest| { assert_eq!(URI, req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) + ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; let mut normalize = NormalizePath::default() @@ -324,12 +354,12 @@ mod tests { } #[actix_rt::test] - async fn should_normalize_notrail() { + async fn should_normalize_no_trail() { const URI: &str = "/v1/something"; let srv = |req: ServiceRequest| { assert_eq!(URI.to_string() + "/", req.path()); - ok(req.into_response(HttpResponse::Ok().finish())) + ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; let mut normalize = NormalizePath::default() From 2a7f2c1d59df14f3ea503f53dc5441099032a8cf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 23 Dec 2020 01:28:17 +0000 Subject: [PATCH 067/187] dispatcher internals testing (#1840) --- actix-http/src/cloneable.rs | 2 +- actix-http/src/extensions.rs | 2 +- actix-http/src/h1/codec.rs | 9 +- actix-http/src/h1/dispatcher.rs | 386 ++++++++++++++++++++++++++++++-- actix-http/src/h1/expect.rs | 10 +- actix-http/src/h1/upgrade.rs | 6 +- actix-http/src/test.rs | 148 ++++++++++-- actix-http/src/ws/mod.rs | 14 +- 8 files changed, 519 insertions(+), 58 deletions(-) diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs index b64c299fc..0e77c455c 100644 --- a/actix-http/src/cloneable.rs +++ b/actix-http/src/cloneable.rs @@ -4,12 +4,12 @@ use std::task::{Context, Poll}; use actix_service::Service; -#[doc(hidden)] /// Service that allows to turn non-clone service to a service with `Clone` impl /// /// # Panics /// CloneableService might panic with some creative use of thread local storage. /// See https://github.com/actix/actix-web/issues/1295 for example +#[doc(hidden)] pub(crate) struct CloneableService(Rc>); impl CloneableService { diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index 7dda74731..b20dfe11d 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -3,8 +3,8 @@ use std::{fmt, mem}; use fxhash::FxHashMap; -#[derive(Default)] /// A type map of request extensions. +#[derive(Default)] pub struct Extensions { /// Use FxHasher with a std HashMap with for faster /// lookups on the small `TypeId` (u64 equivalent) keys. diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 036f16670..c9a62dc30 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -58,6 +58,7 @@ impl Codec { } else { Flags::empty() }; + Codec { config, flags, @@ -69,26 +70,26 @@ impl Codec { } } + /// Check if request is upgrade. #[inline] - /// Check if request is upgrade pub fn upgrade(&self) -> bool { self.ctype == ConnectionType::Upgrade } + /// Check if last response is keep-alive. #[inline] - /// Check if last response is keep-alive pub fn keepalive(&self) -> bool { self.ctype == ConnectionType::KeepAlive } + /// Check if keep-alive enabled on server level. #[inline] - /// Check if keep-alive enabled on server level pub fn keepalive_enabled(&self) -> bool { self.flags.contains(Flags::KEEPALIVE_ENABLED) } + /// Check last request's message type. #[inline] - /// Check last request's message type pub fn message_type(&self) -> MessageType { if self.flags.contains(Flags::STREAM) { MessageType::Stream diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index ace4144e3..1311a0987 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,8 +1,11 @@ -use std::collections::VecDeque; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, io, net}; +use std::{ + collections::VecDeque, + fmt, + future::Future, + io, mem, net, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; use actix_rt::time::{delay_until, Delay, Instant}; @@ -59,6 +62,9 @@ where { #[pin] inner: DispatcherState, + + #[cfg(test)] + poll_count: u64, } #[pin_project(project = DispatcherStateProj)] @@ -247,6 +253,9 @@ where ka_expire, ka_timer, }), + + #[cfg(test)] + poll_count: 0, } } } @@ -511,12 +520,12 @@ where } } - /// Process one incoming requests + /// Process one incoming request. pub(self) fn poll_request( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - // limit a mount of non processed requests + // limit amount of non-processed requests if self.messages.len() >= MAX_PIPELINED_MESSAGES || !self.can_read(cx) { return Ok(false); } @@ -725,6 +734,12 @@ where #[inline] fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.as_mut().project(); + + #[cfg(test)] + { + *this.poll_count += 1; + } + match this.inner.project() { DispatcherStateProj::Normal(mut inner) => { inner.as_mut().poll_keepalive(cx)?; @@ -788,10 +803,10 @@ where let inner_p = inner.as_mut().project(); let mut parts = FramedParts::with_read_buf( inner_p.io.take().unwrap(), - std::mem::take(inner_p.codec), - std::mem::take(inner_p.read_buf), + mem::take(inner_p.codec), + mem::take(inner_p.read_buf), ); - parts.write_buf = std::mem::take(inner_p.write_buf); + parts.write_buf = mem::take(inner_p.write_buf); let framed = Framed::from_parts(parts); let upgrade = inner_p.upgrade.take().unwrap().call((req, framed)); @@ -803,8 +818,11 @@ where } // we didn't get WouldBlock from write operation, - // so data get written to kernel completely (OSX) + // so data get written to kernel completely (macOS) // and we have to write again otherwise response can get stuck + // + // TODO: what? is WouldBlock good or bad? + // want to find a reference for this macOS behavior if inner.as_mut().poll_flush(cx)? || !drain { break; } @@ -854,6 +872,11 @@ where } } +/// Returns either: +/// - `Ok(Some(true))` - data was read and done reading all data. +/// - `Ok(Some(false))` - data was read but there should be more to read. +/// - `Ok(None)` - no data was read but there should be more to read later. +/// - Unhandled Errors fn read_available( cx: &mut Context<'_>, io: &mut T, @@ -887,17 +910,17 @@ where read_some = true; } } - Poll::Ready(Err(e)) => { - return if e.kind() == io::ErrorKind::WouldBlock { + Poll::Ready(Err(err)) => { + return if err.kind() == io::ErrorKind::WouldBlock { if read_some { Ok(Some(false)) } else { Ok(None) } - } else if e.kind() == io::ErrorKind::ConnectionReset && read_some { + } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { Ok(Some(true)) } else { - Err(e) + Err(err) } } } @@ -917,13 +940,64 @@ where #[cfg(test)] mod tests { - use actix_service::IntoService; - use futures_util::future::{lazy, ok}; + use std::{marker::PhantomData, str}; + + use actix_service::fn_service; + use futures_util::future::{lazy, ready}; use super::*; - use crate::error::Error; - use crate::h1::{ExpectHandler, UpgradeHandler}; use crate::test::TestBuffer; + use crate::{error::Error, KeepAlive}; + use crate::{ + h1::{ExpectHandler, UpgradeHandler}, + test::TestSeqBuffer, + }; + + fn find_slice(haystack: &[u8], needle: &[u8], from: usize) -> Option { + haystack[from..] + .windows(needle.len()) + .position(|window| window == needle) + } + + fn stabilize_date_header(payload: &mut [u8]) { + let mut from = 0; + + while let Some(pos) = find_slice(&payload, b"date", from) { + payload[(from + pos)..(from + pos + 35)] + .copy_from_slice(b"date: Thu, 01 Jan 1970 12:34:56 UTC"); + from += 35; + } + } + + fn ok_service() -> impl Service + { + fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) + } + + fn echo_path_service( + ) -> impl Service { + fn_service(|req: Request| { + let path = req.path().as_bytes(); + ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) + }) + } + + fn echo_payload_service( + ) -> impl Service { + fn_service(|mut req: Request| { + Box::pin(async move { + use futures_util::stream::StreamExt as _; + + let mut pl = req.take_payload(); + let mut body = BytesMut::new(); + while let Some(chunk) = pl.next().await { + body.extend_from_slice(chunk.unwrap().bytes()) + } + + Ok::<_, Error>(Response::Ok().body(body)) + }) + }) + } #[actix_rt::test] async fn test_req_parse_err() { @@ -933,9 +1007,7 @@ mod tests { let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), - CloneableService::new( - (|_| ok::<_, Error>(Response::Ok().finish())).into_service(), - ), + CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), None, None, @@ -958,4 +1030,274 @@ mod tests { }) .await; } + + #[actix_rt::test] + async fn test_pipelining() { + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1.1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf, + cfg, + CloneableService::new(echo_path_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + match Pin::new(&mut h1).poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_ok()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherState::Normal(ref mut inner) = h1.inner { + let res = &mut inner.io.take().unwrap().write_buf[..]; + stabilize_date_header(res); + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 200 OK\r\n\ + content-length: 4\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /def\ + "; + + assert_eq!(res.to_vec(), exp.to_vec()); + } + }) + .await; + + lazy(|cx| { + let buf = TestBuffer::new( + "\ + GET /abcd HTTP/1.1\r\n\r\n\ + GET /def HTTP/1\r\n\r\n\ + ", + ); + + let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + buf, + cfg, + CloneableService::new(echo_path_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + match Pin::new(&mut h1).poll(cx) { + Poll::Pending => panic!("first poll should not be pending"), + Poll::Ready(res) => assert!(res.is_err()), + } + + // polls: initial => shutdown + assert_eq!(h1.poll_count, 1); + + if let DispatcherState::Normal(ref mut inner) = h1.inner { + let res = &mut inner.io.take().unwrap().write_buf[..]; + stabilize_date_header(res); + + let exp = b"\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + /abcd\ + HTTP/1.1 400 Bad Request\r\n\ + content-length: 0\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\ + "; + + assert_eq!(res.to_vec(), exp.to_vec()); + } + }) + .await; + } + + #[actix_rt::test] + async fn test_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + buf.clone(), + cfg, + CloneableService::new(echo_payload_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + assert!(Pin::new(&mut h1).poll(cx).is_pending()); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + // polls: manual + assert_eq!(h1.poll_count, 1); + eprintln!("poll count: {}", h1.poll_count); + + if let DispatcherState::Normal(ref inner) = h1.inner { + let io = inner.io.as_ref().unwrap(); + let res = &io.write_buf()[..]; + assert_eq!( + str::from_utf8(res).unwrap(), + "HTTP/1.1 100 Continue\r\n\r\n" + ); + } + + buf.extend_read_buf("12345"); + assert!(Pin::new(&mut h1).poll(cx).is_ready()); + + // polls: manual manual shutdown + assert_eq!(h1.poll_count, 3); + + if let DispatcherState::Normal(ref inner) = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 5\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + 12345\ + " + ); + } + }) + .await; + } + + #[actix_rt::test] + async fn test_eager_expect() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + buf.clone(), + cfg, + CloneableService::new(echo_path_service()), + CloneableService::new(ExpectHandler), + None, + None, + Extensions::new(), + None, + ); + + buf.extend_read_buf( + "\ + POST /upload HTTP/1.1\r\n\ + Content-Length: 5\r\n\ + Expect: 100-continue\r\n\ + \r\n\ + ", + ); + + assert!(Pin::new(&mut h1).poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + + if let DispatcherState::Normal(ref inner) = h1.inner { + let io = inner.io.as_ref().unwrap(); + let mut res = (&io.write_buf()[..]).to_owned(); + stabilize_date_header(&mut res); + + // Despite the content-length header and even though the request payload has not + // been sent, this test expects a complete service response since the payload + // is not used at all. The service passed to dispatcher is path echo and doesn't + // consume payload bytes. + assert_eq!( + str::from_utf8(&res).unwrap(), + "\ + HTTP/1.1 100 Continue\r\n\ + \r\n\ + HTTP/1.1 200 OK\r\n\ + content-length: 7\r\n\ + connection: close\r\n\ + date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\ + \r\n\ + /upload\ + " + ); + } + }) + .await; + } + + #[actix_rt::test] + async fn test_upgrade() { + lazy(|cx| { + let mut buf = TestSeqBuffer::empty(); + let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + buf.clone(), + cfg, + CloneableService::new(ok_service()), + CloneableService::new(ExpectHandler), + Some(CloneableService::new(UpgradeHandler(PhantomData))), + None, + Extensions::new(), + None, + ); + + buf.extend_read_buf( + "\ + GET /ws HTTP/1.1\r\n\ + Connection: Upgrade\r\n\ + Upgrade: websocket\r\n\ + \r\n\ + ", + ); + + assert!(Pin::new(&mut h1).poll(cx).is_ready()); + assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); + + // polls: manual shutdown + assert_eq!(h1.poll_count, 2); + }) + .await; + } } diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index 6c08df08e..b89c7ff74 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,7 +1,7 @@ use std::task::{Context, Poll}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ok, Ready}; +use futures_util::future::{ready, Ready}; use crate::error::Error; use crate::request::Request; @@ -17,8 +17,8 @@ impl ServiceFactory for ExpectHandler { type InitError = Error; type Future = Ready>; - fn new_service(&self, _: ()) -> Self::Future { - ok(ExpectHandler) + fn new_service(&self, _: Self::Config) -> Self::Future { + ready(Ok(ExpectHandler)) } } @@ -33,6 +33,8 @@ impl Service for ExpectHandler { } fn call(&mut self, req: Request) -> Self::Future { - ok(req) + ready(Ok(req)) + // TODO: add some way to trigger error + // Err(error::ErrorExpectationFailed("test")) } } diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 22ba99e26..8615f27a8 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -3,13 +3,13 @@ use std::task::{Context, Poll}; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; -use futures_util::future::Ready; +use futures_util::future::{ready, Ready}; use crate::error::Error; use crate::h1::Codec; use crate::request::Request; -pub struct UpgradeHandler(PhantomData); +pub struct UpgradeHandler(pub(crate) PhantomData); impl ServiceFactory for UpgradeHandler { type Config = (); @@ -36,6 +36,6 @@ impl Service for UpgradeHandler { } fn call(&mut self, _: Self::Request) -> Self::Future { - unimplemented!() + ready(Ok(())) } } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index b79f5a73c..4512e72c2 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -1,9 +1,14 @@ -//! Test Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; -use std::io::{self, Read, Write}; -use std::pin::Pin; -use std::str::FromStr; -use std::task::{Context, Poll}; +//! Various testing helpers for use in internal and app tests. + +use std::{ + cell::{Ref, RefCell}, + convert::TryFrom, + io::{self, Read, Write}, + pin::Pin, + rc::Rc, + str::FromStr, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::{Bytes, BytesMut}; @@ -183,7 +188,7 @@ fn parts(parts: &mut Option) -> &mut Inner { parts.as_mut().expect("cannot reuse test request builder") } -/// Async io buffer +/// Async I/O test buffer. pub struct TestBuffer { pub read_buf: BytesMut, pub write_buf: BytesMut, @@ -191,24 +196,24 @@ pub struct TestBuffer { } impl TestBuffer { - /// Create new TestBuffer instance - pub fn new(data: T) -> TestBuffer + /// Create new `TestBuffer` instance with initial read buffer. + pub fn new(data: T) -> Self where - BytesMut: From, + T: Into, { - TestBuffer { - read_buf: BytesMut::from(data), + Self { + read_buf: data.into(), write_buf: BytesMut::new(), err: None, } } - /// Create new empty TestBuffer instance - pub fn empty() -> TestBuffer { - TestBuffer::new("") + /// Create new empty `TestBuffer` instance. + pub fn empty() -> Self { + Self::new("") } - /// Add extra data to read buffer. + /// Add data to read buffer. pub fn extend_read_buf>(&mut self, data: T) { self.read_buf.extend_from_slice(data.as_ref()) } @@ -236,6 +241,7 @@ impl io::Write for TestBuffer { self.write_buf.extend(buf); Ok(buf.len()) } + fn flush(&mut self) -> io::Result<()> { Ok(()) } @@ -268,3 +274,113 @@ impl AsyncWrite for TestBuffer { Poll::Ready(Ok(())) } } + +/// Async I/O test buffer with ability to incrementally add to the read buffer. +#[derive(Clone)] +pub struct TestSeqBuffer(Rc>); + +impl TestSeqBuffer { + /// Create new `TestBuffer` instance with initial read buffer. + pub fn new(data: T) -> Self + where + T: Into, + { + Self(Rc::new(RefCell::new(TestSeqInner { + read_buf: data.into(), + write_buf: BytesMut::new(), + err: None, + }))) + } + + /// Create new empty `TestBuffer` instance. + pub fn empty() -> Self { + Self::new("") + } + + pub fn read_buf(&self) -> Ref<'_, BytesMut> { + Ref::map(self.0.borrow(), |inner| &inner.read_buf) + } + + pub fn write_buf(&self) -> Ref<'_, BytesMut> { + Ref::map(self.0.borrow(), |inner| &inner.write_buf) + } + + pub fn err(&self) -> Ref<'_, Option> { + Ref::map(self.0.borrow(), |inner| &inner.err) + } + + /// Add data to read buffer. + pub fn extend_read_buf>(&mut self, data: T) { + self.0 + .borrow_mut() + .read_buf + .extend_from_slice(data.as_ref()) + } +} + +pub struct TestSeqInner { + read_buf: BytesMut, + write_buf: BytesMut, + err: Option, +} + +impl io::Read for TestSeqBuffer { + fn read(&mut self, dst: &mut [u8]) -> Result { + if self.0.borrow().read_buf.is_empty() { + if self.0.borrow().err.is_some() { + Err(self.0.borrow_mut().err.take().unwrap()) + } else { + Err(io::Error::new(io::ErrorKind::WouldBlock, "")) + } + } else { + let size = std::cmp::min(self.0.borrow().read_buf.len(), dst.len()); + let b = self.0.borrow_mut().read_buf.split_to(size); + dst[..size].copy_from_slice(&b); + Ok(size) + } + } +} + +impl io::Write for TestSeqBuffer { + fn write(&mut self, buf: &[u8]) -> io::Result { + self.0.borrow_mut().write_buf.extend(buf); + Ok(buf.len()) + } + + fn flush(&mut self) -> io::Result<()> { + Ok(()) + } +} + +impl AsyncRead for TestSeqBuffer { + fn poll_read( + self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &mut [u8], + ) -> Poll> { + let r = self.get_mut().read(buf); + match r { + Ok(n) => Poll::Ready(Ok(n)), + Err(err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending, + Err(err) => Poll::Ready(Err(err)), + } + } +} + +impl AsyncWrite for TestSeqBuffer { + fn poll_write( + self: Pin<&mut Self>, + _: &mut Context<'_>, + buf: &[u8], + ) -> Poll> { + Poll::Ready(self.get_mut().write(buf)) + } + + fn poll_flush(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } + + fn poll_shutdown(self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) + } +} diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 6ffdecc35..cd212fb7e 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -197,13 +197,13 @@ mod tests { let req = TestRequest::default().method(Method::POST).finish(); assert_eq!( HandshakeError::GetMethodRequired, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default().finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -211,7 +211,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -222,7 +222,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -237,7 +237,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::NoVersionHeader, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -256,7 +256,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::UnsupportedVersion, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() @@ -275,7 +275,7 @@ mod tests { .finish(); assert_eq!( HandshakeError::BadWebsocketKey, - verify_handshake(req.head()).err().unwrap() + verify_handshake(req.head()).unwrap_err(), ); let req = TestRequest::default() From 3a192400a68135c0c1e5a069145e91f91cba3aa2 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 23 Dec 2020 23:47:07 +0800 Subject: [PATCH 068/187] Simplify handler (#1843) --- src/handler.rs | 184 ++++++++++++++++--------------------------------- src/route.rs | 9 ++- 2 files changed, 63 insertions(+), 130 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index 0dc06b3ce..3d0a2382e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -1,4 +1,3 @@ -use std::convert::Infallible; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -6,7 +5,7 @@ use std::task::{Context, Poll}; use actix_http::{Error, Response}; use actix_service::{Service, ServiceFactory}; -use futures_util::future::{ok, Ready}; +use futures_util::future::{ready, Ready}; use futures_util::ready; use pin_project::pin_project; @@ -36,9 +35,11 @@ where } #[doc(hidden)] +/// Extract arguments from request, run factory function and make response. pub struct Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, { @@ -49,6 +50,7 @@ where impl Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, { @@ -63,6 +65,7 @@ where impl Clone for Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, { @@ -74,172 +77,103 @@ where } } -impl Service for Handler +impl ServiceFactory for Handler where F: Factory, + T: FromRequest, R: Future, O: Responder, -{ - type Request = (T, HttpRequest); - type Response = ServiceResponse; - type Error = Infallible; - type Future = HandlerServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, (param, req): (T, HttpRequest)) -> Self::Future { - let fut = self.hnd.call(param); - HandlerServiceResponse::Future(fut, Some(req)) - } -} - -#[doc(hidden)] -#[pin_project(project = HandlerProj)] -pub enum HandlerServiceResponse -where - T: Future, - R: Responder, -{ - Future(#[pin] T, Option), - Responder(#[pin] R::Future, Option), -} - -impl Future for HandlerServiceResponse -where - T: Future, - R: Responder, -{ - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - loop { - match self.as_mut().project() { - HandlerProj::Future(fut, req) => { - let res = ready!(fut.poll(cx)); - let fut = res.respond_to(req.as_ref().unwrap()); - let state = HandlerServiceResponse::Responder(fut, req.take()); - self.as_mut().set(state); - } - HandlerProj::Responder(fut, req) => { - let res = ready!(fut.poll(cx)); - let req = req.take().unwrap(); - return match res { - Ok(res) => Poll::Ready(Ok(ServiceResponse::new(req, res))), - Err(e) => { - let res: Response = e.into().into(); - Poll::Ready(Ok(ServiceResponse::new(req, res))) - } - }; - } - } - } - } -} - -/// Extract arguments from request -pub struct Extract { - service: S, - _t: PhantomData, -} - -impl Extract { - pub fn new(service: S) -> Self { - Extract { - service, - _t: PhantomData, - } - } -} - -impl ServiceFactory for Extract -where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - > + Clone, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); - type Service = ExtractService; + type Service = Self; type InitError = (); type Future = Ready>; fn new_service(&self, _: ()) -> Self::Future { - ok(ExtractService { - _t: PhantomData, - service: self.service.clone(), - }) + ready(Ok(self.clone())) } } -pub struct ExtractService { - service: S, - _t: PhantomData, -} - -impl Service for ExtractService +// Handler is both it's ServiceFactory and Service Type. +impl Service for Handler where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - > + Clone, + F: Factory, + T: FromRequest, + R: Future, + O: Responder, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = ExtractResponse; + type Future = HandlerServiceFuture; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&mut self, req: Self::Request) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); - ExtractResponse::Future(fut, Some(req), self.service.clone()) + HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) } } -#[pin_project(project = ExtractProj)] -pub enum ExtractResponse { - Future(#[pin] T::Future, Option, S), - Response(#[pin] S::Future), +#[doc(hidden)] +#[pin_project(project = HandlerProj)] +pub enum HandlerServiceFuture +where + F: Factory, + T: FromRequest, + R: Future, + O: Responder, +{ + Extract(#[pin] T::Future, Option, F), + Handle(#[pin] R, Option), + Respond(#[pin] O::Future, Option), } -impl Future for ExtractResponse +impl Future for HandlerServiceFuture where - S: Service< - Request = (T, HttpRequest), - Response = ServiceResponse, - Error = Infallible, - >, + F: Factory, + T: FromRequest, + R: Future, + O: Responder, { + // Error type in this future is a placeholder type. + // all instances of error must be converted to ServiceResponse and return in Ok. type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { match self.as_mut().project() { - ExtractProj::Future(fut, req, srv) => { - let res = ready!(fut.poll(cx)); - let req = req.take().unwrap(); - match res { - Err(e) => { - let req = ServiceRequest::new(req); - return Poll::Ready(Ok(req.error_response(e.into()))); - } + HandlerProj::Extract(fut, req, handle) => { + match ready!(fut.poll(cx)) { Ok(item) => { - let fut = srv.call((item, req)); - self.as_mut().set(ExtractResponse::Response(fut)); + let fut = handle.call(item); + let state = HandlerServiceFuture::Handle(fut, req.take()); + self.as_mut().set(state); } - } + Err(e) => { + let res: Response = e.into().into(); + let req = req.take().unwrap(); + return Poll::Ready(Ok(ServiceResponse::new(req, res))); + } + }; + } + HandlerProj::Handle(fut, req) => { + let res = ready!(fut.poll(cx)); + let fut = res.respond_to(req.as_ref().unwrap()); + let state = HandlerServiceFuture::Respond(fut, req.take()); + self.as_mut().set(state); + } + HandlerProj::Respond(fut, req) => { + let res = ready!(fut.poll(cx)).unwrap_or_else(|e| e.into().into()); + let req = req.take().unwrap(); + return Poll::Ready(Ok(ServiceResponse::new(req, res))); } - ExtractProj::Response(fut) => return fut.poll(cx).map_err(|_| panic!()), } } } diff --git a/src/route.rs b/src/route.rs index f8ef458f9..439ae6c4a 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use futures_util::future::{ready, FutureExt, LocalBoxFuture}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; -use crate::handler::{Extract, Factory, Handler}; +use crate::handler::{Factory, Handler}; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -51,9 +51,9 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: Box::new(RouteNewService::new(Extract::new(Handler::new(|| { + service: Box::new(RouteNewService::new(Handler::new(|| { ready(HttpResponse::NotFound()) - })))), + }))), guards: Rc::new(Vec::new()), } } @@ -226,8 +226,7 @@ impl Route { R: Future + 'static, U: Responder + 'static, { - self.service = - Box::new(RouteNewService::new(Extract::new(Handler::new(handler)))); + self.service = Box::new(RouteNewService::new(Handler::new(handler))); self } } From 87655b3028eab8f077089725e476b6141e45b469 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 24 Dec 2020 07:58:25 +0800 Subject: [PATCH 069/187] reduce one clone on Arc. (#1850) --- src/types/json.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/types/json.rs b/src/types/json.rs index 95613a0ce..dc0870a6e 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -183,7 +183,7 @@ where let config = JsonConfig::from_req(req); let limit = config.limit; - let ctype = config.content_type.clone(); + let ctype = config.content_type.as_deref(); let err_handler = config.err_handler.clone(); JsonExtractFut { @@ -361,7 +361,7 @@ where pub fn new( req: &HttpRequest, payload: &mut Payload, - ctype: Option bool + Send + Sync>>, + ctype: Option<&(dyn Fn(mime::Mime) -> bool + Send + Sync)>, ) -> Self { // check content-type let json = if let Ok(Some(mime)) = req.mime_type() { From ecf08d5156a27671b9d36c866f3b64acff83f319 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 25 Dec 2020 03:15:17 +0800 Subject: [PATCH 070/187] Remove boxed future from h1 Dispatcher (#1836) --- actix-http/src/h1/dispatcher.rs | 196 +++++++++++++++++++------------- 1 file changed, 118 insertions(+), 78 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 1311a0987..ea8f91e0d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -130,8 +130,8 @@ where B: MessageBody, { None, - ExpectCall(Pin>), - ServiceCall(Pin>), + ExpectCall(#[pin] X::Future), + ServiceCall(#[pin] S::Future), SendPayload(#[pin] ResponseBody), } @@ -347,7 +347,7 @@ where self: Pin<&mut Self>, message: Response<()>, body: ResponseBody, - ) -> Result, DispatchError> { + ) -> Result<(), DispatchError> { let mut this = self.project(); this.codec .encode(Message::Item((message, body.size())), &mut this.write_buf) @@ -360,9 +360,10 @@ where this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); match body.size() { - BodySize::None | BodySize::Empty => Ok(State::None), - _ => Ok(State::SendPayload(body)), - } + BodySize::None | BodySize::Empty => this.state.set(State::None), + _ => this.state.set(State::SendPayload(body)), + }; + Ok(()) } fn send_continue(self: Pin<&mut Self>) { @@ -377,49 +378,52 @@ where ) -> Result { loop { let mut this = self.as_mut().project(); - let state = match this.state.project() { + // state is not changed on Poll::Pending. + // other variant and conditions always trigger a state change(or an error). + let state_change = match this.state.project() { StateProj::None => match this.messages.pop_front() { Some(DispatcherMessage::Item(req)) => { - Some(self.as_mut().handle_request(req, cx)?) + self.as_mut().handle_request(req, cx)?; + true } - Some(DispatcherMessage::Error(res)) => Some( + Some(DispatcherMessage::Error(res)) => { self.as_mut() - .send_response(res, ResponseBody::Other(Body::Empty))?, - ), + .send_response(res, ResponseBody::Other(Body::Empty))?; + true + } Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } - None => None, + None => false, }, - StateProj::ExpectCall(fut) => match fut.as_mut().poll(cx) { + StateProj::ExpectCall(fut) => match fut.poll(cx) { Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); this = self.as_mut().project(); - this.state - .set(State::ServiceCall(Box::pin(this.service.call(req)))); + this.state.set(State::ServiceCall(this.service.call(req))); continue; } Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); - Some(self.as_mut().send_response(res, body.into_body())?) + self.as_mut().send_response(res, body.into_body())?; + true } - Poll::Pending => None, + Poll::Pending => false, }, - StateProj::ServiceCall(fut) => match fut.as_mut().poll(cx) { + StateProj::ServiceCall(fut) => match fut.poll(cx) { Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); - let state = self.as_mut().send_response(res, body)?; - this = self.as_mut().project(); - this.state.set(state); + self.as_mut().send_response(res, body)?; continue; } Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); - Some(self.as_mut().send_response(res, body.into_body())?) + self.as_mut().send_response(res, body.into_body())?; + true } - Poll::Pending => None, + Poll::Pending => false, }, StateProj::SendPayload(mut stream) => { loop { @@ -454,11 +458,8 @@ where } }; - this = self.as_mut().project(); - - // set new state - if let Some(state) = state { - this.state.set(state); + // state is changed and continue when the state is not Empty + if state_change { if !self.state.is_empty() { continue; } @@ -483,39 +484,67 @@ where mut self: Pin<&mut Self>, req: Request, cx: &mut Context<'_>, - ) -> Result, DispatchError> { + ) -> Result<(), DispatchError> { // Handle `EXPECT: 100-Continue` header - let req = if req.head().expect() { - let mut task = Box::pin(self.as_mut().project().expect.call(req)); - match task.as_mut().poll(cx) { - Poll::Ready(Ok(req)) => { - self.as_mut().send_continue(); - req - } - Poll::Pending => return Ok(State::ExpectCall(task)), - Poll::Ready(Err(e)) => { - let e = e.into(); - let res: Response = e.into(); - let (res, body) = res.replace_body(()); - return self.send_response(res, body.into_body()); - } - } + if req.head().expect() { + // set dispatcher state so the future is pinned. + let task = self.as_mut().project().expect.call(req); + self.as_mut().project().state.set(State::ExpectCall(task)); } else { - req + // the same as above. + let task = self.as_mut().project().service.call(req); + self.as_mut().project().state.set(State::ServiceCall(task)); }; - // Call service - let mut task = Box::pin(self.as_mut().project().service.call(req)); - match task.as_mut().poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); - self.send_response(res, body) - } - Poll::Pending => Ok(State::ServiceCall(task)), - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - self.send_response(res, body.into_body()) + // eagerly poll the future for once(or twice if expect is resolved immediately). + loop { + match self.as_mut().project().state.project() { + StateProj::ExpectCall(fut) => { + match fut.poll(cx) { + // expect is resolved. continue loop and poll the service call branch. + Poll::Ready(Ok(req)) => { + self.as_mut().send_continue(); + let task = self.as_mut().project().service.call(req); + self.as_mut().project().state.set(State::ServiceCall(task)); + continue; + } + // future is pending. return Ok(()) to notify that a new state is + // set and the outer loop should be continue. + Poll::Pending => return Ok(()), + // future is error. send response and return a result. On success + // to notify the dispatcher a new state is set and the outer loop + // should be continue. + Poll::Ready(Err(e)) => { + let e = e.into(); + let res: Response = e.into(); + let (res, body) = res.replace_body(()); + return self.send_response(res, body.into_body()); + } + } + } + StateProj::ServiceCall(fut) => { + // return no matter the service call future's result. + return match fut.poll(cx) { + // future is resolved. send response and return a result. On success + // to notify the dispatcher a new state is set and the outer loop + // should be continue. + Poll::Ready(Ok(res)) => { + let (res, body) = res.into().replace_body(()); + self.send_response(res, body) + } + // see the comment on ExpectCall state branch's Pending. + Poll::Pending => Ok(()), + // see the comment on ExpectCall state branch's Ready(Err(e)). + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + self.send_response(res, body.into_body()) + } + }; + } + _ => unreachable!( + "State must be set to ServiceCall or ExceptCall in handle_request" + ), } } } @@ -566,9 +595,8 @@ where // handle request early if this.state.is_empty() { - let state = self.as_mut().handle_request(req, cx)?; + self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); - this.state.set(state); } else { this.messages.push_back(DispatcherMessage::Item(req)); } @@ -1004,7 +1032,7 @@ mod tests { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), CloneableService::new(ok_service()), @@ -1015,15 +1043,17 @@ mod tests { None, ); - match Pin::new(&mut h1).poll(cx) { + futures_util::pin_mut!(h1); + + match h1.as_mut().poll(cx) { Poll::Pending => panic!(), Poll::Ready(res) => assert!(res.is_err()), } - if let DispatcherState::Normal(ref mut inner) = h1.inner { + if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert_eq!( - &inner.io.take().unwrap().write_buf[..26], + &inner.project().io.take().unwrap().write_buf[..26], b"HTTP/1.1 400 Bad Request\r\n" ); } @@ -1043,7 +1073,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1054,9 +1084,11 @@ mod tests { None, ); + futures_util::pin_mut!(h1); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); - match Pin::new(&mut h1).poll(cx) { + match h1.as_mut().poll(cx) { Poll::Pending => panic!("first poll should not be pending"), Poll::Ready(res) => assert!(res.is_ok()), } @@ -1064,8 +1096,8 @@ mod tests { // polls: initial => shutdown assert_eq!(h1.poll_count, 2); - if let DispatcherState::Normal(ref mut inner) = h1.inner { - let res = &mut inner.io.take().unwrap().write_buf[..]; + if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + let res = &mut inner.project().io.take().unwrap().write_buf[..]; stabilize_date_header(res); let exp = b"\ @@ -1096,7 +1128,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1107,9 +1139,11 @@ mod tests { None, ); + futures_util::pin_mut!(h1); + assert!(matches!(&h1.inner, DispatcherState::Normal(_))); - match Pin::new(&mut h1).poll(cx) { + match h1.as_mut().poll(cx) { Poll::Pending => panic!("first poll should not be pending"), Poll::Ready(res) => assert!(res.is_err()), } @@ -1117,8 +1151,8 @@ mod tests { // polls: initial => shutdown assert_eq!(h1.poll_count, 1); - if let DispatcherState::Normal(ref mut inner) = h1.inner { - let res = &mut inner.io.take().unwrap().write_buf[..]; + if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() { + let res = &mut inner.project().io.take().unwrap().write_buf[..]; stabilize_date_header(res); let exp = b"\ @@ -1144,7 +1178,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( buf.clone(), cfg, CloneableService::new(echo_payload_service()), @@ -1164,7 +1198,9 @@ mod tests { ", ); - assert!(Pin::new(&mut h1).poll(cx).is_pending()); + futures_util::pin_mut!(h1); + + assert!(h1.as_mut().poll(cx).is_pending()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); // polls: manual @@ -1181,7 +1217,7 @@ mod tests { } buf.extend_read_buf("12345"); - assert!(Pin::new(&mut h1).poll(cx).is_ready()); + assert!(h1.as_mut().poll(cx).is_ready()); // polls: manual manual shutdown assert_eq!(h1.poll_count, 3); @@ -1214,7 +1250,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( buf.clone(), cfg, CloneableService::new(echo_path_service()), @@ -1234,7 +1270,9 @@ mod tests { ", ); - assert!(Pin::new(&mut h1).poll(cx).is_ready()); + futures_util::pin_mut!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); // polls: manual shutdown @@ -1272,7 +1310,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let mut h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( buf.clone(), cfg, CloneableService::new(ok_service()), @@ -1292,7 +1330,9 @@ mod tests { ", ); - assert!(Pin::new(&mut h1).poll(cx).is_ready()); + futures_util::pin_mut!(h1); + + assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); // polls: manual shutdown From 404b5a7709b7aaac8703a7b9751262508e4d7ca9 Mon Sep 17 00:00:00 2001 From: Edoardo Morandi Date: Sat, 26 Dec 2020 04:36:15 +0100 Subject: [PATCH 071/187] Add optional support for hidden files/directories (#1811) --- actix-files/CHANGES.md | 3 +++ actix-files/src/files.rs | 11 +++++++++++ actix-files/src/path_buf.rs | 22 +++++++++++++++++++++- actix-files/src/service.rs | 10 ++++++---- 4 files changed, 41 insertions(+), 5 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index c4d56010f..afd4696e8 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx +* Optionally support hidden files/directories. [#1811] + +[#1811]: https://github.com/actix/actix-web/pull/1811 ## 0.4.1 - 2020-11-24 diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index a99b4699e..d0cac6aa4 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -39,6 +39,7 @@ pub struct Files { mime_override: Option>, file_flags: named::Flags, guards: Option>, + hidden_files: bool, } impl fmt::Debug for Files { @@ -60,6 +61,7 @@ impl Clone for Files { path: self.path.clone(), mime_override: self.mime_override.clone(), guards: self.guards.clone(), + hidden_files: self.hidden_files, } } } @@ -103,6 +105,7 @@ impl Files { mime_override: None, file_flags: named::Flags::default(), guards: None, + hidden_files: false, } } @@ -213,6 +216,13 @@ impl Files { self } + + /// Enables serving hidden files and directories, allowing a leading dots in url fragments. + #[inline] + pub fn use_hidden_files(mut self) -> Self { + self.hidden_files = true; + self + } } impl HttpServiceFactory for Files { @@ -251,6 +261,7 @@ impl ServiceFactory for Files { mime_override: self.mime_override.clone(), file_flags: self.file_flags, guards: self.guards.clone(), + hidden_files: self.hidden_files, }; if let Some(ref default) = *self.default.borrow() { diff --git a/actix-files/src/path_buf.rs b/actix-files/src/path_buf.rs index 2f3ae84d4..dd8e5b503 100644 --- a/actix-files/src/path_buf.rs +++ b/actix-files/src/path_buf.rs @@ -15,12 +15,19 @@ impl FromStr for PathBufWrap { type Err = UriSegmentError; fn from_str(path: &str) -> Result { + Self::parse_path(path, false) + } +} + +impl PathBufWrap { + /// Parse a path, giving the choice of allowing hidden files to be considered valid segments. + pub fn parse_path(path: &str, hidden_files: bool) -> Result { let mut buf = PathBuf::new(); for segment in path.split('/') { if segment == ".." { buf.pop(); - } else if segment.starts_with('.') { + } else if !hidden_files && segment.starts_with('.') { return Err(UriSegmentError::BadStart('.')); } else if segment.starts_with('*') { return Err(UriSegmentError::BadStart('*')); @@ -96,4 +103,17 @@ mod tests { PathBuf::from_iter(vec!["seg2"]) ); } + + #[test] + fn test_parse_path() { + assert_eq!( + PathBufWrap::parse_path("/test/.tt", false).map(|t| t.0), + Err(UriSegmentError::BadStart('.')) + ); + + assert_eq!( + PathBufWrap::parse_path("/test/.tt", true).unwrap().0, + PathBuf::from_iter(vec!["test", ".tt"]) + ); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index cbf4c2d3b..dc4f2bd2c 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -31,6 +31,7 @@ pub struct FilesService { pub(crate) mime_override: Option>, pub(crate) file_flags: named::Flags, pub(crate) guards: Option>, + pub(crate) hidden_files: bool, } type FilesServiceFuture = Either< @@ -83,10 +84,11 @@ impl Service for FilesService { ))); } - let real_path: PathBufWrap = match req.match_info().path().parse() { - Ok(item) => item, - Err(e) => return Either::Left(ok(req.error_response(e))), - }; + 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))), + }; // full file path let path = match self.directory.join(&real_path).canonicalize() { From b373e1370d21b34ea5fdd79a97a1c15a44c03202 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Dec 2020 04:05:45 +0000 Subject: [PATCH 072/187] prepare files 0.5.0 release --- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 8 ++++---- actix-files/README.md | 4 ++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index afd4696e8..86e5e6563 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2020-xx-xx + + +## 0.5.0 - 2020-12-26 * Optionally support hidden files/directories. [#1811] [#1811]: https://github.com/actix/actix-web/pull/1811 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f7d32f8ec..b67abb1c1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.4.1" +version = "0.5.0" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -23,13 +23,13 @@ bitflags = "1" bytes = "0.5.3" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -derive_more = "0.99.2" +derive_more = "0.99.5" log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" -v_htmlescape = "0.11" +v_htmlescape = "0.12" [dev-dependencies] actix-rt = "1.0.0" -actix-web = { version = "3.0.0", features = ["openssl"] } +actix-web = "3.0.0" diff --git a/actix-files/README.md b/actix-files/README.md index 685e5dbe5..2953b4458 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.4.1)](https://docs.rs/actix-files/0.4.1) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.5.0)](https://docs.rs/actix-files/0.5.0) [![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.4.1/status.svg)](https://deps.rs/crate/actix-files/0.4.1) +[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0) [![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) From 1032f04deddecd8bdc3e95798382d51efb74a30b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 26 Dec 2020 20:46:36 +0800 Subject: [PATCH 073/187] remove unused `actix_http::h1::OneRequest` (#1853) --- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h1/service.rs | 112 ++--------------------------------- 2 files changed, 7 insertions(+), 107 deletions(-) diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 0c85f076a..3d5dea5d6 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -17,7 +17,7 @@ pub use self::codec::Codec; pub use self::dispatcher::Dispatcher; pub use self::expect::ExpectHandler; pub use self::payload::Payload; -pub use self::service::{H1Service, H1ServiceHandler, OneRequest}; +pub use self::service::{H1Service, H1ServiceHandler}; pub use self::upgrade::UpgradeHandler; pub use self::utils::SendResponse; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 5008791c0..cbba0609c 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -9,12 +9,12 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use futures_core::ready; -use futures_util::future::{ok, Ready}; +use futures_util::future::ready; use crate::body::MessageBody; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; -use crate::error::{DispatchError, Error, ParseError}; +use crate::error::{DispatchError, Error}; use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; @@ -22,7 +22,7 @@ use crate::{ConnectCallback, Extensions}; use super::codec::Codec; use super::dispatcher::Dispatcher; -use super::{ExpectHandler, Message, UpgradeHandler}; +use super::{ExpectHandler, UpgradeHandler}; /// `ServiceFactory` implementation for HTTP1 transport pub struct H1Service> { @@ -90,7 +90,7 @@ where > { pipeline_factory(|io: TcpStream| { let peer_addr = io.peer_addr().ok(); - ok((io, peer_addr)) + ready(Ok((io, peer_addr))) }) .and_then(self) } @@ -139,7 +139,7 @@ mod openssl { ) .and_then(|io: SslStream| { let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, peer_addr)) + ready(Ok((io, peer_addr))) }) .and_then(self.map_err(TlsError::Service)) } @@ -189,7 +189,7 @@ mod rustls { ) .and_then(|io: TlsStream| { let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, peer_addr)) + ready(Ok((io, peer_addr))) }) .and_then(self.map_err(TlsError::Service)) } @@ -500,103 +500,3 @@ where ) } } - -/// `ServiceFactory` implementation for `OneRequestService` service -#[derive(Default)] -pub struct OneRequest { - config: ServiceConfig, - _t: PhantomData, -} - -impl OneRequest -where - T: AsyncRead + AsyncWrite + Unpin, -{ - /// Create new `H1SimpleService` instance. - pub fn new() -> Self { - OneRequest { - config: ServiceConfig::default(), - _t: PhantomData, - } - } -} - -impl ServiceFactory for OneRequest -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Config = (); - type Request = T; - type Response = (Request, Framed); - type Error = ParseError; - type InitError = (); - type Service = OneRequestService; - type Future = Ready>; - - fn new_service(&self, _: ()) -> Self::Future { - ok(OneRequestService { - _t: PhantomData, - config: self.config.clone(), - }) - } -} - -/// `Service` implementation for HTTP1 transport. Reads one request and returns -/// request and framed object. -pub struct OneRequestService { - _t: PhantomData, - config: ServiceConfig, -} - -impl Service for OneRequestService -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Request = T; - type Response = (Request, Framed); - type Error = ParseError; - type Future = OneRequestServiceResponse; - - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, req: Self::Request) -> Self::Future { - OneRequestServiceResponse { - framed: Some(Framed::new(req, Codec::new(self.config.clone()))), - } - } -} - -#[doc(hidden)] -#[pin_project::pin_project] -pub struct OneRequestServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, -{ - #[pin] - framed: Option>, -} - -impl Future for OneRequestServiceResponse -where - T: AsyncRead + AsyncWrite + Unpin, -{ - type Output = Result<(Request, Framed), ParseError>; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - match ready!(this.framed.as_pin_mut().unwrap().next_item(cx)) { - Some(Ok(req)) => match req { - Message::Item(req) => { - let mut this = self.as_mut().project(); - Poll::Ready(Ok((req, this.framed.take().unwrap()))) - } - Message::Chunk(_) => unreachable!("Something is wrong"), - }, - Some(Err(err)) => Poll::Ready(Err(err)), - None => Poll::Ready(Err(ParseError::Incomplete)), - } - } -} From cbda928a33ce69e5d83059ff459464ba2a60b19f Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Sat, 26 Dec 2020 16:46:19 -0500 Subject: [PATCH 074/187] Rename factory to handler (#1852) --- CHANGES.md | 4 +-- src/handler.rs | 93 ++++++++++++++++++++++++++----------------------- src/lib.rs | 2 +- src/resource.rs | 10 +++--- src/route.rs | 14 ++++---- src/web.rs | 10 +++--- 6 files changed, 69 insertions(+), 64 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index ee9b9308d..da04c5aa3 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,13 +3,13 @@ ## Unreleased - 2020-xx-xx ### Changed * Bumped `rand` to `0.8` +* Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] [#1812]: https://github.com/actix/actix-web/pull/1812 - - +[#1852]: https://github.com/actix/actix-web/pull/1852 ## 3.3.2 - 2020-12-01 ### Fixed diff --git a/src/handler.rs b/src/handler.rs index 3d0a2382e..b928939a5 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -14,20 +14,25 @@ use crate::request::HttpRequest; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; -/// Async handler converter factory -pub trait Factory: Clone + 'static +/// A request handler is an async function that accepts zero or more parameters that can be +/// extracted from a request (ie, [`impl FromRequest`](crate::FromRequest)) and returns a type that can be converted into +/// an [`HttpResponse`](crate::HttpResponse) (ie, [`impl Responder`](crate::Responder)). +/// +/// If you got the error `the trait Handler<_, _, _> is not implemented`, then your function is not +/// a valid handler. See [Request Handlers](https://actix.rs/docs/handlers/) for more information. +pub trait Handler: Clone + 'static where - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { fn call(&self, param: T) -> R; } -impl Factory<(), R, O> for F +impl Handler<(), R> for F where F: Fn() -> R + Clone + 'static, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { fn call(&self, _: ()) -> R { (self)() @@ -36,53 +41,53 @@ where #[doc(hidden)] /// Extract arguments from request, run factory function and make response. -pub struct Handler +pub struct HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { hnd: F, - _t: PhantomData<(T, R, O)>, + _t: PhantomData<(T, R)>, } -impl Handler +impl HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { pub fn new(hnd: F) -> Self { - Handler { + Self { hnd, _t: PhantomData, } } } -impl Clone for Handler +impl Clone for HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { fn clone(&self) -> Self { - Handler { + Self { hnd: self.hnd.clone(), _t: PhantomData, } } } -impl ServiceFactory for Handler +impl ServiceFactory for HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { type Request = ServiceRequest; type Response = ServiceResponse; @@ -97,18 +102,18 @@ where } } -// Handler is both it's ServiceFactory and Service Type. -impl Service for Handler +// Handler is both it's ServiceHandler and Service Type. +impl Service for HandlerService where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = HandlerServiceFuture; + type Future = HandlerServiceFuture; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -123,24 +128,24 @@ where #[doc(hidden)] #[pin_project(project = HandlerProj)] -pub enum HandlerServiceFuture +pub enum HandlerServiceFuture where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { Extract(#[pin] T::Future, Option, F), Handle(#[pin] R, Option), - Respond(#[pin] O::Future, Option), + Respond(#[pin] ::Future, Option), } -impl Future for HandlerServiceFuture +impl Future for HandlerServiceFuture where - F: Factory, + F: Handler, T: FromRequest, - R: Future, - O: Responder, + R: Future, + R::Output: Responder, { // Error type in this future is a placeholder type. // all instances of error must be converted to ServiceResponse and return in Ok. @@ -181,10 +186,10 @@ where /// FromRequest trait impl for tuples macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { - impl Factory<($($T,)+), Res, O> for Func + impl Handler<($($T,)+), Res> for Func where Func: Fn($($T,)+) -> Res + Clone + 'static, - Res: Future, - O: Responder, + Res: Future, + Res::Output: Responder, { fn call(&self, param: ($($T,)+)) -> Res { (self)($(param.$n,)+) diff --git a/src/lib.rs b/src/lib.rs index b8346d966..8246c8286 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -121,7 +121,7 @@ pub mod dev { pub use crate::config::{AppConfig, AppService}; #[doc(hidden)] - pub use crate::handler::Factory; + pub use crate::handler::Handler; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; pub use crate::service::{ diff --git a/src/resource.rs b/src/resource.rs index dd9b23012..f6046d652 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -17,7 +17,7 @@ use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; use crate::extract::FromRequest; use crate::guard::Guard; -use crate::handler::Factory; +use crate::handler::Handler; use crate::responder::Responder; use crate::route::{CreateRouteService, Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; @@ -227,12 +227,12 @@ where /// # fn index(req: HttpRequest) -> HttpResponse { unimplemented!() } /// App::new().service(web::resource("/").route(web::route().to(index))); /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Factory, + F: Handler, I: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, + R: Future + 'static, + R::Output: Responder + 'static, { self.routes.push(Route::new().to(handler)); self diff --git a/src/route.rs b/src/route.rs index 439ae6c4a..00d93fce9 100644 --- a/src/route.rs +++ b/src/route.rs @@ -11,7 +11,7 @@ use futures_util::future::{ready, FutureExt, LocalBoxFuture}; use crate::extract::FromRequest; use crate::guard::{self, Guard}; -use crate::handler::{Factory, Handler}; +use crate::handler::{Handler, HandlerService}; use crate::responder::Responder; use crate::service::{ServiceRequest, ServiceResponse}; use crate::HttpResponse; @@ -51,7 +51,7 @@ impl Route { #[allow(clippy::new_without_default)] pub fn new() -> Route { Route { - service: Box::new(RouteNewService::new(Handler::new(|| { + service: Box::new(RouteNewService::new(HandlerService::new(|| { ready(HttpResponse::NotFound()) }))), guards: Rc::new(Vec::new()), @@ -219,14 +219,14 @@ impl Route { /// ); /// } /// ``` - pub fn to(mut self, handler: F) -> Self + pub fn to(mut self, handler: F) -> Self where - F: Factory, + F: Handler, T: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, + R: Future + 'static, + R::Output: Responder + 'static, { - self.service = Box::new(RouteNewService::new(Handler::new(handler))); + self.service = Box::new(RouteNewService::new(HandlerService::new(handler))); self } } diff --git a/src/web.rs b/src/web.rs index bf2158917..85e5f2e7b 100644 --- a/src/web.rs +++ b/src/web.rs @@ -9,7 +9,7 @@ pub use futures_channel::oneshot::Canceled; use crate::error::BlockingError; use crate::extract::FromRequest; -use crate::handler::Factory; +use crate::handler::Handler; use crate::resource::Resource; use crate::responder::Responder; use crate::route::Route; @@ -244,12 +244,12 @@ pub fn method(method: Method) -> Route { /// web::to(index)) /// ); /// ``` -pub fn to(handler: F) -> Route +pub fn to(handler: F) -> Route where - F: Factory, + F: Handler, I: FromRequest + 'static, - R: Future + 'static, - U: Responder + 'static, + R: Future + 'static, + R::Output: Responder + 'static, { Route::new().to(handler) } From f9fcf56d5c525956572051303ab8bce793cb4456 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 28 Dec 2020 04:37:53 +0800 Subject: [PATCH 075/187] reduce branch in actix_http::h1::codec (#1854) --- actix-http/src/h1/codec.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index c9a62dc30..d5035df26 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -111,8 +111,8 @@ impl Decoder for Codec { type Error = ParseError; fn decode(&mut self, src: &mut BytesMut) -> Result, Self::Error> { - if self.payload.is_some() { - Ok(match self.payload.as_mut().unwrap().decode(src)? { + if let Some(ref mut payload) = self.payload { + Ok(match payload.decode(src)? { Some(PayloadItem::Chunk(chunk)) => Some(Message::Chunk(Some(chunk))), Some(PayloadItem::Eof) => { self.payload.take(); From 8c9ea43e232b55234fe24b04b023a7a7d5f18bd4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 27 Dec 2020 20:53:19 +0000 Subject: [PATCH 076/187] address clippy warnings --- actix-files/CHANGES.md | 1 + actix-files/src/range.rs | 41 +++++++++++++++++++---------------- src/middleware/errhandlers.rs | 1 + tests/test_httpserver.rs | 1 + 4 files changed, 25 insertions(+), 19 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 86e5e6563..49768419b 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,7 @@ # Changes ## Unreleased - 2020-xx-xx +* `HttpRange::parse` now has its own error type. ## 0.5.0 - 2020-12-26 diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index e891ca7ec..e420ce414 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -1,3 +1,5 @@ +use derive_more::{Display, Error}; + /// HTTP Range header representation. #[derive(Debug, Clone, Copy)] pub struct HttpRange { @@ -11,17 +13,21 @@ pub struct HttpRange { const PREFIX: &str = "bytes="; const PREFIX_LEN: usize = 6; +#[derive(Debug, Clone, Display, Error)] +#[display(fmt = "Parse HTTP Range failed")] +pub struct ParseRangeErr(#[error(not(source))] ()); + impl HttpRange { /// Parses Range HTTP header string as per RFC 2616. /// /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). /// `size` is full size of response (file). - pub fn parse(header: &str, size: u64) -> Result, ()> { + pub fn parse(header: &str, size: u64) -> Result, ParseRangeErr> { if header.is_empty() { return Ok(Vec::new()); } if !header.starts_with(PREFIX) { - return Err(()); + return Err(ParseRangeErr(())); } let size_sig = size as i64; @@ -34,13 +40,14 @@ impl HttpRange { .map(|ra| { let mut start_end_iter = ra.split('-'); - let start_str = start_end_iter.next().ok_or(())?.trim(); - let end_str = start_end_iter.next().ok_or(())?.trim(); + let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); + let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); if start_str.is_empty() { // If no start is specified, end specifies the // range start relative to the end of the file. - let mut length: i64 = end_str.parse().map_err(|_| ())?; + let mut length: i64 = + end_str.parse().map_err(|_| ParseRangeErr(()))?; if length > size_sig { length = size_sig; @@ -51,10 +58,10 @@ impl HttpRange { length: length as u64, })) } else { - let start: i64 = start_str.parse().map_err(|_| ())?; + let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?; if start < 0 { - return Err(()); + return Err(ParseRangeErr(())); } if start >= size_sig { no_overlap = true; @@ -65,10 +72,11 @@ impl HttpRange { // If no end is specified, range extends to end of the file. size_sig - start } else { - let mut end: i64 = end_str.parse().map_err(|_| ())?; + let mut end: i64 = + end_str.parse().map_err(|_| ParseRangeErr(()))?; if start > end { - return Err(()); + return Err(ParseRangeErr(())); } if end >= size_sig { @@ -89,7 +97,7 @@ impl HttpRange { let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); if no_overlap && ranges.is_empty() { - return Err(()); + return Err(ParseRangeErr(())); } Ok(ranges) @@ -333,8 +341,7 @@ mod tests { if expected.is_empty() { continue; } else { - assert!( - false, + panic!( "parse({}, {}) returned error {:?}", header, size, @@ -346,28 +353,24 @@ mod tests { let got = res.unwrap(); if got.len() != expected.len() { - assert!( - false, + panic!( "len(parseRange({}, {})) = {}, want {}", header, size, got.len(), expected.len() ); - continue; } for i in 0..expected.len() { if got[i].start != expected[i].start { - assert!( - false, + panic!( "parseRange({}, {})[{}].start = {}, want {}", header, size, i, got[i].start, expected[i].start ) } if got[i].length != expected[i].length { - assert!( - false, + panic!( "parseRange({}, {})[{}].length = {}, want {}", header, size, i, got[i].length, expected[i].length ) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index c0cb9594e..d2d3b0d8c 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -179,6 +179,7 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } + #[allow(clippy::unnecessary_wraps)] fn render_500_async( mut res: ServiceResponse, ) -> Result> { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 118640aca..d164f4445 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -63,6 +63,7 @@ async fn test_start() { let _ = sys.stop(); } +#[allow(clippy::unnecessary_wraps)] #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; From 093d3a6c59338045f46ee85f1a3ec3b028f97712 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 27 Dec 2020 23:23:30 +0000 Subject: [PATCH 077/187] remove deprecated on_connect methods (#1857) --- actix-http/CHANGES.md | 7 +++++++ actix-http/src/builder.rs | 23 ----------------------- actix-http/src/clinu/mod.rs | 0 actix-http/src/h1/dispatcher.rs | 18 ------------------ actix-http/src/h1/service.rs | 23 ----------------------- actix-http/src/h2/dispatcher.rs | 10 ---------- actix-http/src/h2/service.rs | 25 ------------------------- actix-http/src/helpers.rs | 14 -------------- actix-http/src/service.rs | 30 +----------------------------- actix-http/tests/test_openssl.rs | 2 -- actix-http/tests/test_server.rs | 2 -- 11 files changed, 8 insertions(+), 146 deletions(-) create mode 100644 actix-http/src/clinu/mod.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 81577688d..c43246bb0 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,6 +4,13 @@ ### Changed * Bumped `rand` to `0.8` +### Removed +* Deprecated `on_connect` methods have been removed. Prefer the new + `on_connect_ext` technique. [#1857] + +[#1857]: https://github.com/actix/actix-web/pull/1857 + + ## 2.2.0 - 2020-11-25 ### Added * HttpResponse builders for 1xx status codes. [#1768] diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index b28c69761..df1b332c1 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -10,7 +10,6 @@ use crate::config::{KeepAlive, ServiceConfig}; use crate::error::Error; use crate::h1::{Codec, ExpectHandler, H1Service, UpgradeHandler}; use crate::h2::H2Service; -use crate::helpers::{Data, DataFactory}; use crate::request::Request; use crate::response::Response; use crate::service::HttpService; @@ -28,8 +27,6 @@ pub struct HttpServiceBuilder> { local_addr: Option, expect: X, upgrade: Option, - // DEPRECATED: in favor of on_connect_ext - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, S)>, } @@ -51,7 +48,6 @@ where local_addr: None, expect: ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -141,7 +137,6 @@ where local_addr: self.local_addr, expect: expect.into_factory(), upgrade: self.upgrade, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } @@ -171,26 +166,11 @@ where local_addr: self.local_addr, expect: self.expect, upgrade: Some(upgrade.into_factory()), - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } } - /// Set on-connect callback. - /// - /// Called once per connection. Return value of the call is stored in request extensions. - /// - /// *SOFT DEPRECATED*: Prefer the `on_connect_ext` style callback. - pub fn on_connect(mut self, f: F) -> Self - where - F: Fn(&T) -> I + 'static, - I: Clone + 'static, - { - self.on_connect = Some(Rc::new(move |io| Box::new(Data(f(io))))); - self - } - /// Sets the callback to be run on connection establishment. /// /// Has mutable access to a data container that will be merged into request extensions. @@ -224,7 +204,6 @@ where H1Service::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) - .on_connect(self.on_connect) .on_connect_ext(self.on_connect_ext) } @@ -247,7 +226,6 @@ where ); H2Service::with_config(cfg, service.into_factory()) - .on_connect(self.on_connect) .on_connect_ext(self.on_connect_ext) } @@ -272,7 +250,6 @@ where HttpService::with_config(cfg, service.into_factory()) .expect(self.expect) .upgrade(self.upgrade) - .on_connect(self.on_connect) .on_connect_ext(self.on_connect_ext) } } diff --git a/actix-http/src/clinu/mod.rs b/actix-http/src/clinu/mod.rs new file mode 100644 index 000000000..e69de29bb diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index ea8f91e0d..91e208aac 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -19,7 +19,6 @@ use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; -use crate::helpers::DataFactory; use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::Response; @@ -96,7 +95,6 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option>, on_connect_data: Extensions, flags: Flags, peer_addr: Option, @@ -184,7 +182,6 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option>, on_connect_data: Extensions, peer_addr: Option, ) -> Self { @@ -197,7 +194,6 @@ where service, expect, upgrade, - on_connect, on_connect_data, peer_addr, ) @@ -213,7 +209,6 @@ where service: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option>, on_connect_data: Extensions, peer_addr: Option, ) -> Self { @@ -246,7 +241,6 @@ where service, expect, upgrade, - on_connect, on_connect_data, flags, peer_addr, @@ -572,12 +566,6 @@ where let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; - // DEPRECATED - // set on_connect data - if let Some(ref on_connect) = this.on_connect { - on_connect.set(&mut req.extensions_mut()); - } - // merge on_connect_ext data into request extensions req.extensions_mut().drain_from(this.on_connect_data); @@ -1038,7 +1026,6 @@ mod tests { CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1079,7 +1066,6 @@ mod tests { CloneableService::new(echo_path_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1134,7 +1120,6 @@ mod tests { CloneableService::new(echo_path_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1184,7 +1169,6 @@ mod tests { CloneableService::new(echo_payload_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1256,7 +1240,6 @@ mod tests { CloneableService::new(echo_path_service()), CloneableService::new(ExpectHandler), None, - None, Extensions::new(), None, ); @@ -1316,7 +1299,6 @@ mod tests { CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), Some(CloneableService::new(UpgradeHandler(PhantomData))), - None, Extensions::new(), None, ); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index cbba0609c..919a5d932 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -15,7 +15,6 @@ use crate::body::MessageBody; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{ConnectCallback, Extensions}; @@ -30,7 +29,6 @@ pub struct H1Service> { cfg: ServiceConfig, expect: X, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -53,7 +51,6 @@ where srv: service.into_factory(), expect: ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -215,7 +212,6 @@ where cfg: self.cfg, srv: self.srv, upgrade: self.upgrade, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } @@ -232,21 +228,11 @@ where cfg: self.cfg, srv: self.srv, expect: self.expect, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } } - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } - /// Set on connect callback. pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { self.on_connect_ext = f; @@ -284,7 +270,6 @@ where fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), expect: None, upgrade: None, - on_connect: self.on_connect.clone(), on_connect_ext: self.on_connect_ext.clone(), cfg: Some(self.cfg.clone()), _t: PhantomData, @@ -314,7 +299,6 @@ where fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, cfg: Option, _t: PhantomData<(T, B)>, @@ -371,7 +355,6 @@ where service, this.expect.take().unwrap(), this.upgrade.take(), - this.on_connect.clone(), this.on_connect_ext.clone(), ) })) @@ -383,7 +366,6 @@ pub struct H1ServiceHandler { srv: CloneableService, expect: CloneableService, upgrade: Option>, - on_connect: Option Box>>, on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, @@ -405,7 +387,6 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, ) -> H1ServiceHandler { H1ServiceHandler { @@ -413,7 +394,6 @@ where expect: CloneableService::new(expect), upgrade: upgrade.map(CloneableService::new), cfg, - on_connect, on_connect_ext, _t: PhantomData, } @@ -480,8 +460,6 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); - let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions @@ -494,7 +472,6 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - deprecated_on_connect, connect_extensions, addr, ) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 594339121..7a0be9492 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -18,7 +18,6 @@ use crate::body::{BodySize, MessageBody, ResponseBody}; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::httpmessage::HttpMessage; use crate::message::ResponseHead; use crate::payload::Payload; @@ -36,7 +35,6 @@ where { service: CloneableService, connection: Connection, - on_connect: Option>, on_connect_data: Extensions, config: ServiceConfig, peer_addr: Option, @@ -57,7 +55,6 @@ where pub(crate) fn new( service: CloneableService, connection: Connection, - on_connect: Option>, on_connect_data: Extensions, config: ServiceConfig, timeout: Option, @@ -84,7 +81,6 @@ where config, peer_addr, connection, - on_connect, on_connect_data, ka_expire, ka_timer, @@ -134,12 +130,6 @@ where head.headers = parts.headers.into(); head.peer_addr = this.peer_addr; - // DEPRECATED - // set on_connect data - if let Some(ref on_connect) = this.on_connect { - on_connect.set(&mut req.extensions_mut()); - } - // merge on_connect_ext data into request extensions req.extensions_mut().drain_from(&mut this.on_connect_data); diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 3103127b4..b1fb9a634 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -20,7 +20,6 @@ use crate::body::MessageBody; use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{ConnectCallback, Extensions}; @@ -31,7 +30,6 @@ use super::dispatcher::Dispatcher; pub struct H2Service { srv: S, cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -51,23 +49,12 @@ where ) -> Self { H2Service { cfg, - on_connect: None, on_connect_ext: None, srv: service.into_factory(), _t: PhantomData, } } - /// Set on connect callback. - - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } - /// Set on connect callback. pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { self.on_connect_ext = f; @@ -212,7 +199,6 @@ where H2ServiceResponse { fut: self.srv.new_service(()), cfg: Some(self.cfg.clone()), - on_connect: self.on_connect.clone(), on_connect_ext: self.on_connect_ext.clone(), _t: PhantomData, } @@ -225,7 +211,6 @@ pub struct H2ServiceResponse { #[pin] fut: S::Future, cfg: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -248,7 +233,6 @@ where let this = self.as_mut().project(); H2ServiceHandler::new( this.cfg.take().unwrap(), - this.on_connect.clone(), this.on_connect_ext.clone(), service, ) @@ -260,7 +244,6 @@ where pub struct H2ServiceHandler { srv: CloneableService, cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -275,13 +258,11 @@ where { fn new( cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, srv: S, ) -> H2ServiceHandler { H2ServiceHandler { cfg, - on_connect, on_connect_ext, srv: CloneableService::new(srv), _t: PhantomData, @@ -312,8 +293,6 @@ where } fn call(&mut self, (io, addr): Self::Request) -> Self::Future { - let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); - let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions @@ -325,7 +304,6 @@ where Some(self.srv.clone()), Some(self.cfg.clone()), addr, - deprecated_on_connect, Some(connect_extensions), server::handshake(io), ), @@ -343,7 +321,6 @@ where Option>, Option, Option, - Option>, Option, Handshake, ), @@ -379,7 +356,6 @@ where ref mut srv, ref mut config, ref peer_addr, - ref mut on_connect, ref mut on_connect_data, ref mut handshake, ) => match Pin::new(handshake).poll(cx) { @@ -387,7 +363,6 @@ where self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, - on_connect.take(), on_connect_data.take().unwrap(), config.take().unwrap(), None, diff --git a/actix-http/src/helpers.rs b/actix-http/src/helpers.rs index ac0e0f118..13195f7db 100644 --- a/actix-http/src/helpers.rs +++ b/actix-http/src/helpers.rs @@ -3,8 +3,6 @@ use std::io; use bytes::{BufMut, BytesMut}; use http::Version; -use crate::extensions::Extensions; - const DIGITS_START: u8 = b'0'; pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) { @@ -56,18 +54,6 @@ impl<'a> io::Write for Writer<'a> { } } -pub(crate) trait DataFactory { - fn set(&self, ext: &mut Extensions); -} - -pub(crate) struct Data(pub(crate) T); - -impl DataFactory for Data { - fn set(&self, ext: &mut Extensions) { - ext.insert(self.0.clone()) - } -} - #[cfg(test)] mod tests { use std::str::from_utf8; diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 75745209c..527ed3833 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -17,7 +17,6 @@ use crate::builder::HttpServiceBuilder; use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error}; -use crate::helpers::DataFactory; use crate::request::Request; use crate::response::Response; use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; @@ -28,8 +27,6 @@ pub struct HttpService cfg: ServiceConfig, expect: X, upgrade: Option, - // DEPRECATED: in favor of on_connect_ext - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B)>, } @@ -67,7 +64,6 @@ where srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -83,7 +79,6 @@ where srv: service.into_factory(), expect: h1::ExpectHandler, upgrade: None, - on_connect: None, on_connect_ext: None, _t: PhantomData, } @@ -116,7 +111,6 @@ where cfg: self.cfg, srv: self.srv, upgrade: self.upgrade, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } @@ -142,21 +136,11 @@ where cfg: self.cfg, srv: self.srv, expect: self.expect, - on_connect: self.on_connect, on_connect_ext: self.on_connect_ext, _t: PhantomData, } } - /// Set on connect callback. - pub(crate) fn on_connect( - mut self, - f: Option Box>>, - ) -> Self { - self.on_connect = f; - self - } - /// Set connect callback with mutable access to request data container. pub(crate) fn on_connect_ext(mut self, f: Option>>) -> Self { self.on_connect_ext = f; @@ -366,7 +350,6 @@ where fut_upg: self.upgrade.as_ref().map(|f| f.new_service(())), expect: None, upgrade: None, - on_connect: self.on_connect.clone(), on_connect_ext: self.on_connect_ext.clone(), cfg: self.cfg.clone(), _t: PhantomData, @@ -391,7 +374,6 @@ pub struct HttpServiceResponse< fut_upg: Option, expect: Option, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, cfg: ServiceConfig, _t: PhantomData<(T, B)>, @@ -451,7 +433,6 @@ where service, this.expect.take().unwrap(), this.upgrade.take(), - this.on_connect.clone(), this.on_connect_ext.clone(), ) })) @@ -464,7 +445,6 @@ pub struct HttpServiceHandler { expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, - on_connect: Option Box>>, on_connect_ext: Option>>, _t: PhantomData<(T, B, X)>, } @@ -486,12 +466,10 @@ where srv: S, expect: X, upgrade: Option, - on_connect: Option Box>>, on_connect_ext: Option>>, ) -> HttpServiceHandler { HttpServiceHandler { cfg, - on_connect, on_connect_ext, srv: CloneableService::new(srv), expect: CloneableService::new(expect), @@ -564,7 +542,6 @@ where fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { let mut connect_extensions = Extensions::new(); - let deprecated_on_connect = self.on_connect.as_ref().map(|handler| handler(&io)); if let Some(ref handler) = self.on_connect_ext { handler(&io, &mut connect_extensions); } @@ -575,7 +552,6 @@ where server::handshake(io), self.cfg.clone(), self.srv.clone(), - deprecated_on_connect, connect_extensions, peer_addr, ))), @@ -588,7 +564,6 @@ where self.srv.clone(), self.expect.clone(), self.upgrade.clone(), - deprecated_on_connect, connect_extensions, peer_addr, )), @@ -617,7 +592,6 @@ where Handshake, ServiceConfig, CloneableService, - Option>, Extensions, Option, )>, @@ -694,12 +668,10 @@ where } else { panic!() }; - let (_, cfg, srv, on_connect, on_connect_data, peer_addr) = - data.take().unwrap(); + let (_, cfg, srv, on_connect_data, peer_addr) = data.take().unwrap(); self.set(State::H2(Dispatcher::new( srv, conn, - on_connect, on_connect_data, cfg, None, diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 05f01d240..6b80bad0a 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -410,10 +410,8 @@ async fn test_h2_service_error() { async fn test_h2_on_connect() { let srv = test_server(move || { HttpService::build() - .on_connect(|_| 10usize) .on_connect_ext(|_, data| data.insert(20isize)) .h2(|req: Request| { - assert!(req.extensions().contains::()); assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) }) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index de6368fda..44794e199 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -662,10 +662,8 @@ async fn test_h1_service_error() { async fn test_h1_on_connect() { let srv = test_server(|| { HttpService::build() - .on_connect(|_| 10usize) .on_connect_ext(|_, data| data.insert(20isize)) .h1(|req: Request| { - assert!(req.extensions().contains::()); assert!(req.extensions().contains::()); future::ok::<_, ()>(Response::Ok().finish()) }) From 2a2a20c3e740a47b1534662500e9d3c065bdfa79 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 28 Dec 2020 00:44:15 +0000 Subject: [PATCH 078/187] bump msrv to 1.46 (#1858) --- .github/workflows/linux.yml | 18 +++++++++-- .github/workflows/macos.yml | 12 ++++++++ .github/workflows/windows.yml | 12 ++++++++ CHANGES.md | 1 + README.md | 4 +-- actix-files/README.md | 4 +-- actix-http-test/README.md | 2 +- actix-http/README.md | 2 +- .../src/header/common/content_disposition.rs | 3 +- actix-http/src/lib.rs | 1 - actix-web-codegen/README.md | 4 +-- actix-web-codegen/tests/trybuild.rs | 30 +++++-------------- .../route-missing-method-fail-msrv.rs | 1 - .../route-missing-method-fail-msrv.stderr | 11 ------- awc/README.md | 2 +- src/lib.rs | 4 +-- 16 files changed, 59 insertions(+), 52 deletions(-) delete mode 120000 actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs delete mode 100644 actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr diff --git a/.github/workflows/linux.yml b/.github/workflows/linux.yml index a068070ff..53f22df63 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/linux.yml @@ -13,7 +13,7 @@ jobs: fail-fast: false matrix: version: - - 1.42.0 # MSRV + - 1.46.0 # MSRV - stable - nightly @@ -30,6 +30,13 @@ jobs: profile: minimal override: true + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.0.1 + - name: check build uses: actions-rs/cargo@v1 with: @@ -58,12 +65,17 @@ jobs: args: --package=awc --no-default-features --features=rustls -- --nocapture - name: Generate coverage file - if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') + if: matrix.version == 'stable' && github.ref == 'refs/heads/master' run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml - name: Upload to Codecov - if: matrix.version == 'stable' && (github.ref == 'refs/heads/master' || github.event_name == 'pull_request') + if: matrix.version == 'stable' && github.ref == 'refs/heads/master' uses: codecov/codecov-action@v1 with: file: cobertura.xml + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml index dc8558ac1..6b5366faf 100644 --- a/.github/workflows/macos.yml +++ b/.github/workflows/macos.yml @@ -29,6 +29,13 @@ jobs: profile: minimal override: true + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.0.1 + - name: check build uses: actions-rs/cargo@v1 with: @@ -42,3 +49,8 @@ jobs: args: --all --all-features --no-fail-fast -- --nocapture --skip=test_h2_content_length --skip=test_reading_deflate_encoding_large_random_rustls + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml index d53d50a61..d3de72a61 100644 --- a/.github/workflows/windows.yml +++ b/.github/workflows/windows.yml @@ -41,6 +41,13 @@ jobs: Get-ChildItem C:\vcpkg\installed\x64-windows\bin Get-ChildItem C:\vcpkg\installed\x64-windows\lib + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: + command: generate-lockfile + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.0.1 + - name: check build uses: actions-rs/cargo@v1 with: @@ -62,3 +69,8 @@ jobs: --skip=test_connection_force_close --skip=test_connection_server_close --skip=test_connection_wait_queue_force_close + + - name: Clear the cargo caches + run: | + cargo install cargo-cache --no-default-features --features ci-autoclean + cargo-cache diff --git a/CHANGES.md b/CHANGES.md index da04c5aa3..fa56acc17 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,7 @@ ### Changed * Bumped `rand` to `0.8` * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +* MSRV is now 1.46.0. ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/README.md b/README.md index b9f2b7594..62ee50243 100644 --- a/README.md +++ b/README.md @@ -7,7 +7,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![Documentation](https://docs.rs/actix-web/badge.svg?version=3.3.2)](https://docs.rs/actix-web/3.3.2) -[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
@@ -34,7 +34,7 @@ * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) * Supports [Actix actor framework](https://github.com/actix/actix) -* Runs on stable Rust 1.42+ +* Runs on stable Rust 1.46+ ## Documentation diff --git a/actix-files/README.md b/actix-files/README.md index 2953b4458..463f20224 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.5.0)](https://docs.rs/actix-files/0.5.0) -[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![License](https://img.shields.io/crates/l/actix-files.svg)
[![dependency status](https://deps.rs/crate/actix-files/0.5.0/status.svg)](https://deps.rs/crate/actix-files/0.5.0) @@ -16,4 +16,4 @@ - [API Documentation](https://docs.rs/actix-files/) - [Example Project](https://github.com/actix/examples/tree/master/static_index) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum supported Rust version: 1.42 or later +- Minimum supported Rust version: 1.46 or later diff --git a/actix-http-test/README.md b/actix-http-test/README.md index c847c8515..bca9a7976 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -12,4 +12,4 @@ - [API Documentation](https://docs.rs/actix-http-test) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum Supported Rust Version (MSRV): 1.42.0 +- Minimum Supported Rust Version (MSRV): 1.46.0 diff --git a/actix-http/README.md b/actix-http/README.md index 9103cd184..9dfb85e24 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -12,7 +12,7 @@ - [API Documentation](https://docs.rs/actix-http) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum Supported Rust Version (MSRV): 1.42.0 +- Minimum Supported Rust Version (MSRV): 1.46.0 ## Example diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 826cfef63..4c512acbe 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -318,9 +318,8 @@ impl ContentDisposition { return Err(crate::error::ParseError::Header); } left = new_left; - if param_name.ends_with('*') { + if let Some(param_name) = param_name.strip_suffix('*') { // extended parameters - let param_name = ¶m_name[..param_name.len() - 1]; // trim asterisk let (ext_value, new_left) = split_once_and_trim(left, ';'); left = new_left; let ext_value = header::parse_extended_value(ext_value)?; diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 89d64fb77..94cc50a76 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -7,7 +7,6 @@ clippy::new_without_default, clippy::borrow_interior_mutable_const )] -#![allow(clippy::manual_strip)] // Allow this to keep MSRV(1.42). #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 283591e86..887502075 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -4,7 +4,7 @@ [![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) [![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/) -[![Version](https://img.shields.io/badge/rustc-1.42+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.42.html) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) @@ -14,7 +14,7 @@ - [API Documentation](https://docs.rs/actix-web-codegen) - [Chat on Gitter](https://gitter.im/actix/actix-web) - Cargo package: [actix-web-codegen](https://crates.io/crates/actix-web-codegen) -- Minimum supported Rust version: 1.42 or later. +- Minimum supported Rust version: 1.46 or later. ## Compile Testing Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this. diff --git a/actix-web-codegen/tests/trybuild.rs b/actix-web-codegen/tests/trybuild.rs index 6c7c58986..d2d8a38f5 100644 --- a/actix-web-codegen/tests/trybuild.rs +++ b/actix-web-codegen/tests/trybuild.rs @@ -6,31 +6,15 @@ fn compile_macros() { t.compile_fail("tests/trybuild/simple-fail.rs"); t.pass("tests/trybuild/route-ok.rs"); - - test_route_duplicate_unexpected_method(&t); - test_route_missing_method(&t) -} - -#[rustversion::stable(1.42)] -fn test_route_missing_method(t: &trybuild::TestCases) { - t.compile_fail("tests/trybuild/route-missing-method-fail-msrv.rs"); -} - -#[rustversion::not(stable(1.42))] -#[rustversion::not(nightly)] -fn test_route_missing_method(t: &trybuild::TestCases) { t.compile_fail("tests/trybuild/route-missing-method-fail.rs"); -} - -#[rustversion::nightly] -fn test_route_missing_method(_t: &trybuild::TestCases) {} - -// FIXME: Re-test them on nightly once rust-lang/rust#77993 is fixed. -#[rustversion::not(nightly)] -fn test_route_duplicate_unexpected_method(t: &trybuild::TestCases) { t.compile_fail("tests/trybuild/route-duplicate-method-fail.rs"); t.compile_fail("tests/trybuild/route-unexpected-method-fail.rs"); } -#[rustversion::nightly] -fn test_route_duplicate_unexpected_method(_t: &trybuild::TestCases) {} +// #[rustversion::not(nightly)] +// fn skip_on_nightly(t: &trybuild::TestCases) { +// +// } + +// #[rustversion::nightly] +// fn skip_on_nightly(_t: &trybuild::TestCases) {} diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs b/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs deleted file mode 120000 index 70a5c0e33..000000000 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.rs +++ /dev/null @@ -1 +0,0 @@ -route-missing-method-fail.rs \ No newline at end of file diff --git a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr b/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr deleted file mode 100644 index d3e2b60ae..000000000 --- a/actix-web-codegen/tests/trybuild/route-missing-method-fail-msrv.stderr +++ /dev/null @@ -1,11 +0,0 @@ -error: The #[route(..)] macro requires at least one `method` attribute - --> $DIR/route-missing-method-fail-msrv.rs:3:1 - | -3 | #[route("/")] - | ^^^^^^^^^^^^^ - -error[E0425]: cannot find value `index` in this scope - --> $DIR/route-missing-method-fail-msrv.rs:12:49 - | -12 | let srv = test::start(|| App::new().service(index)); - | ^^^^^ not found in this scope diff --git a/awc/README.md b/awc/README.md index b97d4fa00..972a80140 100644 --- a/awc/README.md +++ b/awc/README.md @@ -13,7 +13,7 @@ - [API Documentation](https://docs.rs/awc) - [Example Project](https://github.com/actix/examples/tree/HEAD/awc_https) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Minimum Supported Rust Version (MSRV): 1.42.0 +- Minimum Supported Rust Version (MSRV): 1.46.0 ## Example ```rust diff --git a/src/lib.rs b/src/lib.rs index 8246c8286..88eae44bf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,7 @@ //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) //! * Supports [Actix actor framework](https://github.com/actix/actix) -//! * Runs on stable Rust 1.42+ +//! * Runs on stable Rust 1.46+ //! //! ## Crate Features //! @@ -65,7 +65,7 @@ //! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` //! * `secure-cookies` - secure cookies support -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] #![allow(clippy::needless_doctest_main, clippy::type_complexity)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] From 20b46cdaf907aeb7253b63458307b564209d6210 Mon Sep 17 00:00:00 2001 From: Ibraheem Ahmed Date: Mon, 28 Dec 2020 16:04:02 -0500 Subject: [PATCH 079/187] format factory_tuple macro invocations (#1859) --- src/handler.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/handler.rs b/src/handler.rs index b928939a5..d4b755e57 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -201,14 +201,14 @@ macro_rules! factory_tuple ({ $(($n:tt, $T:ident)),+} => { mod m { use super::*; -factory_tuple!((0, A)); -factory_tuple!((0, A), (1, B)); -factory_tuple!((0, A), (1, B), (2, C)); -factory_tuple!((0, A), (1, B), (2, C), (3, D)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); -factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); + factory_tuple!((0, A)); + factory_tuple!((0, A), (1, B)); + factory_tuple!((0, A), (1, B), (2, C)); + factory_tuple!((0, A), (1, B), (2, C), (3, D)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); + factory_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); } From 102bb8f9abf9fdc61d0c63b3ab183bd4e186ef47 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 29 Dec 2020 00:22:13 +0000 Subject: [PATCH 080/187] update dot dep graphs --- docs/graphs/net-only.dot | 6 +----- docs/graphs/web-focus.dot | 42 +++++++++++++++++++-------------------- 2 files changed, 21 insertions(+), 27 deletions(-) diff --git a/docs/graphs/net-only.dot b/docs/graphs/net-only.dot index 0eebf2a6f..9488f3fe7 100644 --- a/docs/graphs/net-only.dot +++ b/docs/graphs/net-only.dot @@ -2,12 +2,10 @@ digraph { subgraph cluster_net { label="actix/actix-net"; "actix-codec" - "actix-connect" "actix-macros" "actix-rt" "actix-server" "actix-service" - "actix-testing" "actix-threadpool" "actix-tls" "actix-tracing" @@ -17,9 +15,7 @@ digraph { "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } "actix-tracing" -> { "actix-service" } - "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } - "actix-testing" -> { "actix-rt" "actix-macros" "actix-server" "actix-service" } + "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } "actix-rt" -> { "actix-macros" "actix-threadpool" } - "actix-connect" -> { "actix-service" "actix-codec" "actix-utils" "actix-rt" } } diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 17228fe62..55a82bb41 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -2,31 +2,29 @@ digraph { subgraph cluster_web { label="actix/actix-web" "awc" - "web" - "files" - "http" - "multipart" - "web-actors" - "codegen" - "http-test" + "actix-web" + "actix-files" + "actix-http" + "actix-multipart" + "actix-web-actors" + "actix-web-codegen" + "actix-http-test" } - "web" -> { "codec" "service" "utils" "router" "rt" "server" "testing" "macros" "threadpool" "tls" "codegen" "http" "awc" } - "awc" -> { "codec" "service" "http" "rt" } - "web-actors" -> { "actix" "web" "http" "codec" } - "multipart" -> { "web" "service" "utils" } - "http" -> { "service" "codec" "connect" "utils" "rt" "threadpool" } - "http" -> { "actix" "tls" }[color=blue] // optional - "files" -> { "web" } - "http-test" -> { "service" "codec" "connect" "utils" "rt" "server" "testing" "awc" } + "actix-web" -> { "actix-codec" "actix-service" "actix-utils" "actix-router" "actix-rt" "actix-server" "macros" "threadpool" "actix-tls" "actix-web-codegen" "actix-http" "awc" } + "awc" -> { "actix-codec" "actix-service" "actix-http" "actix-rt" } + "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } + "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } + "actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" } + "actix-http" -> { "actix" "actix-tls" }[color=blue] // optional + "actix-files" -> { "actix-web" } + "actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" } // net - "utils" -> { "service" "rt" "codec" } - "tracing" -> { "service" } - "tls" -> { "service" "codec" "utils" } - "testing" -> { "rt" "macros" "server" "service" } - "server" -> { "service" "rt" "codec" "utils" } - "rt" -> { "macros" "threadpool" } - "connect" -> { "service" "codec" "utils" "rt" } + "actix-utils" -> { "actix-service" "actix-rt" "actix-codec" } + "actix-tracing" -> { "actix-service" } + "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } + "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } + "actix-rt" -> { "macros" "threadpool" } } From 522c9a5ea6eb45c1350a20b4d34d62908f736a78 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 31 Dec 2020 03:24:18 +0000 Subject: [PATCH 081/187] update CoC text --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 62ee50243..956bb3741 100644 --- a/README.md +++ b/README.md @@ -107,5 +107,5 @@ at your option. ## Code of Conduct -Contribution to the actix-web crate is organized under the terms of the Contributor Covenant, the -maintainers of Actix web, promises to intervene to uphold that code of conduct. +Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. +The Actix team promises to intervene to uphold that code of conduct. From 3beb4cf2da01440c2b6454558dba70274305dd18 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 2 Jan 2021 07:18:25 +0800 Subject: [PATCH 082/187] replace tinyvec with smallvec (#1866) --- Cargo.toml | 2 +- src/app_service.rs | 2 -- src/request.rs | 18 ++++++++++++------ 3 files changed, 13 insertions(+), 9 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 6ed327f56..e33ff844b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -107,7 +107,7 @@ time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { package = "openssl", version = "0.10", optional = true } rust-tls = { package = "rustls", version = "0.18.0", optional = true } -tinyvec = { version = "1", features = ["alloc"] } +smallvec = "1.6" [dev-dependencies] actix = "0.10.0" diff --git a/src/app_service.rs b/src/app_service.rs index e5f8dd9cf..4452778df 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -10,7 +10,6 @@ use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory}; use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture}; -use tinyvec::tiny_vec; use crate::config::{AppConfig, AppService}; use crate::data::{DataFactory, FnDataFactory}; @@ -246,7 +245,6 @@ where inner.path.reset(); inner.head = head; inner.payload = payload; - inner.app_data = tiny_vec![self.data.clone()]; req } else { HttpRequest::new( diff --git a/src/request.rs b/src/request.rs index bd4bbbf58..432134cd7 100644 --- a/src/request.rs +++ b/src/request.rs @@ -6,7 +6,7 @@ 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 tinyvec::TinyVec; +use smallvec::SmallVec; use crate::config::AppConfig; use crate::error::UrlGenerationError; @@ -22,7 +22,7 @@ pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, pub(crate) payload: Payload, - pub(crate) app_data: TinyVec<[Rc; 4]>, + pub(crate) app_data: SmallVec<[Rc; 4]>, rmap: Rc, config: AppConfig, pool: &'static HttpRequestPool, @@ -39,7 +39,7 @@ impl HttpRequest { app_data: Rc, pool: &'static HttpRequestPool, ) -> HttpRequest { - let mut data = TinyVec::<[Rc; 4]>::new(); + let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); HttpRequest(Rc::new(HttpRequestInner { @@ -277,10 +277,16 @@ impl HttpMessage for HttpRequest { impl Drop for HttpRequest { fn drop(&mut self) { // if possible, contribute to current worker's HttpRequest allocation pool - if Rc::strong_count(&self.0) == 1 { - let v = &mut self.0.pool.0.borrow_mut(); + + // This relies on no Weak exists anywhere.(There is none) + if let Some(inner) = Rc::get_mut(&mut self.0) { + let v = &mut inner.pool.0.borrow_mut(); if v.len() < 128 { - self.extensions_mut().clear(); + // clear additional app_data and keep the root one for reuse. + inner.app_data.truncate(1); + // inner is borrowed mut here. get head's Extension mutably + // to reduce borrow check + inner.head.extensions.get_mut().clear(); v.push(self.0.clone()); } } From a1b00b2cd07362fa2e219c3256d4c0ca2eabb6b4 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 2 Jan 2021 00:12:18 +0000 Subject: [PATCH 083/187] change unreleased year --- CHANGES.md | 3 ++- actix-files/CHANGES.md | 2 +- actix-http-test/CHANGES.md | 2 +- actix-http/CHANGES.md | 3 ++- actix-multipart/CHANGES.md | 2 +- actix-web-actors/CHANGES.md | 3 ++- actix-web-codegen/CHANGES.md | 2 +- awc/CHANGES.md | 2 +- 8 files changed, 11 insertions(+), 8 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index fa56acc17..2e886e0f5 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ### Changed * Bumped `rand` to `0.8` * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] @@ -12,6 +12,7 @@ [#1812]: https://github.com/actix/actix-web/pull/1812 [#1852]: https://github.com/actix/actix-web/pull/1852 + ## 3.3.2 - 2020-12-01 ### Fixed * Removed an occasional `unwrap` on `None` panic in `NormalizePathNormalization`. [#1762] diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 49768419b..f992e5fdb 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx * `HttpRange::parse` now has its own error type. diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 835b75ddc..535766373 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ## 2.1.0 - 2020-11-25 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c43246bb0..1d0cc3d31 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,9 +1,10 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ### Changed * Bumped `rand` to `0.8` + ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 446ca5ad2..0d13d5015 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx * Fix multipart consuming payload before header checks #1513 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 9df0df159..0d830fe64 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,8 +1,9 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx * Upgrade `pin-project` to `1.0`. + ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 1ab51f924..a7675d9dd 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ## 0.4.0 - 2020-09-20 diff --git a/awc/CHANGES.md b/awc/CHANGES.md index e4f801bbe..ee795fccb 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2020-xx-xx +## Unreleased - 2021-xx-xx ### Changed * Bumped `rand` to `0.8` From ad608aa64ef56c048c0b691ebe379224747a6902 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 3 Jan 2021 03:40:31 +0800 Subject: [PATCH 084/187] optimize Resource and Scope service call (#1867) --- src/resource.rs | 21 ++++++++++----------- src/scope.rs | 13 +++++++------ 2 files changed, 17 insertions(+), 17 deletions(-) diff --git a/src/resource.rs b/src/resource.rs index f6046d652..29a7daa78 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -11,7 +11,7 @@ use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; @@ -524,10 +524,7 @@ impl Service for ResourceService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either< - Ready>, - LocalBoxFuture<'static, Result>, - >; + type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -539,20 +536,22 @@ impl Service for ResourceService { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - return Either::Right(route.call(req)); + return route.call(req); } } if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - Either::Right(default.call(req)) + default.call(req) } else { let req = req.into_parts().0; - Either::Left(ok(ServiceResponse::new( - req, - Response::MethodNotAllowed().finish(), - ))) + Box::pin(async { + Ok(ServiceResponse::new( + req, + Response::MethodNotAllowed().finish(), + )) + }) } } } diff --git a/src/scope.rs b/src/scope.rs index 681d142be..ce8d94159 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -11,7 +11,7 @@ use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, }; -use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; use crate::config::ServiceConfig; use crate::data::Data; @@ -28,7 +28,6 @@ use crate::service::{ type Guards = Vec>; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxedResponse = LocalBoxFuture<'static, Result>; /// Resources scope. /// @@ -606,7 +605,7 @@ impl Service for ScopeService { type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type Future = Either>>; + type Future = LocalBoxFuture<'static, Result>; fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) @@ -628,15 +627,17 @@ impl Service for ScopeService { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - Either::Left(srv.call(req)) + srv.call(req) } else if let Some(ref mut default) = self.default { if let Some(ref data) = self.data { req.add_data_container(data.clone()); } - Either::Left(default.call(req)) + default.call(req) } else { let req = req.into_parts().0; - Either::Right(ok(ServiceResponse::new(req, Response::NotFound().finish()))) + Box::pin(async { + Ok(ServiceResponse::new(req, Response::NotFound().finish())) + }) } } } From 1f202d40e48120b413e191df36431b45a9b0fccd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 3 Jan 2021 16:53:01 +0000 Subject: [PATCH 085/187] optimize write_camel_case in h1 encoder (#1868) --- actix-http/Cargo.toml | 2 +- actix-http/benches/content-length.rs | 291 ------------------------- actix-http/benches/write-camel-case.rs | 89 ++++++++ actix-http/src/h1/encoder.rs | 39 ++-- 4 files changed, 110 insertions(+), 311 deletions(-) delete mode 100644 actix-http/benches/content-length.rs create mode 100644 actix-http/benches/write-camel-case.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7cf344487..50690537a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ open-ssl = { version="0.10", package = "openssl" } rust-tls = { version="0.18", package = "rustls" } [[bench]] -name = "content-length" +name = "write-camel-case" harness = false [[bench]] diff --git a/actix-http/benches/content-length.rs b/actix-http/benches/content-length.rs deleted file mode 100644 index 18a55a33b..000000000 --- a/actix-http/benches/content-length.rs +++ /dev/null @@ -1,291 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; - -use bytes::BytesMut; - -// benchmark sending all requests at the same time -fn bench_write_content_length(c: &mut Criterion) { - let mut group = c.benchmark_group("write_content_length"); - - let sizes = [ - 0, 1, 11, 83, 101, 653, 1001, 6323, 10001, 56329, 100001, 123456, 98724245, - 4294967202, - ]; - - for i in sizes.iter() { - group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _original::write_content_length(i, &mut b) - }) - }); - - group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _new::write_content_length(i, &mut b) - }) - }); - - group.bench_with_input(BenchmarkId::new("itoa", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _itoa::write_content_length(i, &mut b) - }) - }); - } - - group.finish(); -} - -criterion_group!(benches, bench_write_content_length); -criterion_main!(benches); - -mod _itoa { - use bytes::{BufMut, BytesMut}; - - pub fn write_content_length(n: usize, bytes: &mut BytesMut) { - if n == 0 { - bytes.put_slice(b"\r\ncontent-length: 0\r\n"); - return; - } - - let mut buf = itoa::Buffer::new(); - - bytes.put_slice(b"\r\ncontent-length: "); - bytes.put_slice(buf.format(n).as_bytes()); - bytes.put_slice(b"\r\n"); - } -} - -mod _new { - use bytes::{BufMut, BytesMut}; - - const DIGITS_START: u8 = b'0'; - - /// NOTE: bytes object has to contain enough space - pub fn write_content_length(n: usize, bytes: &mut BytesMut) { - if n == 0 { - bytes.put_slice(b"\r\ncontent-length: 0\r\n"); - return; - } - - bytes.put_slice(b"\r\ncontent-length: "); - - if n < 10 { - bytes.put_u8(DIGITS_START + (n as u8)); - } else if n < 100 { - let n = n as u8; - - let d10 = n / 10; - let d1 = n % 10; - - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 1000 { - let n = n as u16; - - let d100 = (n / 100) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 10_000 { - let n = n as u16; - - let d1000 = (n / 1000) as u8; - let d100 = ((n / 100) % 10) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d1000); - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 100_000 { - let n = n as u32; - - let d10000 = (n / 10000) as u8; - let d1000 = ((n / 1000) % 10) as u8; - let d100 = ((n / 100) % 10) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d10000); - bytes.put_u8(DIGITS_START + d1000); - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else if n < 1_000_000 { - let n = n as u32; - - let d100000 = (n / 100000) as u8; - let d10000 = ((n / 10000) % 10) as u8; - let d1000 = ((n / 1000) % 10) as u8; - let d100 = ((n / 100) % 10) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d100000); - bytes.put_u8(DIGITS_START + d10000); - bytes.put_u8(DIGITS_START + d1000); - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - } else { - write_usize(n, bytes); - } - - bytes.put_slice(b"\r\n"); - } - - fn write_usize(n: usize, bytes: &mut BytesMut) { - let mut n = n; - - // 20 chars is max length of a usize (2^64) - // digits will be added to the buffer from lsd to msd - let mut buf = BytesMut::with_capacity(20); - - while n > 9 { - // "pop" the least-significant digit - let lsd = (n % 10) as u8; - - // remove the lsd from n - n = n / 10; - - buf.put_u8(DIGITS_START + lsd); - } - - // put msd to result buffer - bytes.put_u8(DIGITS_START + (n as u8)); - - // put, in reverse (msd to lsd), remaining digits to buffer - for i in (0..buf.len()).rev() { - bytes.put_u8(buf[i]); - } - } -} - -mod _original { - use std::{mem, ptr, slice}; - - use bytes::{BufMut, BytesMut}; - - const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ - 2021222324252627282930313233343536373839\ - 4041424344454647484950515253545556575859\ - 6061626364656667686970717273747576777879\ - 8081828384858687888990919293949596979899"; - - /// NOTE: bytes object has to contain enough space - pub fn write_content_length(mut n: usize, bytes: &mut BytesMut) { - if n < 10 { - let mut buf: [u8; 21] = [ - b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', - b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'\r', b'\n', - ]; - buf[18] = (n as u8) + b'0'; - bytes.put_slice(&buf); - } else if n < 100 { - let mut buf: [u8; 22] = [ - b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', - b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'\r', b'\n', - ]; - let d1 = n << 1; - unsafe { - ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().add(d1), - buf.as_mut_ptr().offset(18), - 2, - ); - } - bytes.put_slice(&buf); - } else if n < 1000 { - let mut buf: [u8; 23] = [ - b'\r', b'\n', b'c', b'o', b'n', b't', b'e', b'n', b't', b'-', b'l', - b'e', b'n', b'g', b't', b'h', b':', b' ', b'0', b'0', b'0', b'\r', - b'\n', - ]; - // decode 2 more chars, if > 2 chars - let d1 = (n % 100) << 1; - n /= 100; - unsafe { - ptr::copy_nonoverlapping( - DEC_DIGITS_LUT.as_ptr().add(d1), - buf.as_mut_ptr().offset(19), - 2, - ) - }; - - // decode last 1 - buf[18] = (n as u8) + b'0'; - - bytes.put_slice(&buf); - } else { - bytes.put_slice(b"\r\ncontent-length: "); - convert_usize(n, bytes); - } - } - - pub(crate) fn convert_usize(mut n: usize, bytes: &mut BytesMut) { - let mut curr: isize = 39; - let mut buf: [u8; 41] = unsafe { mem::MaybeUninit::uninit().assume_init() }; - buf[39] = b'\r'; - buf[40] = b'\n'; - let buf_ptr = buf.as_mut_ptr(); - let lut_ptr = DEC_DIGITS_LUT.as_ptr(); - - // eagerly decode 4 characters at a time - while n >= 10_000 { - let rem = (n % 10_000) as isize; - n /= 10_000; - - let d1 = (rem / 100) << 1; - let d2 = (rem % 100) << 1; - curr -= 4; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - ptr::copy_nonoverlapping( - lut_ptr.offset(d2), - buf_ptr.offset(curr + 2), - 2, - ); - } - } - - // if we reach here numbers are <= 9999, so at most 4 chars long - let mut n = n as isize; // possibly reduce 64bit math - - // decode 2 more chars, if > 2 chars - if n >= 100 { - let d1 = (n % 100) << 1; - n /= 100; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - } - } - - // decode last 1 or 2 chars - if n < 10 { - curr -= 1; - unsafe { - *buf_ptr.offset(curr) = (n as u8) + b'0'; - } - } else { - let d1 = n << 1; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1), buf_ptr.offset(curr), 2); - } - } - - unsafe { - bytes.extend_from_slice(slice::from_raw_parts( - buf_ptr.offset(curr), - 41 - curr as usize, - )); - } - } -} diff --git a/actix-http/benches/write-camel-case.rs b/actix-http/benches/write-camel-case.rs new file mode 100644 index 000000000..fa4930eb9 --- /dev/null +++ b/actix-http/benches/write-camel-case.rs @@ -0,0 +1,89 @@ +use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; + +fn bench_write_camel_case(c: &mut Criterion) { + let mut group = c.benchmark_group("write_camel_case"); + + let names = ["connection", "Transfer-Encoding", "transfer-encoding"]; + + for &i in &names { + let bts = i.as_bytes(); + + group.bench_with_input(BenchmarkId::new("Original", i), bts, |b, bts| { + b.iter(|| { + let mut buf = black_box([0; 24]); + _original::write_camel_case(black_box(bts), &mut buf) + }); + }); + + group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| { + b.iter(|| { + let mut buf = black_box([0; 24]); + _new::write_camel_case(black_box(bts), &mut buf) + }); + }); + } + + group.finish(); +} + +criterion_group!(benches, bench_write_camel_case); +criterion_main!(benches); + +mod _new { + pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) { + // first copy entire (potentially wrong) slice to output + buffer[..value.len()].copy_from_slice(value); + + let mut iter = value.iter(); + + // first character should be uppercase + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[0] = c & 0b1101_1111; + } + + // track 1 ahead of the current position since that's the location being assigned to + let mut index = 2; + + // remaining characters after hyphens should also be uppercase + while let Some(&c) = iter.next() { + if c == b'-' { + // advance iter by one and uppercase if needed + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[index] = c & 0b1101_1111; + } + } + + index += 1; + } + } +} + +mod _original { + pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) { + let mut index = 0; + let key = value; + let mut key_iter = key.iter(); + + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer[index] = *c ^ b' '; + index += 1; + } + } else { + return; + } + + while let Some(c) = key_iter.next() { + buffer[index] = *c; + index += 1; + if *c == b'-' { + if let Some(c) = key_iter.next() { + if *c >= b'a' && *c <= b'z' { + buffer[index] = *c ^ b' '; + index += 1; + } + } + } + } + } +} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index b7648eff1..2ec4899a7 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -532,30 +532,29 @@ unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) { } fn write_camel_case(value: &[u8], buffer: &mut [u8]) { - let mut index = 0; - let key = value; - let mut key_iter = key.iter(); + // first copy entire (potentially wrong) slice to output + buffer[..value.len()].copy_from_slice(value); - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } - } else { - return; + let mut iter = value.iter(); + + // first character should be uppercase + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[0] = c & 0b1101_1111; } - while let Some(c) = key_iter.next() { - buffer[index] = *c; - index += 1; - if *c == b'-' { - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } + // track 1 ahead of the current position since that's the location being assigned to + let mut index = 2; + + // remaining characters after hyphens should also be uppercase + while let Some(&c) = iter.next() { + if c == b'-' { + // advance iter by one and uppercase if needed + if let Some(c @ b'a'..=b'z') = iter.next() { + buffer[index] = c & 0b1101_1111; } } + + index += 1; } } @@ -604,6 +603,8 @@ mod tests { ); let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); + eprintln!("{}", &data); + assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Content-Type: plain/text\r\n")); From 32de9f8840a39d42f070a1a87b26ed3d32a80fb5 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 4 Jan 2021 07:47:04 +0800 Subject: [PATCH 086/187] Tokio 1.0 (#1813) Co-authored-by: Rob Ede --- CHANGES.md | 7 +- Cargo.toml | 32 ++-- actix-files/CHANGES.md | 3 + actix-files/Cargo.toml | 6 +- actix-files/src/files.rs | 9 +- actix-files/src/service.rs | 3 +- actix-http-test/CHANGES.md | 3 + actix-http-test/Cargo.toml | 19 ++- actix-http-test/src/lib.rs | 15 +- actix-http/CHANGES.md | 14 +- actix-http/Cargo.toml | 36 ++--- actix-http/src/builder.rs | 48 +++--- actix-http/src/client/connection.rs | 34 +--- actix-http/src/client/connector.rs | 87 +++++----- actix-http/src/client/error.rs | 30 ++-- actix-http/src/client/h1proto.rs | 17 +- actix-http/src/client/pool.rs | 32 ++-- actix-http/src/cloneable.rs | 11 +- actix-http/src/config.rs | 12 +- actix-http/src/error.rs | 11 +- actix-http/src/h1/dispatcher.rs | 108 +++++++------ actix-http/src/h1/encoder.rs | 6 +- actix-http/src/h1/expect.rs | 8 +- actix-http/src/h1/service.rs | 107 +++++++------ actix-http/src/h1/upgrade.rs | 15 +- actix-http/src/h2/dispatcher.rs | 14 +- actix-http/src/h2/service.rs | 81 +++++----- actix-http/src/header/shared/httpdate.rs | 3 +- actix-http/src/request.rs | 8 +- actix-http/src/service.rs | 193 ++++++++++++----------- actix-http/src/test.rs | 22 ++- actix-http/src/ws/dispatcher.rs | 10 +- actix-http/tests/test_client.rs | 7 +- actix-http/tests/test_openssl.rs | 14 +- actix-http/tests/test_server.rs | 4 +- actix-http/tests/test_ws.rs | 8 +- actix-multipart/CHANGES.md | 3 + actix-multipart/Cargo.toml | 10 +- actix-web-actors/CHANGES.md | 4 +- actix-web-actors/Cargo.toml | 17 +- actix-web-actors/src/context.rs | 2 +- actix-web-actors/src/ws.rs | 2 +- actix-web-codegen/Cargo.toml | 4 +- actix-web-codegen/tests/test_macro.rs | 12 +- awc/CHANGES.md | 6 +- awc/Cargo.toml | 24 +-- awc/src/builder.rs | 2 +- awc/src/connect.rs | 17 +- awc/src/sender.rs | 7 +- awc/tests/test_client.rs | 8 +- awc/tests/test_connector.rs | 2 +- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- benches/server.rs | 14 +- benches/service.rs | 25 +-- src/app.rs | 21 +-- src/app_service.rs | 33 ++-- src/config.rs | 4 +- src/handler.rs | 10 +- src/middleware/compress.rs | 16 +- src/middleware/condition.rs | 18 +-- src/middleware/defaultheaders.rs | 14 +- src/middleware/errhandlers.rs | 12 +- src/middleware/logger.rs | 16 +- src/middleware/normalize.rs | 12 +- src/resource.rs | 38 +++-- src/route.rs | 38 ++--- src/scope.rs | 30 ++-- src/server.rs | 31 ++-- src/service.rs | 6 +- src/test.rs | 54 +++---- src/types/json.rs | 2 +- src/web.rs | 1 - tests/test_httpserver.rs | 51 +++--- tests/test_server.rs | 7 +- 75 files changed, 788 insertions(+), 826 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 2e886e0f5..32f444ec1 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,10 +2,15 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8` +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. +* Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] * MSRV is now 1.46.0. +[#1813]: https://github.com/actix/actix-web/pull/1813 + + ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/Cargo.toml b/Cargo.toml index e33ff844b..165004447 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"] secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["actix-tls/openssl", "awc/openssl", "open-ssl"] +openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"] # rustls -rustls = ["actix-tls/rustls", "awc/rustls", "rust-tls"] +rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"] [[example]] name = "basic" @@ -73,27 +73,25 @@ name = "client" required-features = ["rustls"] [dependencies] -actix-codec = "0.3.0" -actix-service = "1.0.6" -actix-utils = "2.0.0" -actix-router = "0.2.4" -actix-rt = "1.1.1" -actix-server = "1.0.0" -actix-testing = "1.0.0" +actix-codec = "0.4.0-beta.1" actix-macros = "0.1.0" +actix-router = "0.2.4" +actix-rt = "2.0.0-beta.1" +actix-server = "2.0.0-beta.2" +actix-service = "2.0.0-beta.2" +actix-utils = "3.0.0-beta.1" actix-threadpool = "0.3.1" -actix-tls = "2.0.0" +actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } actix-web-codegen = "0.4.0" actix-http = "2.2.0" awc = { version = "2.0.3", default-features = false } -bytes = "0.5.3" +bytes = "1" derive_more = "0.99.5" encoding_rs = "0.8" -futures-channel = { version = "0.3.5", default-features = false } -futures-core = { version = "0.3.5", default-features = false } -futures-util = { version = "0.3.5", default-features = false } +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false } fxhash = "0.2.1" log = "0.4" mime = "0.3" @@ -106,12 +104,12 @@ serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } url = "2.1" open-ssl = { package = "openssl", version = "0.10", optional = true } -rust-tls = { package = "rustls", version = "0.18.0", optional = true } +rust-tls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] -actix = "0.10.0" -actix-http = { version = "2.1.0", features = ["actors"] } +actix = "0.11.0-beta.1" +actix-http = { version = "2.2.0", features = ["actors"] } rand = "0.8" env_logger = "0.8" serde_derive = "1.0" diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index f992e5fdb..6dcf4f66f 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased - 2021-xx-xx * `HttpRange::parse` now has its own error type. +* Update `bytes` to `1.0`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 0.5.0 - 2020-12-26 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b67abb1c1..17e1a4888 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,9 +18,9 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "3.0.0", default-features = false } -actix-service = "1.0.6" +actix-service = "2.0.0-beta.2" bitflags = "1" -bytes = "0.5.3" +bytes = "1" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } derive_more = "0.99.5" @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "1.0.0" +actix-rt = "2.0.0-beta.1" actix-web = "3.0.0" diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index d0cac6aa4..98dd26880 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -1,6 +1,6 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; -use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; +use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_web::{ dev::{ AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse, @@ -201,10 +201,10 @@ impl Files { /// Sets default handler which is used when no matched file could be found. pub fn default_handler(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -241,8 +241,7 @@ impl HttpServiceFactory for Files { } } -impl ServiceFactory for Files { - type Request = ServiceRequest; +impl ServiceFactory for Files { type Response = ServiceResponse; type Error = Error; type Config = (); diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index dc4f2bd2c..1e3d64a0d 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -57,8 +57,7 @@ impl fmt::Debug for FilesService { } } -impl Service for FilesService { - type Request = ServiceRequest; +impl Service for FilesService { type Response = ServiceResponse; type Error = Error; type Future = FilesServiceFuture; diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 535766373..6ed6a0603 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Update `bytes` to `1.0`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.1.0 - 2020-11-25 diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 8b23bef1c..910fbab73 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,19 +29,18 @@ default = [] openssl = ["open-ssl", "awc/openssl"] [dependencies] -actix-service = "1.0.6" -actix-codec = "0.3.0" -actix-connect = "2.0.0" -actix-utils = "2.0.0" -actix-rt = "1.1.1" -actix-server = "1.0.0" -actix-testing = "1.0.0" +actix-service = "2.0.0-beta.2" +actix-codec = "0.4.0-beta.1" +actix-tls = "3.0.0-beta.2" +actix-utils = "3.0.0-beta.1" +actix-rt = "2.0.0-beta.1" +actix-server = "2.0.0-beta.2" awc = "2.0.0" base64 = "0.13" -bytes = "0.5.3" -futures-core = { version = "0.3.5", default-features = false } -http = "0.2.0" +bytes = "1" +futures-core = { version = "0.3.7", default-features = false } +http = "0.2.2" log = "0.4" socket2 = "0.3" serde = "1.0" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 3ab3f8a0d..4fd74d6eb 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -16,8 +16,6 @@ use futures_core::stream::Stream; use http::Method; use socket2::{Domain, Protocol, Socket, Type}; -pub use actix_testing::*; - /// Start test server /// /// `TestServer` is very simple test server that simplify process of writing @@ -65,13 +63,16 @@ pub async fn test_server_with_addr>( let sys = System::new("actix-test-server"); let local_addr = tcp.local_addr().unwrap(); - Server::build() + let srv = Server::build() .listen("test", tcp, factory)? .workers(1) - .disable_signals() - .start(); + .disable_signals(); + + sys.block_on(async { + srv.start(); + tx.send((System::current(), local_addr)).unwrap(); + }); - tx.send((System::current(), local_addr)).unwrap(); sys.run() }); @@ -105,7 +106,7 @@ pub async fn test_server_with_addr>( Client::builder().connector(connector).finish() }; - actix_connect::start_default_resolver().await.unwrap(); + actix_tls::connect::start_default_resolver().await.unwrap(); TestServer { addr, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 1d0cc3d31..147285ddf 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,13 +2,25 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8` +* Bumped `rand` to `0.8`. +* Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Update `bytes` to `1.0`. [#1813] +* Update `h2` to `0.3`. [#1813] + + +[#1813]: https://github.com/actix/actix-web/pull/1813 ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new `on_connect_ext` technique. [#1857] +* Remove `ResponseError` impl for `actix::actors::resolver::ResolverError` + due to deprecate of resolver actor. [#1813] +* Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. + due to the removal of this type from `tokio-openssl` crate. openssl handshake + error would return as `ConnectError::SslError`. [#1813] +[#1813]: https://github.com/actix/actix-web/pull/1813 [#1857]: https://github.com/actix/actix-web/pull/1857 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 50690537a..e98bcf76d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -25,10 +25,10 @@ path = "src/lib.rs" default = [] # openssl -openssl = ["actix-tls/openssl", "actix-connect/openssl"] +openssl = ["actix-tls/openssl"] # rustls support -rustls = ["actix-tls/rustls", "actix-connect/rustls"] +rustls = ["actix-tls/rustls"] # enable compressison support compress = ["flate2", "brotli2"] @@ -40,29 +40,28 @@ secure-cookies = ["cookie/secure"] actors = ["actix"] [dependencies] -actix-service = "1.0.6" -actix-codec = "0.3.0" -actix-connect = "2.0.0" -actix-utils = "2.0.0" -actix-rt = "1.0.0" +actix-service = "2.0.0-beta.2" +actix-codec = "0.4.0-beta.1" +actix-utils = "3.0.0-beta.1" +actix-rt = "2.0.0-beta.1" actix-threadpool = "0.3.1" -actix-tls = { version = "2.0.0", optional = true } -actix = { version = "0.10.0", optional = true } +actix-tls = "3.0.0-beta.2" +actix = { version = "0.11.0-beta.1", optional = true } base64 = "0.13" bitflags = "1.2" -bytes = "0.5.3" +bytes = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } copyless = "0.1.4" derive_more = "0.99.2" either = "1.5.3" encoding_rs = "0.8" -futures-channel = { version = "0.3.5", default-features = false } -futures-core = { version = "0.3.5", default-features = false } -futures-util = { version = "0.3.5", default-features = false } +futures-channel = { version = "0.3.7", default-features = false } +futures-core = { version = "0.3.7", default-features = false } +futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } fxhash = "0.2.1" -h2 = "0.2.1" -http = "0.2.0" +h2 = "0.3.0" +http = "0.2.2" httparse = "1.3" indexmap = "1.3" itoa = "0.4" @@ -86,15 +85,14 @@ brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } [dev-dependencies] -actix-server = "1.0.1" -actix-connect = { version = "2.0.0", features = ["openssl"] } +actix-server = "2.0.0-beta.2" actix-http-test = { version = "2.0.0", features = ["openssl"] } -actix-tls = { version = "2.0.0", features = ["openssl"] } +actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.7" serde_derive = "1.0" open-ssl = { version="0.10", package = "openssl" } -rust-tls = { version="0.18", package = "rustls" } +rust-tls = { version="0.19", package = "rustls" } [[bench]] name = "write-camel-case" diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index df1b332c1..ecb4327df 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -19,7 +19,7 @@ use crate::{ConnectCallback, Extensions}; /// /// This type can be used to construct an instance of [`HttpService`] through a /// builder-like pattern. -pub struct HttpServiceBuilder> { +pub struct HttpServiceBuilder { keep_alive: KeepAlive, client_timeout: u64, client_disconnect: u64, @@ -28,15 +28,15 @@ pub struct HttpServiceBuilder> { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, S)>, + _t: PhantomData, } -impl HttpServiceBuilder> +impl HttpServiceBuilder where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, { /// Create instance of `ServiceConfigBuilder` pub fn new() -> Self { @@ -56,18 +56,18 @@ where impl HttpServiceBuilder where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, - ::Future: 'static, - X: ServiceFactory, + >::Future: 'static, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory), Response = ()>, + >::Future: 'static, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { /// Set server keep-alive setting. /// @@ -123,11 +123,11 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: F) -> HttpServiceBuilder where - F: IntoServiceFactory, - X1: ServiceFactory, + F: IntoServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -148,15 +148,11 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: F) -> HttpServiceBuilder where - F: IntoServiceFactory, - U1: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + F: IntoServiceFactory)>, + U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { HttpServiceBuilder { keep_alive: self.keep_alive, @@ -188,7 +184,7 @@ where pub fn h1(self, service: F) -> H1Service where B: MessageBody, - F: IntoServiceFactory, + F: IntoServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -211,11 +207,11 @@ where pub fn h2(self, service: F) -> H2Service where B: MessageBody + 'static, - F: IntoServiceFactory, + F: IntoServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, { let cfg = ServiceConfig::new( self.keep_alive, @@ -233,11 +229,11 @@ where pub fn finish(self, service: F) -> HttpService where B: MessageBody + 'static, - F: IntoServiceFactory, + F: IntoServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: '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 ec86dabb0..d22f2c7ac 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,10 +1,10 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{fmt, io, mem, time}; +use std::{fmt, io, time}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::{Buf, Bytes}; +use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use bytes::Bytes; use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready}; use h2::client::SendRequest; use pin_project::pin_project; @@ -223,23 +223,13 @@ where fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { match self.project() { EitherIoProj::A(val) => val.poll_read(cx, buf), EitherIoProj::B(val) => val.poll_read(cx, buf), } } - - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - match self { - EitherIo::A(ref val) => val.prepare_uninitialized_buffer(buf), - EitherIo::B(ref val) => val.prepare_uninitialized_buffer(buf), - } - } } impl AsyncWrite for EitherIo @@ -274,18 +264,4 @@ where EitherIoProj::B(val) => val.poll_shutdown(cx), } } - - fn poll_write_buf( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - buf: &mut U, - ) -> Poll> - where - Self: Sized, - { - match self.project() { - EitherIoProj::A(val) => val.poll_write_buf(cx, buf), - EitherIoProj::B(val) => val.poll_write_buf(cx, buf), - } - } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index e1aed6382..b638336f7 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -3,11 +3,11 @@ use std::marker::PhantomData; use std::time::Duration; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_connect::{ +use actix_rt::net::TcpStream; +use actix_service::{apply_fn, Service, ServiceExt}; +use actix_tls::connect::{ default_connector, Connect as TcpConnect, Connection as TcpConnection, }; -use actix_rt::net::TcpStream; -use actix_service::{apply_fn, Service}; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; @@ -18,10 +18,10 @@ use super::pool::{ConnectionPool, Protocol}; use super::Connect; #[cfg(feature = "openssl")] -use actix_connect::ssl::openssl::SslConnector as OpensslConnector; +use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; #[cfg(feature = "rustls")] -use actix_connect::ssl::rustls::ClientConfig; +use actix_tls::connect::ssl::rustls::ClientConfig; #[cfg(feature = "rustls")] use std::sync::Arc; @@ -62,9 +62,9 @@ impl Connector<(), ()> { #[allow(clippy::new_ret_no_self, clippy::let_unit_value)] pub fn new() -> Connector< impl Service< - Request = TcpConnect, + TcpConnect, Response = TcpConnection, - Error = actix_connect::ConnectError, + Error = actix_tls::connect::ConnectError, > + Clone, TcpStream, > { @@ -79,7 +79,7 @@ impl Connector<(), ()> { // Build Ssl connector with openssl, based on supplied alpn protocols #[cfg(feature = "openssl")] fn build_ssl(protocols: Vec>) -> SslConnector { - use actix_connect::ssl::openssl::SslMethod; + use actix_tls::connect::ssl::openssl::SslMethod; use bytes::{BufMut, BytesMut}; let mut alpn = BytesMut::with_capacity(20); @@ -102,7 +102,7 @@ impl Connector<(), ()> { config.set_protocols(&protocols); config .root_store - .add_server_trust_anchors(&actix_tls::rustls::TLS_SERVER_ROOTS); + .add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS); SslConnector::Rustls(Arc::new(config)) } @@ -117,9 +117,9 @@ impl Connector { where U1: AsyncRead + AsyncWrite + Unpin + fmt::Debug, T1: Service< - Request = TcpConnect, + TcpConnect, Response = TcpConnection, - Error = actix_connect::ConnectError, + Error = actix_tls::connect::ConnectError, > + Clone, { Connector { @@ -135,9 +135,9 @@ impl Connector where U: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, T: Service< - Request = TcpConnect, + TcpConnect, Response = TcpConnection, - Error = actix_connect::ConnectError, + Error = actix_tls::connect::ConnectError, > + Clone + 'static, { @@ -241,8 +241,8 @@ where /// its combinator chain. pub fn finish( self, - ) -> impl Service - + Clone { + ) -> impl Service + Clone + { #[cfg(not(any(feature = "openssl", feature = "rustls")))] { let connector = TimeoutService::new( @@ -268,11 +268,11 @@ where #[cfg(any(feature = "openssl", feature = "rustls"))] { const H2: &[u8] = b"h2"; - #[cfg(feature = "openssl")] - use actix_connect::ssl::openssl::OpensslConnector; - #[cfg(feature = "rustls")] - use actix_connect::ssl::rustls::{RustlsConnector, Session}; use actix_service::{boxed::service, pipeline}; + #[cfg(feature = "openssl")] + use actix_tls::connect::ssl::openssl::OpensslConnector; + #[cfg(feature = "rustls")] + use actix_tls::connect::ssl::rustls::{RustlsConnector, Session}; let ssl_service = TimeoutService::new( self.config.timeout, @@ -363,8 +363,7 @@ mod connect_impl { pub(crate) struct InnerConnector where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { pub(crate) tcp_pool: ConnectionPool, } @@ -372,8 +371,7 @@ mod connect_impl { impl Clone for InnerConnector where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -382,17 +380,15 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { - type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = Either< - as Service>::Future, + as Service>::Future, Ready, ConnectError>>, >; @@ -428,8 +424,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service, - T2: Service, + T1: Service, + T2: Service, { pub(crate) tcp_pool: ConnectionPool, pub(crate) ssl_pool: ConnectionPool, @@ -439,10 +435,8 @@ mod connect_impl { where Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service - + 'static, - T2: Service - + 'static, + T1: Service + 'static, + T2: Service + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -452,16 +446,13 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service - + 'static, - T2: Service - + 'static, + T1: Service + 'static, + T2: Service + 'static, { - type Request = Connect; type Response = EitherConnection; type Error = ConnectError; type Future = Either< @@ -491,18 +482,16 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseA where Io1: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { #[pin] - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseA where - T: Service - + 'static, + T: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { @@ -520,18 +509,16 @@ mod connect_impl { pub(crate) struct InnerConnectorResponseB where Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { #[pin] - fut: as Service>::Future, + fut: as Service>::Future, _t: PhantomData, } impl Future for InnerConnectorResponseB where - T: Service - + 'static, + T: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index ba697bca4..a5f1b2e8e 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -1,10 +1,10 @@ use std::io; -use actix_connect::resolver::ResolveError; +use actix_tls::connect::resolver::ResolveError; use derive_more::{Display, From}; #[cfg(feature = "openssl")] -use actix_connect::ssl::openssl::{HandshakeError, SslError}; +use actix_tls::accept::openssl::SslError; use crate::error::{Error, ParseError, ResponseError}; use crate::http::{Error as HttpError, StatusCode}; @@ -21,11 +21,6 @@ pub enum ConnectError { #[display(fmt = "{}", _0)] SslError(SslError), - /// SSL Handshake error - #[cfg(feature = "openssl")] - #[display(fmt = "{}", _0)] - SslHandshakeError(String), - /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] Resolver(ResolveError), @@ -57,25 +52,18 @@ pub enum ConnectError { impl std::error::Error for ConnectError {} -impl From for ConnectError { - fn from(err: actix_connect::ConnectError) -> ConnectError { +impl From for ConnectError { + fn from(err: actix_tls::connect::ConnectError) -> ConnectError { match err { - actix_connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), - actix_connect::ConnectError::NoRecords => ConnectError::NoRecords, - actix_connect::ConnectError::InvalidInput => panic!(), - actix_connect::ConnectError::Unresolved => ConnectError::Unresolved, - actix_connect::ConnectError::Io(e) => ConnectError::Io(e), + actix_tls::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), + actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords, + actix_tls::connect::ConnectError::InvalidInput => panic!(), + actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved, + actix_tls::connect::ConnectError::Io(e) => ConnectError::Io(e), } } } -#[cfg(feature = "openssl")] -impl From> for ConnectError { - fn from(err: HandshakeError) -> ConnectError { - ConnectError::SslHandshakeError(format!("{:?}", err)) - } -} - #[derive(Debug, Display, From)] pub enum InvalidUrl { #[display(fmt = "Missing url scheme")] diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 06cc05404..754c53968 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -1,10 +1,10 @@ use std::io::Write; use std::pin::Pin; use std::task::{Context, Poll}; -use std::{io, mem, time}; +use std::{io, time}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use bytes::buf::BufMutExt; +use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::future::poll_fn; @@ -204,18 +204,11 @@ where } impl AsyncRead for H1Connection { - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - self.io.as_ref().unwrap().prepare_uninitialized_buffer(buf) - } - fn poll_read( mut self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { Pin::new(&mut self.io.as_mut().unwrap()).poll_read(cx, buf) } } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index a8687dbeb..f9973a850 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -6,8 +6,8 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; -use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{delay_for, Delay}; +use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; +use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use actix_utils::task::LocalWaker; use bytes::Bytes; @@ -50,8 +50,7 @@ pub(crate) struct ConnectionPool(Rc>, Rc ConnectionPool where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { let connector_rc = Rc::new(RefCell::new(connector)); @@ -90,13 +89,11 @@ impl Drop for ConnectionPool { } } -impl Service for ConnectionPool +impl Service for ConnectionPool where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service - + 'static, + T: Service + 'static, { - type Request = Connect; type Response = IoConnection; type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; @@ -334,10 +331,11 @@ where } else { let mut io = conn.io; let mut buf = [0; 2]; + let mut read_buf = ReadBuf::new(&mut buf); if let ConnectionType::H1(ref mut s) = io { - match Pin::new(s).poll_read(cx, &mut buf) { + match Pin::new(s).poll_read(cx, &mut read_buf) { Poll::Pending => (), - Poll::Ready(Ok(n)) if n > 0 => { + Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { actix_rt::spawn(CloseConnection::new( @@ -387,9 +385,11 @@ where } } +#[pin_project::pin_project] struct CloseConnection { io: T, - timeout: Delay, + #[pin] + timeout: Sleep, } impl CloseConnection @@ -399,7 +399,7 @@ where fn new(io: T, timeout: Duration) -> Self { CloseConnection { io, - timeout: delay_for(timeout), + timeout: sleep(timeout), } } } @@ -411,11 +411,11 @@ where type Output = (); fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()> { - let this = self.get_mut(); + let this = self.project(); - match Pin::new(&mut this.timeout).poll(cx) { + match this.timeout.poll(cx) { Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => match Pin::new(&mut this.io).poll_shutdown(cx) { + Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) { Poll::Ready(_) => Poll::Ready(()), Poll::Pending => Poll::Pending, }, @@ -435,7 +435,7 @@ where impl Future for ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service, + T: Service, T::Future: 'static, { type Output = (); diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs index 0e77c455c..5f0b1ea28 100644 --- a/actix-http/src/cloneable.rs +++ b/actix-http/src/cloneable.rs @@ -10,22 +10,21 @@ use actix_service::Service; /// CloneableService might panic with some creative use of thread local storage. /// See https://github.com/actix/actix-web/issues/1295 for example #[doc(hidden)] -pub(crate) struct CloneableService(Rc>); +pub(crate) struct CloneableService(Rc>); -impl CloneableService { +impl CloneableService { pub(crate) fn new(service: T) -> Self { Self(Rc::new(RefCell::new(service))) } } -impl Clone for CloneableService { +impl Clone for CloneableService { fn clone(&self) -> Self { Self(self.0.clone()) } } -impl Service for CloneableService { - type Request = T::Request; +impl, Req> Service for CloneableService { type Response = T::Response; type Error = T::Error; type Future = T::Future; @@ -34,7 +33,7 @@ impl Service for CloneableService { self.0.borrow_mut().poll_ready(cx) } - fn call(&mut self, req: T::Request) -> Self::Future { + fn call(&mut self, req: Req) -> Self::Future { self.0.borrow_mut().call(req) } } diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index b314d4c99..1cd7e4aea 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -4,7 +4,7 @@ use std::rc::Rc; use std::time::Duration; use std::{fmt, net}; -use actix_rt::time::{delay_for, delay_until, Delay, Instant}; +use actix_rt::time::{sleep, sleep_until, Instant, Sleep}; use bytes::BytesMut; use futures_util::{future, FutureExt}; use time::OffsetDateTime; @@ -121,10 +121,10 @@ impl ServiceConfig { #[inline] /// Client timeout for first request. - pub fn client_timer(&self) -> Option { + pub fn client_timer(&self) -> Option { let delay_time = self.0.client_timeout; if delay_time != 0 { - Some(delay_until( + Some(sleep_until( self.0.timer.now() + Duration::from_millis(delay_time), )) } else { @@ -154,9 +154,9 @@ impl ServiceConfig { #[inline] /// Return keep-alive timer delay is configured. - pub fn keep_alive_timer(&self) -> Option { + pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(delay_until(self.0.timer.now() + ka)) + Some(sleep_until(self.0.timer.now() + ka)) } else { None } @@ -266,7 +266,7 @@ impl DateService { // periodic date update let s = self.clone(); - actix_rt::spawn(delay_for(Duration::from_millis(500)).then(move |_| { + actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| { s.0.reset(); future::ready(()) })); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 0ebd4c05c..03e5467c5 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -178,11 +178,7 @@ impl ResponseError for FormError {} #[cfg(feature = "openssl")] /// `InternalServerError` for `openssl::ssl::Error` -impl ResponseError for actix_connect::ssl::openssl::SslError {} - -#[cfg(feature = "openssl")] -/// `InternalServerError` for `openssl::ssl::HandshakeError` -impl ResponseError for actix_tls::openssl::HandshakeError {} +impl ResponseError for actix_tls::accept::openssl::SslError {} /// Return `BAD_REQUEST` for `de::value::Error` impl ResponseError for DeError { @@ -956,11 +952,6 @@ where /// This is supported on feature=`actors` only impl ResponseError for actix::MailboxError {} -#[cfg(feature = "actors")] -/// `InternalServerError` for `actix::ResolverError` -/// This is supported on feature=`actors` only -impl ResponseError for actix::actors::resolver::ResolverError {} - #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 91e208aac..41caea902 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed, FramedParts}; -use actix_rt::time::{delay_until, Delay, Instant}; +use actix_rt::time::{sleep_until, Instant, Sleep}; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; @@ -51,12 +51,12 @@ bitflags! { /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where - S: Service, + S: Service, S::Error: Into, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { #[pin] @@ -69,12 +69,12 @@ where #[pin_project(project = DispatcherStateProj)] enum DispatcherState where - S: Service, + S: Service, S::Error: Into, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { Normal(#[pin] InnerDispatcher), @@ -84,12 +84,12 @@ where #[pin_project(project = InnerDispatcherProj)] struct InnerDispatcher where - S: Service, + S: Service, S::Error: Into, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { service: CloneableService, @@ -106,7 +106,8 @@ where messages: VecDeque, ka_expire: Instant, - ka_timer: Option, + #[pin] + ka_timer: Option, io: Option, read_buf: BytesMut, @@ -123,8 +124,8 @@ enum DispatcherMessage { #[pin_project(project = StateProj)] enum State where - S: Service, - X: Service, + S: Service, + X: Service, B: MessageBody, { None, @@ -135,8 +136,8 @@ where impl State where - S: Service, - X: Service, + S: Service, + X: Service, B: MessageBody, { fn is_empty(&self) -> bool { @@ -166,13 +167,13 @@ impl PartialEq for PollResponse { impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { /// Create HTTP/1 dispatcher. @@ -205,7 +206,7 @@ where codec: Codec, config: ServiceConfig, read_buf: BytesMut, - timeout: Option, + timeout: Option, service: CloneableService, expect: CloneableService, upgrade: Option>, @@ -257,13 +258,13 @@ where impl InnerDispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn can_read(&self, cx: &mut Context<'_>) -> bool { @@ -660,7 +661,7 @@ where // shutdown timeout if this.flags.contains(Flags::SHUTDOWN) { if let Some(interval) = this.codec.config().client_disconnect_timer() { - *this.ka_timer = Some(delay_until(interval)); + this.ka_timer.set(Some(sleep_until(interval))); } else { this.flags.insert(Flags::READ_DISCONNECT); if let Some(mut payload) = this.payload.take() { @@ -673,12 +674,14 @@ where } } - match Pin::new(&mut this.ka_timer.as_mut().unwrap()).poll(cx) { + match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) { Poll::Ready(()) => { // if we get timeout during shutdown, drop connection if this.flags.contains(Flags::SHUTDOWN) { return Err(DispatchError::DisconnectTimeout); - } else if this.ka_timer.as_mut().unwrap().deadline() >= *this.ka_expire { + } else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline() + >= *this.ka_expire + { // check for any outstanding tasks if this.state.is_empty() && this.write_buf.is_empty() { if this.flags.contains(Flags::STARTED) { @@ -689,9 +692,15 @@ where if let Some(deadline) = this.codec.config().client_disconnect_timer() { - if let Some(mut timer) = this.ka_timer.as_mut() { + if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() + { timer.reset(deadline); - let _ = Pin::new(&mut timer).poll(cx); + let _ = this + .ka_timer + .as_mut() + .as_pin_mut() + .unwrap() + .poll(cx); } } else { // no shutdown timeout, drop socket @@ -716,14 +725,15 @@ where } else if let Some(deadline) = this.codec.config().keep_alive_expire() { - if let Some(mut timer) = this.ka_timer.as_mut() { + if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() { timer.reset(deadline); - let _ = Pin::new(&mut timer).poll(cx); + let _ = + this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } - } else if let Some(mut timer) = this.ka_timer.as_mut() { + } else if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() { timer.reset(*this.ka_expire); - let _ = Pin::new(&mut timer).poll(cx); + let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } Poll::Pending => (), @@ -736,13 +746,13 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { type Output = Result<(), DispatchError>; @@ -951,12 +961,12 @@ fn read( where T: AsyncRead + Unpin, { - Pin::new(io).poll_read_buf(cx, buf) + actix_codec::poll_read_buf(Pin::new(io), cx, buf) } #[cfg(test)] mod tests { - use std::{marker::PhantomData, str}; + use std::str; use actix_service::fn_service; use futures_util::future::{lazy, ready}; @@ -985,21 +995,19 @@ mod tests { } } - fn ok_service() -> impl Service - { + fn ok_service() -> impl Service { fn_service(|_req: Request| ready(Ok::<_, Error>(Response::Ok().finish()))) } - fn echo_path_service( - ) -> impl Service { + fn echo_path_service() -> impl Service { fn_service(|req: Request| { let path = req.path().as_bytes(); ready(Ok::<_, Error>(Response::Ok().body(Body::from_slice(path)))) }) } - fn echo_payload_service( - ) -> impl Service { + fn echo_payload_service() -> impl Service + { fn_service(|mut req: Request| { Box::pin(async move { use futures_util::stream::StreamExt as _; @@ -1007,7 +1015,7 @@ mod tests { let mut pl = req.take_payload(); let mut body = BytesMut::new(); while let Some(chunk) = pl.next().await { - body.extend_from_slice(chunk.unwrap().bytes()) + body.extend_from_slice(chunk.unwrap().chunk()) } Ok::<_, Error>(Response::Ok().body(body)) @@ -1020,7 +1028,7 @@ mod tests { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), CloneableService::new(ok_service()), @@ -1060,7 +1068,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1114,7 +1122,7 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, CloneableService::new(echo_path_service()), @@ -1163,7 +1171,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, CloneableService::new(echo_payload_service()), @@ -1234,7 +1242,7 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, CloneableService::new(echo_path_service()), @@ -1293,12 +1301,12 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); - let h1 = Dispatcher::<_, _, _, _, UpgradeHandler<_>>::new( + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, CloneableService::new(ok_service()), CloneableService::new(ExpectHandler), - Some(CloneableService::new(UpgradeHandler(PhantomData))), + Some(CloneableService::new(UpgradeHandler)), Extensions::new(), None, ); diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 2ec4899a7..4fadbb518 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -135,7 +135,7 @@ pub(crate) trait MessageType: Sized { let mut has_date = false; - let mut buf = dst.bytes_mut().as_mut_ptr() as *mut u8; + let mut buf = dst.chunk_mut().as_mut_ptr() as *mut u8; let mut remaining = dst.capacity() - dst.len(); // tracks bytes written since last buffer resize @@ -177,7 +177,7 @@ pub(crate) trait MessageType: Sized { // re-assign buf raw pointer since it's possible that the buffer was // reallocated and/or resized - buf = dst.bytes_mut().as_mut_ptr() as *mut u8; + buf = dst.chunk_mut().as_mut_ptr() as *mut u8; } // SAFETY: on each write, it is enough to ensure that the advancement of the @@ -224,7 +224,7 @@ pub(crate) trait MessageType: Sized { // re-assign buf raw pointer since it's possible that the buffer was // reallocated and/or resized - buf = dst.bytes_mut().as_mut_ptr() as *mut u8; + buf = dst.chunk_mut().as_mut_ptr() as *mut u8; } // SAFETY: on each write, it is enough to ensure that the advancement of diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index b89c7ff74..c3e4ccdaa 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -8,11 +8,10 @@ use crate::request::Request; pub struct ExpectHandler; -impl ServiceFactory for ExpectHandler { - type Config = (); - type Request = Request; +impl ServiceFactory for ExpectHandler { type Response = Request; type Error = Error; + type Config = (); type Service = ExpectHandler; type InitError = Error; type Future = Ready>; @@ -22,8 +21,7 @@ impl ServiceFactory for ExpectHandler { } } -impl Service for ExpectHandler { - type Request = Request; +impl Service for ExpectHandler { type Response = Request; type Error = Error; type Future = Ready>; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 919a5d932..67f1127c7 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -24,25 +24,25 @@ use super::dispatcher::Dispatcher; use super::{ExpectHandler, UpgradeHandler}; /// `ServiceFactory` implementation for HTTP1 transport -pub struct H1Service> { +pub struct H1Service { srv: S, cfg: ServiceConfig, expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl H1Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, { /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -59,19 +59,15 @@ where impl H1Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { @@ -79,8 +75,8 @@ where pub fn tcp( self, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = DispatchError, InitError = (), @@ -97,22 +93,23 @@ where mod openssl { use super::*; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, TlsError}; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::TlsError; impl H1Service, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< + (Request, Framed, Codec>), Config = (), - Request = (Request, Framed, Codec>), Response = (), >, U::Error: fmt::Display + Into, @@ -123,10 +120,10 @@ mod openssl { self, acceptor: SslAcceptor, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), - Error = TlsError, DispatchError>, + Error = TlsError, InitError = (), > { pipeline_factory( @@ -146,23 +143,24 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::TlsError; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; + use actix_tls::accept::TlsError; use std::{fmt, io}; impl H1Service, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, U: ServiceFactory< + (Request, Framed, Codec>), Config = (), - Request = (Request, Framed, Codec>), Response = (), >, U::Error: fmt::Display + Into, @@ -173,8 +171,8 @@ mod rustls { self, config: ServerConfig, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = TlsError, InitError = (), @@ -195,7 +193,7 @@ mod rustls { impl H1Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, @@ -203,7 +201,7 @@ where { pub fn expect(self, expect: X1) -> H1Service where - X1: ServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, { @@ -219,7 +217,7 @@ where pub fn upgrade(self, upgrade: Option) -> H1Service where - U1: ServiceFactory), Response = ()>, + U1: ServiceFactory<(Request, Framed), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, { @@ -240,27 +238,27 @@ where } } -impl ServiceFactory for H1Service +impl ServiceFactory<(T, Option)> + for H1Service where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, { - type Config = (); - type Request = (T, Option); type Response = (); type Error = DispatchError; - type InitError = (); + type Config = (); type Service = H1ServiceHandler; + type InitError = (); type Future = H1ServiceResponse; fn new_service(&self, _: ()) -> Self::Future { @@ -281,13 +279,13 @@ where #[pin_project::pin_project] pub struct H1ServiceResponse where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, + U: ServiceFactory<(Request, Framed), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, { @@ -307,15 +305,15 @@ where impl Future for H1ServiceResponse where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into, S::Response: Into>, S::InitError: fmt::Debug, B: MessageBody, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - U: ServiceFactory), Response = ()>, + U: ServiceFactory<(Request, Framed), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, { @@ -362,24 +360,29 @@ where } /// `Service` implementation for HTTP/1 transport -pub struct H1ServiceHandler { +pub struct H1ServiceHandler +where + S: Service, + X: Service, + U: Service<(Request, Framed)>, +{ srv: CloneableService, expect: CloneableService, upgrade: Option>, on_connect_ext: Option>>, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl H1ServiceHandler where - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn new( @@ -400,19 +403,19 @@ where } } -impl Service for H1ServiceHandler +impl Service<(T, Option)> + for H1ServiceHandler where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, S::Response: Into>, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display + Into, { - type Request = (T, Option); type Response = (); type Error = DispatchError; type Future = Dispatcher; @@ -459,7 +462,7 @@ where } } - fn call(&mut self, (io, addr): Self::Request) -> Self::Future { + fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 8615f27a8..007aff1bf 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::task::{Context, Poll}; use actix_codec::Framed; @@ -9,14 +8,13 @@ use crate::error::Error; use crate::h1::Codec; use crate::request::Request; -pub struct UpgradeHandler(pub(crate) PhantomData); +pub struct UpgradeHandler; -impl ServiceFactory for UpgradeHandler { - type Config = (); - type Request = (Request, Framed); +impl ServiceFactory<(Request, Framed)> for UpgradeHandler { type Response = (); type Error = Error; - type Service = UpgradeHandler; + type Config = (); + type Service = UpgradeHandler; type InitError = Error; type Future = Ready>; @@ -25,8 +23,7 @@ impl ServiceFactory for UpgradeHandler { } } -impl Service for UpgradeHandler { - type Request = (Request, Framed); +impl Service<(Request, Framed)> for UpgradeHandler { type Response = (); type Error = Error; type Future = Ready>; @@ -35,7 +32,7 @@ impl Service for UpgradeHandler { Poll::Ready(Ok(())) } - fn call(&mut self, _: Self::Request) -> Self::Future { + fn call(&mut self, _: (Request, Framed)) -> Self::Future { ready(Ok(())) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 7a0be9492..4aeda942a 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -6,7 +6,7 @@ use std::pin::Pin; use std::task::{Context, Poll}; use actix_codec::{AsyncRead, AsyncWrite}; -use actix_rt::time::{Delay, Instant}; +use actix_rt::time::{Instant, Sleep}; use actix_service::Service; use bytes::{Bytes, BytesMut}; use h2::server::{Connection, SendResponse}; @@ -29,9 +29,11 @@ const CHUNK_SIZE: usize = 16_384; /// Dispatcher for HTTP/2 protocol #[pin_project::pin_project] -pub struct Dispatcher, B: MessageBody> +pub struct Dispatcher where T: AsyncRead + AsyncWrite + Unpin, + S: Service, + B: MessageBody, { service: CloneableService, connection: Connection, @@ -39,14 +41,14 @@ where config: ServiceConfig, peer_addr: Option, ka_expire: Instant, - ka_timer: Option, + ka_timer: Option, _t: PhantomData, } impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into, // S::Future: 'static, S::Response: Into>, @@ -57,7 +59,7 @@ where connection: Connection, on_connect_data: Extensions, config: ServiceConfig, - timeout: Option, + timeout: Option, peer_addr: Option, ) -> Self { // let keepalive = config.keep_alive_enabled(); @@ -92,7 +94,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index b1fb9a634..719f3622c 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -36,14 +36,14 @@ pub struct H2Service { impl H2Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -64,18 +64,18 @@ where impl H2Service where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create simple tcp based service pub fn tcp( self, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = DispatchError, InitError = S::InitError, @@ -92,18 +92,18 @@ where #[cfg(feature = "openssl")] mod openssl { - use actix_service::{fn_factory, fn_service}; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, TlsError}; + use actix_service::{fn_factory, fn_service, ServiceFactoryExt}; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::TlsError; use super::*; impl H2Service, S, B> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create ssl based service @@ -111,10 +111,10 @@ mod openssl { self, acceptor: SslAcceptor, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), - Error = TlsError, DispatchError>, + Error = TlsError, InitError = S::InitError, > { pipeline_factory( @@ -136,16 +136,17 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, TlsStream}; - use actix_tls::TlsError; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, TlsStream}; + use actix_tls::accept::TlsError; use std::io; impl H2Service, S, B> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create openssl based service @@ -153,8 +154,8 @@ mod rustls { self, mut config: ServerConfig, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = TlsError, InitError = S::InitError, @@ -178,21 +179,20 @@ mod rustls { } } -impl ServiceFactory for H2Service +impl ServiceFactory<(T, Option)> for H2Service where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { - type Config = (); - type Request = (T, Option); type Response = (); type Error = DispatchError; - type InitError = S::InitError; + type Config = (); type Service = H2ServiceHandler; + type InitError = S::InitError; type Future = H2ServiceResponse; fn new_service(&self, _: ()) -> Self::Future { @@ -207,21 +207,24 @@ where #[doc(hidden)] #[pin_project::pin_project] -pub struct H2ServiceResponse { +pub struct H2ServiceResponse +where + S: ServiceFactory, +{ #[pin] fut: S::Future, cfg: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl Future for H2ServiceResponse where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { type Output = Result, S::InitError>; @@ -241,16 +244,19 @@ where } /// `Service` implementation for http/2 transport -pub struct H2ServiceHandler { +pub struct H2ServiceHandler +where + S: Service, +{ srv: CloneableService, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl H2ServiceHandler where - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, @@ -270,16 +276,15 @@ where } } -impl Service for H2ServiceHandler +impl Service<(T, Option)> for H2ServiceHandler where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, { - type Request = (T, Option); type Response = (); type Error = DispatchError; type Future = H2ServiceHandlerResponse; @@ -292,7 +297,7 @@ where }) } - fn call(&mut self, (io, addr): Self::Request) -> Self::Future { + fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { // run on_connect_ext callback, populating connect extensions @@ -311,7 +316,7 @@ where } } -enum State, B: MessageBody> +enum State, B: MessageBody> where T: AsyncRead + AsyncWrite + Unpin, S::Future: 'static, @@ -329,7 +334,7 @@ where pub struct H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, @@ -341,7 +346,7 @@ where impl Future for H2ServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index 81caf6d53..d6b9d8001 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -3,7 +3,8 @@ use std::io::Write; use std::str::FromStr; use std::time::{SystemTime, UNIX_EPOCH}; -use bytes::{buf::BufMutExt, BytesMut}; +use bytes::buf::BufMut; +use bytes::BytesMut; use http::header::{HeaderValue, InvalidHeaderValue}; use time::{offset, OffsetDateTime, PrimitiveDateTime}; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 64e302441..0bc84a44e 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -23,6 +23,10 @@ impl

HttpMessage for Request

{ &self.head().headers } + fn take_payload(&mut self) -> Payload

{ + std::mem::replace(&mut self.payload, Payload::None) + } + /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { @@ -34,10 +38,6 @@ impl

HttpMessage for Request

{ fn extensions_mut(&self) -> RefMut<'_, Extensions> { self.head.extensions_mut() } - - fn take_payload(&mut self) -> Payload

{ - std::mem::replace(&mut self.payload, Payload::None) - } } impl From> for Request { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 527ed3833..f23115cd5 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -22,22 +22,22 @@ use crate::response::Response; use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. -pub struct HttpService> { +pub struct HttpService { srv: S, cfg: ServiceConfig, expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _t: PhantomData, } impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create builder for `HttpService` instance. @@ -48,15 +48,15 @@ where impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { /// Create new `HttpService` instance. - pub fn new>(service: F) -> Self { + pub fn new>(service: F) -> Self { let cfg = ServiceConfig::new(KeepAlive::Timeout(5), 5000, 0, false, None); HttpService { @@ -70,7 +70,7 @@ where } /// Create new `HttpService` instance with config. - pub(crate) fn with_config>( + pub(crate) fn with_config>( cfg: ServiceConfig, service: F, ) -> Self { @@ -87,11 +87,11 @@ where impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody, { /// Provide service for `EXPECT: 100-Continue` support. @@ -101,10 +101,10 @@ where /// request will be forwarded to main service. pub fn expect(self, expect: X1) -> HttpService where - X1: ServiceFactory, + X1: ServiceFactory, X1::Error: Into, X1::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, { HttpService { expect, @@ -122,14 +122,10 @@ where /// and this service get called with original request and framed object. pub fn upgrade(self, upgrade: Option) -> HttpService where - U1: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + U1: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U1::Error: fmt::Display, U1::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { HttpService { upgrade, @@ -150,31 +146,31 @@ where impl HttpService where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, U: ServiceFactory< + (Request, Framed), Config = (), - Request = (Request, Framed), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { /// Create simple tcp stream service pub fn tcp( self, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = DispatchError, InitError = (), @@ -190,39 +186,40 @@ where #[cfg(feature = "openssl")] mod openssl { use super::*; - use actix_tls::openssl::{Acceptor, SslAcceptor, SslStream}; - use actix_tls::{openssl::HandshakeError, TlsError}; + use actix_service::ServiceFactoryExt; + use actix_tls::accept::openssl::{Acceptor, SslAcceptor, SslError, SslStream}; + use actix_tls::accept::TlsError; impl HttpService, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, U: ServiceFactory< + (Request, Framed, h1::Codec>), Config = (), - Request = (Request, Framed, h1::Codec>), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn openssl( self, acceptor: SslAcceptor, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), - Error = TlsError, DispatchError>, + Error = TlsError, InitError = (), > { pipeline_factory( @@ -250,39 +247,42 @@ mod openssl { #[cfg(feature = "rustls")] mod rustls { - use super::*; - use actix_tls::rustls::{Acceptor, ServerConfig, Session, TlsStream}; - use actix_tls::TlsError; use std::io; + use actix_tls::accept::rustls::{Acceptor, ServerConfig, Session, TlsStream}; + use actix_tls::accept::TlsError; + + use super::*; + use actix_service::ServiceFactoryExt; + impl HttpService, S, B, X, U> where - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, + >::Future: 'static, U: ServiceFactory< + (Request, Framed, h1::Codec>), Config = (), - Request = (Request, Framed, h1::Codec>), Response = (), >, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + , h1::Codec>)>>::Future: 'static, { /// Create openssl based service pub fn rustls( self, mut config: ServerConfig, ) -> impl ServiceFactory< + TcpStream, Config = (), - Request = TcpStream, Response = (), Error = TlsError, InitError = (), @@ -313,34 +313,30 @@ mod rustls { } } -impl ServiceFactory for HttpService +impl ServiceFactory<(T, Protocol, Option)> + for HttpService where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory< - Config = (), - Request = (Request, Framed), - Response = (), - >, + >::Future: 'static, + U: ServiceFactory<(Request, Framed), Config = (), Response = ()>, U::Error: fmt::Display + Into, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { - type Config = (); - type Request = (T, Protocol, Option); type Response = (); type Error = DispatchError; - type InitError = (); + type Config = (); type Service = HttpServiceHandler; + type InitError = (); type Future = HttpServiceResponse; fn new_service(&self, _: ()) -> Self::Future { @@ -359,13 +355,12 @@ where #[doc(hidden)] #[pin_project] -pub struct HttpServiceResponse< - T, - S: ServiceFactory, - B, - X: ServiceFactory, - U: ServiceFactory, -> { +pub struct HttpServiceResponse +where + S: ServiceFactory, + X: ServiceFactory, + U: ServiceFactory<(Request, Framed)>, +{ #[pin] fut: S::Future, #[pin] @@ -382,20 +377,20 @@ pub struct HttpServiceResponse< impl Future for HttpServiceResponse where T: AsyncRead + AsyncWrite + Unpin, - S: ServiceFactory, + S: ServiceFactory, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, - X: ServiceFactory, + X: ServiceFactory, X::Error: Into, X::InitError: fmt::Debug, - ::Future: 'static, - U: ServiceFactory), Response = ()>, + >::Future: 'static, + U: ServiceFactory<(Request, Framed), Response = ()>, U::Error: fmt::Display, U::InitError: fmt::Debug, - ::Future: 'static, + )>>::Future: 'static, { type Output = Result, ()>; @@ -440,25 +435,30 @@ where } /// `Service` implementation for http transport -pub struct HttpServiceHandler { +pub struct HttpServiceHandler +where + S: Service, + X: Service, + U: Service<(Request, Framed)>, +{ srv: CloneableService, expect: CloneableService, upgrade: Option>, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData<(T, B, X)>, + _t: PhantomData, } impl HttpServiceHandler where - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn new( @@ -479,20 +479,20 @@ where } } -impl Service for HttpServiceHandler +impl Service<(T, Protocol, Option)> + for HttpServiceHandler where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display + Into, { - type Request = (T, Protocol, Option); type Response = (); type Error = DispatchError; type Future = HttpServiceHandlerResponse; @@ -539,7 +539,10 @@ where } } - fn call(&mut self, (io, proto, peer_addr): Self::Request) -> Self::Future { + fn call( + &mut self, + (io, proto, peer_addr): (T, Protocol, Option), + ) -> Self::Future { let mut connect_extensions = Extensions::new(); if let Some(ref handler) = self.on_connect_ext { @@ -575,14 +578,14 @@ where #[pin_project(project = StateProj)] enum State where - S: Service, + S: Service, S::Future: 'static, S::Error: Into, T: AsyncRead + AsyncWrite + Unpin, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { H1(#[pin] h1::Dispatcher), @@ -602,14 +605,14 @@ where pub struct HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { #[pin] @@ -619,14 +622,14 @@ where impl Future for HttpServiceHandlerResponse where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Future: 'static, S::Response: Into> + 'static, B: MessageBody, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { type Output = Result<(), DispatchError>; @@ -639,13 +642,13 @@ where impl State where T: AsyncRead + AsyncWrite + Unpin, - S: Service, + S: Service, S::Error: Into + 'static, S::Response: Into> + 'static, B: MessageBody + 'static, - X: Service, + X: Service, X::Error: Into, - U: Service), Response = ()>, + U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { fn poll( diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 4512e72c2..3f08bb7ee 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -10,7 +10,7 @@ use std::{ task::{Context, Poll}, }; -use actix_codec::{AsyncRead, AsyncWrite}; +use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; use http::header::{self, HeaderName, HeaderValue}; use http::{Error as HttpError, Method, Uri, Version}; @@ -251,9 +251,11 @@ impl AsyncRead for TestBuffer { fn poll_read( self: Pin<&mut Self>, _: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - Poll::Ready(self.get_mut().read(buf)) + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let dst = buf.initialize_unfilled(); + let res = self.get_mut().read(dst).map(|n| buf.advance(n)); + Poll::Ready(res) } } @@ -356,11 +358,15 @@ impl AsyncRead for TestSeqBuffer { fn poll_read( self: Pin<&mut Self>, _: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { - let r = self.get_mut().read(buf); + buf: &mut ReadBuf<'_>, + ) -> Poll> { + let dst = buf.initialize_unfilled(); + let r = self.get_mut().read(dst); match r { - Ok(n) => Poll::Ready(Ok(n)), + Ok(n) => { + buf.advance(n); + Poll::Ready(Ok(())) + } Err(err) if err.kind() == io::ErrorKind::WouldBlock => Poll::Pending, Err(err) => Poll::Ready(Err(err)), } diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index b114217a0..7be7cf637 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -11,7 +11,7 @@ use super::{Codec, Frame, Message}; #[pin_project::pin_project] pub struct Dispatcher where - S: Service + 'static, + S: Service + 'static, T: AsyncRead + AsyncWrite, { #[pin] @@ -21,17 +21,17 @@ where impl Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { - pub fn new>(io: T, service: F) -> Self { + pub fn new>(io: T, service: F) -> Self { Dispatcher { inner: InnerDispatcher::new(Framed::new(io, Codec::new()), service), } } - pub fn with>(framed: Framed, service: F) -> Self { + pub fn with>(framed: Framed, service: F) -> Self { Dispatcher { inner: InnerDispatcher::new(framed, service), } @@ -41,7 +41,7 @@ where impl Future for Dispatcher where T: AsyncRead + AsyncWrite, - S: Service, + S: Service, S::Future: 'static, S::Error: 'static, { diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index 07104decc..f78636b9a 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -1,9 +1,8 @@ -use actix_service::ServiceFactory; -use bytes::Bytes; -use futures_util::future::{self, ok}; - use actix_http::{http, HttpService, Request, Response}; use actix_http_test::test_server; +use actix_service::ServiceFactoryExt; +use bytes::Bytes; +use futures_util::future::{self, ok}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 6b80bad0a..bb4732281 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -1,19 +1,17 @@ #![cfg(feature = "openssl")] use std::io; -use actix_http_test::test_server; -use actix_service::{fn_service, ServiceFactory}; - -use bytes::{Bytes, BytesMut}; -use futures_util::future::{err, ok, ready}; -use futures_util::stream::{once, Stream, StreamExt}; -use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; - use actix_http::error::{ErrorBadRequest, PayloadError}; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; use actix_http::httpmessage::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; +use actix_http_test::test_server; +use actix_service::{fn_service, ServiceFactoryExt}; +use bytes::{Bytes, BytesMut}; +use futures_util::future::{err, ok, ready}; +use futures_util::stream::{once, Stream, StreamExt}; +use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; async fn load_body(stream: S) -> Result where diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index 44794e199..fa1aeb695 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -3,7 +3,7 @@ use std::time::Duration; use std::{net, thread}; use actix_http_test::test_server; -use actix_rt::time::delay_for; +use actix_rt::time::sleep; use actix_service::fn_service; use bytes::Bytes; use futures_util::future::{self, err, ok, ready, FutureExt}; @@ -88,7 +88,7 @@ async fn test_expect_continue_h1() { let srv = test_server(|| { HttpService::build() .expect(fn_service(|req: Request| { - delay_for(Duration::from_millis(20)).then(move |_| { + sleep(Duration::from_millis(20)).then(move |_| { if req.head().uri.query() == Some("yes=") { ok(req) } else { diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 5d86605f4..e31f2745c 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -36,11 +36,10 @@ impl Clone for WsService { } } -impl Service for WsService +impl Service<(Request, Framed)> for WsService where T: AsyncRead + AsyncWrite + Unpin + 'static, { - type Request = (Request, Framed); type Response = (); type Error = Error; type Future = Pin>>>; @@ -50,7 +49,10 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, (req, mut framed): Self::Request) -> Self::Future { + fn call( + &mut self, + (req, mut framed): (Request, Framed), + ) -> Self::Future { let fut = async move { let res = ws::handshake(req.head()).unwrap().message_body(()); diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 0d13d5015..4c6f01d29 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -2,6 +2,9 @@ ## Unreleased - 2021-xx-xx * Fix multipart consuming payload before header checks #1513 +* Update `bytes` to `1.0`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 3.0.0 - 2020-09-11 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index e2e9dbf14..ed572a700 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,16 +17,16 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "3.0.0", default-features = false } -actix-service = "1.0.6" -actix-utils = "2.0.0" -bytes = "0.5.3" +actix-utils = "3.0.0-beta.1" + +bytes = "1" derive_more = "0.99.2" httparse = "1.3" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } log = "0.4" mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "1.0.0" +actix-rt = "2.0.0-beta.1" actix-http = "2.0.0" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 0d830fe64..e47f09135 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,8 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx -* Upgrade `pin-project` to `1.0`. +* Update `pin-project` to `1.0`. +* Update `bytes` to `1.0`. [#1813] +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 920940c40..28b9d6fa2 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,16 +16,17 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.10.0" -actix-web = { version = "3.0.0", default-features = false } +actix = "0.11.0-beta.1" +actix-codec = "0.4.0-beta.1" actix-http = "2.0.0" -actix-codec = "0.3.0" -bytes = "0.5.2" -futures-channel = { version = "0.3.5", default-features = false } -futures-core = { version = "0.3.5", default-features = false } +actix-web = { version = "3.0.0", default-features = false } + +bytes = "1" +futures-core = { version = "0.3.7", default-features = false } pin-project = "1.0.0" +tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "1.1.1" +actix-rt = "2.0.0-beta.1" env_logger = "0.7" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 0839a4288..2dd93c727 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -12,8 +12,8 @@ use actix::{ }; use actix_web::error::Error; use bytes::Bytes; -use futures_channel::oneshot::Sender; use futures_core::Stream; +use tokio::sync::oneshot::Sender; /// Execution context for http actors pub struct HttpContext diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 8fd03f6a1..9dd7bf500 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -24,8 +24,8 @@ use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; -use futures_channel::oneshot::Sender; use futures_core::Stream; +use tokio::sync::oneshot::Sender; /// Do websocket handshake and start ws actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index fd99a8376..3fc4ae1be 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,8 +19,8 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "1.1.1" +actix-rt = "2.0.0-beta.1" actix-web = "3.0.0" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index dd2bccd7f..389d09c82 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -88,17 +88,16 @@ async fn route_test() -> impl Responder { pub struct ChangeStatusCode; -impl Transform for ChangeStatusCode +impl Transform for ChangeStatusCode where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = ChangeStatusCodeMiddleware; + type InitError = (); type Future = future::Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -110,13 +109,12 @@ pub struct ChangeStatusCodeMiddleware { service: S, } -impl Service for ChangeStatusCodeMiddleware +impl Service for ChangeStatusCodeMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; #[allow(clippy::type_complexity)] diff --git a/awc/CHANGES.md b/awc/CHANGES.md index ee795fccb..45a38259c 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -2,7 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8` +* Update `rand` to `0.8` +* Update `bytes` to `1.0`. [#1813] +* Update `rust-tls` to `0.19`. [#1813] + +[#1813]: https://github.com/actix/actix-web/pull/1813 ## 2.0.3 - 2020-11-29 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 2e92526d2..b80f1ba6b 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -37,16 +37,16 @@ rustls = ["rust-tls", "actix-http/rustls"] compress = ["actix-http/compress"] [dependencies] -actix-codec = "0.3.0" -actix-service = "1.0.6" +actix-codec = "0.4.0-beta.1" +actix-service = "2.0.0-beta.2" actix-http = "2.2.0" -actix-rt = "1.0.0" +actix-rt = "2.0.0-beta.1" base64 = "0.13" -bytes = "0.5.3" +bytes = "1" cfg-if = "1.0" derive_more = "0.99.2" -futures-core = { version = "0.3.5", default-features = false } +futures-core = { version = "0.3.7", default-features = false } log =" 0.4" mime = "0.3" percent-encoding = "2.1" @@ -55,18 +55,20 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" open-ssl = { version = "0.10", package = "openssl", optional = true } -rust-tls = { version = "0.18.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } +rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -actix-connect = { version = "2.0.0", features = ["openssl"] } +# TODO: actix is temporary added as dev dep for actix-macro reason. +# Can be removed when it does not impact tests. +actix = "0.11.0-beta.1" actix-web = { version = "3.0.0", features = ["openssl"] } actix-http = { version = "2.0.0", features = ["openssl"] } actix-http-test = { version = "2.0.0", features = ["openssl"] } -actix-utils = "2.0.0" -actix-server = "1.0.0" -actix-tls = { version = "2.0.0", features = ["openssl", "rustls"] } +actix-utils = "3.0.0-beta.1" +actix-server = "2.0.0-beta.2" +actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" -futures-util = { version = "0.3.5", default-features = false } +futures-util = { version = "0.3.7", default-features = false } env_logger = "0.7" webpki = "0.21" diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 7cd659c38..6be0112d8 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -51,7 +51,7 @@ impl ClientBuilder { /// Use custom connector service. pub fn connector(mut self, connector: T) -> Self where - T: Service + 'static, + T: Service + 'static, T::Response: Connection, ::Future: 'static, T::Future: 'static, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 7fbe1543a..8ee239f76 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -2,9 +2,9 @@ use std::future::Future; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; -use std::{fmt, io, mem, net}; +use std::{fmt, io, net}; -use actix_codec::{AsyncRead, AsyncWrite, Framed}; +use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use actix_http::body::Body; use actix_http::client::{ Connect as ClientConnect, ConnectError, Connection, SendRequestError, @@ -70,7 +70,7 @@ pub(crate) trait Connect { impl Connect for ConnectorWrapper where - T: Service, + T: Service, T::Response: Connection, ::Io: 'static, ::Future: 'static, @@ -221,18 +221,11 @@ impl fmt::Debug for BoxedSocket { } impl AsyncRead for BoxedSocket { - unsafe fn prepare_uninitialized_buffer( - &self, - buf: &mut [mem::MaybeUninit], - ) -> bool { - self.0.as_read().prepare_uninitialized_buffer(buf) - } - fn poll_read( self: Pin<&mut Self>, cx: &mut Context<'_>, - buf: &mut [u8], - ) -> Poll> { + buf: &mut ReadBuf<'_>, + ) -> Poll> { Pin::new(self.get_mut().0.as_read_mut()).poll_read(cx, buf) } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 0bcdf4307..b5ff61da5 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -5,7 +5,7 @@ use std::rc::Rc; use std::task::{Context, Poll}; use std::time::Duration; -use actix_rt::time::{delay_for, Delay}; +use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; use futures_core::Stream; @@ -56,7 +56,8 @@ impl Into for PrepForSendingError { pub enum SendClientRequest { Fut( Pin>>>, - Option, + // FIXME: use a pinned Sleep instead of box. + Option>>, bool, ), Err(Option), @@ -68,7 +69,7 @@ impl SendClientRequest { response_decompress: bool, timeout: Option, ) -> SendClientRequest { - let delay = timeout.map(delay_for); + let delay = timeout.map(|d| Box::pin(sleep(d))); SendClientRequest::Fut(send, delay, response_decompress) } } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 0024c6652..1b7413312 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -108,14 +108,14 @@ async fn test_form() { async fn test_timeout() { let srv = test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { - actix_rt::time::delay_for(Duration::from_millis(200)).await; + actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) }))) }); let connector = awc::Connector::new() - .connector(actix_connect::new_connector( - actix_connect::start_default_resolver().await.unwrap(), + .connector(actix_tls::connect::new_connector( + actix_tls::connect::start_default_resolver().await.unwrap(), )) .timeout(Duration::from_secs(15)) .finish(); @@ -136,7 +136,7 @@ async fn test_timeout() { async fn test_timeout_override() { let srv = test::start(|| { App::new().service(web::resource("/").route(web::to(|| async { - actix_rt::time::delay_for(Duration::from_millis(200)).await; + actix_rt::time::sleep(Duration::from_millis(200)).await; Ok::<_, Error>(HttpResponse::Ok().body(STR)) }))) }); diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 888f7a900..e500801c4 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -1,7 +1,7 @@ #![cfg(feature = "openssl")] use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, ServiceFactory}; +use actix_service::{map_config, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 0df6b154c..3fa76d4a9 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactory}; +use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index eced5f14b..de1514042 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -4,7 +4,7 @@ use std::sync::Arc; use actix_http::HttpService; use actix_http_test::test_server; -use actix_service::{map_config, pipeline_factory, ServiceFactory}; +use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; diff --git a/benches/server.rs b/benches/server.rs index 041d0fa57..117b6136e 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -29,18 +29,22 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn bench_async_burst(c: &mut Criterion) { // We are using System here, since Runtime requires preinitialized tokio // Maybe add to actix_rt docs - let mut rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new("test"); - let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + let srv = rt.block_on(async { + test::start(|| { + App::new().service( + web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), + ) + }) }); let url = srv.url("/"); c.bench_function("get_body_async_burst", move |b| { b.iter_custom(|iters| { - let client = Client::new().get(url.clone()).freeze().unwrap(); + let client = + rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() }); let start = std::time::Instant::now(); // benchmark body diff --git a/benches/service.rs b/benches/service.rs index 8adbc8a0c..8ca6cbe28 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -23,10 +23,9 @@ use actix_web::test::{init_service, ok_service, TestRequest}; /// async_service_direct time: [1.0908 us 1.1656 us 1.2613 us] pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) where - S: Service - + 'static, + S: Service + 'static, { - let mut rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new("test"); let srv = Rc::new(RefCell::new(srv)); let req = TestRequest::default().to_srv_request(); @@ -41,14 +40,15 @@ where b.iter_custom(|iters| { let srv = srv.clone(); // exclude request generation, it appears it takes significant time vs call (3us vs 1us) - let reqs: Vec<_> = (0..iters) + let futs = (0..iters) .map(|_| TestRequest::default().to_srv_request()) - .collect(); + .map(|req| srv.borrow_mut().call(req)); + let start = std::time::Instant::now(); // benchmark body rt.block_on(async move { - for req in reqs { - srv.borrow_mut().call(req).await.unwrap(); + for fut in futs { + fut.await.unwrap(); } }); let elapsed = start.elapsed(); @@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result { // Sample results on MacBook Pro '14 // time: [2.0724 us 2.1345 us 2.2074 us] fn async_web_service(c: &mut Criterion) { - let mut rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new("test"); let srv = Rc::new(RefCell::new(rt.block_on(init_service( App::new().service(web::service("/").finish(index)), )))); @@ -83,13 +83,14 @@ fn async_web_service(c: &mut Criterion) { c.bench_function("async_web_service_direct", move |b| { b.iter_custom(|iters| { let srv = srv.clone(); - let reqs = (0..iters).map(|_| TestRequest::get().uri("/").to_request()); - + let futs = (0..iters) + .map(|_| TestRequest::get().uri("/").to_request()) + .map(|req| srv.borrow_mut().call(req)); let start = std::time::Instant::now(); // benchmark body rt.block_on(async move { - for req in reqs { - srv.borrow_mut().call(req).await.unwrap(); + for fut in futs { + fut.await.unwrap(); } }); let elapsed = start.elapsed(); diff --git a/src/app.rs b/src/app.rs index 8dd86f7ec..d41d692ee 100644 --- a/src/app.rs +++ b/src/app.rs @@ -5,10 +5,11 @@ use std::marker::PhantomData; use std::rc::Rc; use actix_http::body::{Body, MessageBody}; -use actix_http::Extensions; +use actix_http::{Extensions, Request}; use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, Transform, + apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, + Transform, }; use futures_util::future::FutureExt; @@ -63,8 +64,8 @@ impl App where B: MessageBody, T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -268,10 +269,10 @@ where /// ``` pub fn default_service(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -353,8 +354,8 @@ where mw: M, ) -> App< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -364,7 +365,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -420,8 +421,8 @@ where mw: F, ) -> App< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -447,12 +448,12 @@ where } } -impl IntoServiceFactory> for App +impl IntoServiceFactory, Request> for App where B: MessageBody, T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), diff --git a/src/app_service.rs b/src/app_service.rs index 4452778df..f02bb831a 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -29,8 +29,8 @@ type BoxResponse = LocalBoxFuture<'static, Result>; pub struct AppInit where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -46,22 +46,21 @@ where pub(crate) external: RefCell>, } -impl ServiceFactory for AppInit +impl ServiceFactory for AppInit where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), >, { - type Config = AppConfig; - type Request = Request; type Response = ServiceResponse; type Error = T::Error; - type InitError = T::InitError; + type Config = AppConfig; type Service = AppInitService; + type InitError = T::InitError; type Future = AppInitResult; fn new_service(&self, config: AppConfig) -> Self::Future { @@ -132,7 +131,7 @@ where #[pin_project::pin_project] pub struct AppInitResult where - T: ServiceFactory, + T: ServiceFactory, { #[pin] endpoint_fut: T::Future, @@ -155,8 +154,8 @@ where impl Future for AppInitResult where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -214,7 +213,7 @@ where /// Service to convert `Request` to a `ServiceRequest` pub struct AppInitService where - T: Service, Error = Error>, + T: Service, Error = Error>, { service: T, rmap: Rc, @@ -223,11 +222,10 @@ where pool: &'static HttpRequestPool, } -impl Service for AppInitService +impl Service for AppInitService where - T: Service, Error = Error>, + T: Service, Error = Error>, { - type Request = Request; type Response = ServiceResponse; type Error = T::Error; type Future = T::Future; @@ -263,7 +261,7 @@ where impl Drop for AppInitService where - T: Service, Error = Error>, + T: Service, Error = Error>, { fn drop(&mut self) { self.pool.clear(); @@ -275,9 +273,8 @@ pub struct AppRoutingFactory { default: Rc, } -impl ServiceFactory for AppRoutingFactory { +impl ServiceFactory for AppRoutingFactory { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -386,8 +383,7 @@ pub struct AppRouting { default: Option, } -impl Service for AppRouting { - type Request = ServiceRequest; +impl Service for AppRouting { type Response = ServiceResponse; type Error = Error; type Future = BoxResponse; @@ -434,9 +430,8 @@ impl AppEntry { } } -impl ServiceFactory for AppEntry { +impl ServiceFactory for AppEntry { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); diff --git a/src/config.rs b/src/config.rs index 01959daa1..4ec36952a 100644 --- a/src/config.rs +++ b/src/config.rs @@ -105,10 +105,10 @@ impl AppService { factory: F, nested: Option>, ) where - F: IntoServiceFactory, + F: IntoServiceFactory, S: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), diff --git a/src/handler.rs b/src/handler.rs index d4b755e57..14e8cb40b 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -82,14 +82,13 @@ where } } -impl ServiceFactory for HandlerService +impl ServiceFactory for HandlerService where F: Handler, T: FromRequest, R: Future, R::Output: Responder, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); @@ -102,15 +101,14 @@ where } } -// Handler is both it's ServiceHandler and Service Type. -impl Service for HandlerService +// HandlerService is both it's ServiceFactory and Service Type. +impl Service for HandlerService where F: Handler, T: FromRequest, R: Future, R::Output: Responder, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = HandlerServiceFuture; @@ -119,7 +117,7 @@ where Poll::Ready(Ok(())) } - fn call(&mut self, req: Self::Request) -> Self::Future { + fn call(&mut self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 7575d7455..faff5003a 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -51,16 +51,15 @@ impl Default for Compress { } } -impl Transform for Compress +impl Transform for Compress where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; - type InitError = (); type Transform = CompressMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -76,12 +75,11 @@ pub struct CompressMiddleware { encoding: ContentEncoding, } -impl Service for CompressMiddleware +impl Service for CompressMiddleware where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type Future = CompressResponse; @@ -115,7 +113,7 @@ where #[pin_project] pub struct CompressResponse where - S: Service, + S: Service, B: MessageBody, { #[pin] @@ -127,7 +125,7 @@ where impl Future for CompressResponse where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { type Output = Result>, Error>; diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 9061c7458..87323e325 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -31,19 +31,18 @@ impl Condition { } } -impl Transform for Condition +impl Transform for Condition where - S: Service + 'static, - T: Transform, + S: Service + 'static, + T: Transform, T::Future: 'static, T::InitError: 'static, T::Transform: 'static, { - type Request = S::Request; type Response = S::Response; type Error = S::Error; - type InitError = T::InitError; type Transform = ConditionMiddleware; + type InitError = T::InitError; type Future = LocalBoxFuture<'static, Result>; fn new_transform(&self, service: S) -> Self::Future { @@ -66,12 +65,11 @@ pub enum ConditionMiddleware { Disable(D), } -impl Service for ConditionMiddleware +impl Service for ConditionMiddleware where - E: Service, - D: Service, + E: Service, + D: Service, { - type Request = E::Request; type Response = E::Response; type Error = E::Error; type Future = Either; @@ -84,7 +82,7 @@ where } } - fn call(&mut self, req: E::Request) -> Self::Future { + fn call(&mut self, req: Req) -> Self::Future { use ConditionMiddleware::*; match self { Enable(service) => Either::Left(service.call(req)), diff --git a/src/middleware/defaultheaders.rs b/src/middleware/defaultheaders.rs index a6f1a4336..d648ad70f 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/defaultheaders.rs @@ -93,12 +93,11 @@ impl DefaultHeaders { } } -impl Transform for DefaultHeaders +impl Transform for DefaultHeaders where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Transform = DefaultHeadersMiddleware; @@ -118,12 +117,11 @@ pub struct DefaultHeadersMiddleware { inner: Rc, } -impl Service for DefaultHeadersMiddleware +impl Service for DefaultHeadersMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = DefaultHeaderFuture; @@ -145,7 +143,7 @@ where } #[pin_project::pin_project] -pub struct DefaultHeaderFuture { +pub struct DefaultHeaderFuture, B> { #[pin] fut: S::Future, inner: Rc, @@ -154,7 +152,7 @@ pub struct DefaultHeaderFuture { impl Future for DefaultHeaderFuture where - S: Service, Error = Error>, + S: Service, Error = Error>, { type Output = ::Output; diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index d2d3b0d8c..9e78bb7d0 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -81,17 +81,16 @@ impl ErrorHandlers { } } -impl Transform for ErrorHandlers +impl Transform for ErrorHandlers where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = ErrorHandlersMiddleware; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -108,13 +107,12 @@ pub struct ErrorHandlersMiddleware { handlers: Rc>>>, } -impl Service for ErrorHandlersMiddleware +impl Service for ErrorHandlersMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, B: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 563cb6c32..2a543f66f 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -179,12 +179,11 @@ impl Default for Logger { } } -impl Transform for Logger +impl Transform for Logger where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type InitError = (); @@ -216,12 +215,11 @@ pub struct LoggerMiddleware { service: S, } -impl Service for LoggerMiddleware +impl Service for LoggerMiddleware where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody, { - type Request = ServiceRequest; type Response = ServiceResponse>; type Error = Error; type Future = LoggerResponse; @@ -262,19 +260,19 @@ where pub struct LoggerResponse where B: MessageBody, - S: Service, + S: Service, { #[pin] fut: S::Future, time: OffsetDateTime, format: Option, - _t: PhantomData<(B,)>, + _t: PhantomData, } impl Future for LoggerResponse where B: MessageBody, - S: Service, Error = Error>, + S: Service, Error = Error>, { type Output = Result>, Error>; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index ad9f51079..4109364bf 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -91,16 +91,15 @@ impl NormalizePath { } } -impl Transform for NormalizePath +impl Transform for NormalizePath where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; - type InitError = (); type Transform = NormalizePathNormalization; + type InitError = (); type Future = Ready>; fn new_transform(&self, service: S) -> Self::Future { @@ -119,12 +118,11 @@ pub struct NormalizePathNormalization { trailing_slash_behavior: TrailingSlash, } -impl Service for NormalizePathNormalization +impl Service for NormalizePathNormalization where - S: Service, Error = Error>, + S: Service, Error = Error>, S::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = S::Future; diff --git a/src/resource.rs b/src/resource.rs index 29a7daa78..7d53ef936 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -9,7 +9,8 @@ use actix_http::{Error, Extensions, Response}; use actix_router::IntoPattern; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; @@ -78,8 +79,8 @@ impl Resource { impl Resource where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -250,8 +251,8 @@ where mw: M, ) -> Resource< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -260,7 +261,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -317,8 +318,8 @@ where mw: F, ) -> Resource< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -345,10 +346,10 @@ where /// default handler from `App` or `Scope`. pub fn default_service(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -368,8 +369,8 @@ where impl HttpServiceFactory for Resource where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -398,11 +399,11 @@ where } } -impl IntoServiceFactory for Resource +impl IntoServiceFactory for Resource where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -425,13 +426,12 @@ pub struct ResourceFactory { default: Rc>>>, } -impl ServiceFactory for ResourceFactory { - type Config = (); - type Request = ServiceRequest; +impl ServiceFactory for ResourceFactory { type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = ResourceService; + type InitError = (); type Future = CreateResourceService; fn new_service(&self, _: ()) -> Self::Future { @@ -520,8 +520,7 @@ pub struct ResourceService { default: Option, } -impl Service for ResourceService { - type Request = ServiceRequest; +impl Service for ResourceService { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -567,9 +566,8 @@ impl ResourceEndpoint { } } -impl ServiceFactory for ResourceEndpoint { +impl ServiceFactory for ResourceEndpoint { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -585,7 +583,7 @@ impl ServiceFactory for ResourceEndpoint { mod tests { use std::time::Duration; - use actix_rt::time::delay_for; + use actix_rt::time::sleep; use actix_service::Service; use futures_util::future::ok; @@ -653,7 +651,7 @@ mod tests { async fn test_to() { let mut srv = init_service(App::new().service(web::resource("/test").to(|| async { - delay_for(Duration::from_millis(100)).await; + sleep(Duration::from_millis(100)).await; Ok::<_, Error>(HttpResponse::Ok()) }))) .await; diff --git a/src/route.rs b/src/route.rs index 00d93fce9..8a3d1da9f 100644 --- a/src/route.rs +++ b/src/route.rs @@ -18,7 +18,7 @@ use crate::HttpResponse; type BoxedRouteService = Box< dyn Service< - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, Future = LocalBoxFuture<'static, Result>, @@ -27,8 +27,8 @@ type BoxedRouteService = Box< type BoxedRouteNewService = Box< dyn ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -63,9 +63,8 @@ impl Route { } } -impl ServiceFactory for Route { +impl ServiceFactory for Route { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -117,8 +116,7 @@ impl RouteService { } } -impl Service for RouteService { - type Request = ServiceRequest; +impl Service for RouteService { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -233,7 +231,7 @@ impl Route { struct RouteNewService where - T: ServiceFactory, + T: ServiceFactory, { service: T, } @@ -241,33 +239,32 @@ where impl RouteNewService where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, T::Future: 'static, T::Service: 'static, - ::Future: 'static, + >::Future: 'static, { pub fn new(service: T) -> Self { RouteNewService { service } } } -impl ServiceFactory for RouteNewService +impl ServiceFactory for RouteNewService where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, >, T::Future: 'static, T::Service: 'static, - ::Future: 'static, + >::Future: 'static, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Config = (); @@ -289,16 +286,15 @@ where } } -struct RouteServiceWrapper { +struct RouteServiceWrapper> { service: T, } -impl Service for RouteServiceWrapper +impl Service for RouteServiceWrapper where T::Future: 'static, - T: Service, + T: Service, { - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -316,7 +312,7 @@ where mod tests { use std::time::Duration; - use actix_rt::time::delay_for; + use actix_rt::time::sleep; use bytes::Bytes; use serde_derive::Serialize; @@ -340,16 +336,16 @@ mod tests { Err::(error::ErrorBadRequest("err")) })) .route(web::post().to(|| async { - delay_for(Duration::from_millis(100)).await; + sleep(Duration::from_millis(100)).await; Ok::<_, ()>(HttpResponse::Created()) })) .route(web::delete().to(|| async { - delay_for(Duration::from_millis(100)).await; + sleep(Duration::from_millis(100)).await; Err::(error::ErrorBadRequest("err")) })), ) .service(web::resource("/json").route(web::get().to(|| async { - delay_for(Duration::from_millis(25)).await; + sleep(Duration::from_millis(25)).await; web::Json(MyObject { name: "test".to_string(), }) diff --git a/src/scope.rs b/src/scope.rs index ce8d94159..419e572aa 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -9,7 +9,8 @@ use actix_http::{Extensions, Response}; use actix_router::{ResourceDef, ResourceInfo, Router}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, + ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; @@ -88,8 +89,8 @@ impl Scope { impl Scope where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -284,10 +285,10 @@ where /// If default resource is not registered, app's default resource is being used. pub fn default_service(mut self, f: F) -> Self where - F: IntoServiceFactory, + F: IntoServiceFactory, U: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, > + 'static, @@ -317,8 +318,8 @@ where mw: M, ) -> Scope< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -327,7 +328,7 @@ where where M: Transform< T::Service, - Request = ServiceRequest, + ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -382,8 +383,8 @@ where mw: F, ) -> Scope< impl ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -409,8 +410,8 @@ where impl HttpServiceFactory for Scope where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -480,9 +481,8 @@ pub struct ScopeFactory { default: Rc>>>, } -impl ServiceFactory for ScopeFactory { +impl ServiceFactory for ScopeFactory { type Config = (); - type Request = ServiceRequest; type Response = ServiceResponse; type Error = Error; type InitError = (); @@ -601,8 +601,7 @@ pub struct ScopeService { _ready: Option<(ServiceRequest, ResourceInfo)>, } -impl Service for ScopeService { - type Request = ServiceRequest; +impl Service for ScopeService { type Response = ServiceResponse; type Error = Error; type Future = LocalBoxFuture<'static, Result>; @@ -653,13 +652,12 @@ impl ScopeEndpoint { } } -impl ServiceFactory for ScopeEndpoint { - type Config = (); - type Request = ServiceRequest; +impl ServiceFactory for ScopeEndpoint { type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = ScopeService; + type InitError = (); type Future = ScopeFactoryResponse; fn new_service(&self, _: ()) -> Self::Future { diff --git a/src/server.rs b/src/server.rs index be97e8a0d..fc80cbed8 100644 --- a/src/server.rs +++ b/src/server.rs @@ -20,9 +20,9 @@ use actix_service::pipeline_factory; use futures_util::future::ok; #[cfg(feature = "openssl")] -use actix_tls::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; +use actix_tls::accept::openssl::{AlpnError, SslAcceptor, SslAcceptorBuilder}; #[cfg(feature = "rustls")] -use actix_tls::rustls::ServerConfig as RustlsServerConfig; +use actix_tls::accept::rustls::ServerConfig as RustlsServerConfig; use crate::config::AppConfig; @@ -58,8 +58,8 @@ struct Config { pub struct HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -67,7 +67,7 @@ where { pub(super) factory: F, config: Arc>, - backlog: i32, + backlog: u32, sockets: Vec, builder: ServerBuilder, on_connect_fn: Option>, @@ -77,12 +77,13 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, + I: IntoServiceFactory, + S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, + S::Service: 'static, B: MessageBody + 'static, { /// Create new http server with application factory @@ -147,7 +148,7 @@ where /// Generally set in the 64-2048 range. Default value is 2048. /// /// This method should be called before `bind()` method call. - pub fn backlog(mut self, backlog: i32) -> Self { + pub fn backlog(mut self, backlog: u32) -> Self { self.backlog = backlog; self.builder = self.builder.backlog(backlog); self @@ -170,8 +171,10 @@ where /// limit the global TLS CPU usage. /// /// By default max connections is set to a 256. + #[allow(unused_variables)] pub fn max_connection_rate(self, num: usize) -> Self { - actix_tls::max_concurrent_tls_connect(num); + #[cfg(any(feature = "rustls", feature = "openssl"))] + actix_tls::accept::max_concurrent_tls_connect(num); self } @@ -603,8 +606,8 @@ where impl HttpServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory, + I: IntoServiceFactory, + S: ServiceFactory, S::Error: Into, S::InitError: fmt::Debug, S::Response: Into>, @@ -639,7 +642,7 @@ where fn create_tcp_listener( addr: net::SocketAddr, - backlog: i32, + backlog: u32, ) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; let domain = match addr { @@ -649,6 +652,8 @@ fn create_tcp_listener( let socket = Socket::new(domain, Type::stream(), Some(Protocol::tcp()))?; socket.set_reuse_address(true)?; socket.bind(&addr.into())?; + // clamp backlog to max u32 that fits in i32 range + let backlog = backlog.min(i32::MAX as u32) as i32; socket.listen(backlog)?; Ok(socket.into_tcp_listener()) } diff --git a/src/service.rs b/src/service.rs index 189ba5554..85bc6123d 100644 --- a/src/service.rs +++ b/src/service.rs @@ -486,10 +486,10 @@ impl WebService { /// Set a service factory implementation and generate web service. pub fn finish(self, service: F) -> impl HttpServiceFactory where - F: IntoServiceFactory, + F: IntoServiceFactory, T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), @@ -514,8 +514,8 @@ struct WebServiceImpl { impl HttpServiceFactory for WebServiceImpl where T: ServiceFactory< + ServiceRequest, Config = (), - Request = ServiceRequest, Response = ServiceResponse, Error = Error, InitError = (), diff --git a/src/test.rs b/src/test.rs index cff6c3e51..a76bae6a6 100644 --- a/src/test.rs +++ b/src/test.rs @@ -11,7 +11,7 @@ use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version}; use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; -use actix_rt::{time::delay_for, System}; +use actix_rt::{time::sleep, System}; use actix_service::{ map_config, IntoService, IntoServiceFactory, Service, ServiceFactory, }; @@ -37,16 +37,14 @@ use crate::{Error, HttpRequest, HttpResponse}; /// Create service that always responds with `HttpResponse::Ok()` pub fn ok_service( -) -> impl Service, Error = Error> -{ +) -> impl Service, Error = Error> { default_service(StatusCode::OK) } /// Create service that responds with response with specified status code pub fn default_service( status_code: StatusCode, -) -> impl Service, Error = Error> -{ +) -> impl Service, Error = Error> { (move |req: ServiceRequest| { ok(req.into_response(HttpResponse::build(status_code).finish())) }) @@ -77,12 +75,12 @@ pub fn default_service( /// ``` pub async fn init_service( app: R, -) -> impl Service, Error = E> +) -> impl Service, Error = E> where - R: IntoServiceFactory, + R: IntoServiceFactory, S: ServiceFactory< + Request, Config = AppConfig, - Request = Request, Response = ServiceResponse, Error = E, >, @@ -96,15 +94,12 @@ where /// Fallible version of init_service that allows testing data factory errors. pub(crate) async fn try_init_service( app: R, -) -> Result< - impl Service, Error = E>, - S::InitError, -> +) -> Result, Error = E>, S::InitError> where - R: IntoServiceFactory, + R: IntoServiceFactory, S: ServiceFactory< + Request, Config = AppConfig, - Request = Request, Response = ServiceResponse, Error = E, >, @@ -138,7 +133,7 @@ where /// ``` pub async fn call_service(app: &mut S, req: R) -> S::Response where - S: Service, Error = E>, + S: Service, Error = E>, E: std::fmt::Debug, { app.call(req).await.unwrap() @@ -171,7 +166,7 @@ where /// ``` pub async fn read_response(app: &mut S, req: Request) -> Bytes where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody + Unpin, { let mut resp = app @@ -321,7 +316,7 @@ where /// ``` pub async fn read_response_json(app: &mut S, req: Request) -> T where - S: Service, Error = Error>, + S: Service, Error = Error>, B: MessageBody + Unpin, T: DeserializeOwned, { @@ -602,7 +597,7 @@ impl TestRequest { /// Complete request creation, calls service and waits for response future completion. pub async fn send_request(self, app: &mut S) -> S::Response where - S: Service, Error = E>, + S: Service, Error = E>, E: std::fmt::Debug, { let req = self.to_request(); @@ -639,12 +634,12 @@ impl TestRequest { pub fn start(factory: F) -> TestServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { start_with(TestServerConfig::default(), factory) @@ -678,12 +673,12 @@ where pub fn start_with(cfg: TestServerConfig, factory: F) -> TestServer where F: Fn() -> I + Send + Clone + 'static, - I: IntoServiceFactory, - S: ServiceFactory + 'static, + I: IntoServiceFactory, + S: ServiceFactory + 'static, S::Error: Into + 'static, S::InitError: fmt::Debug, S::Response: Into> + 'static, - ::Future: 'static, + >::Future: 'static, B: MessageBody + 'static, { let (tx, rx) = mpsc::channel(); @@ -788,10 +783,13 @@ where }), }, } - .unwrap() - .start(); + .unwrap(); + + sys.block_on(async { + let srv = srv.start(); + tx.send((System::current(), srv, local_addr)).unwrap(); + }); - tx.send((System::current(), srv, local_addr)).unwrap(); sys.run() }); @@ -1022,7 +1020,7 @@ impl TestServer { pub async fn stop(self) { self.server.stop(true).await; self.system.stop(); - delay_for(time::Duration::from_millis(100)).await; + sleep(time::Duration::from_millis(100)).await; } } diff --git a/src/types/json.rs b/src/types/json.rs index dc0870a6e..74138ca56 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -367,7 +367,7 @@ where let json = if let Ok(Some(mime)) = req.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) - || ctype.as_ref().map_or(false, |predicate| predicate(mime)) + || ctype.map_or(false, |predicate| predicate(mime)) } else { false }; diff --git a/src/web.rs b/src/web.rs index 85e5f2e7b..39dfc450a 100644 --- a/src/web.rs +++ b/src/web.rs @@ -5,7 +5,6 @@ use std::future::Future; pub use actix_http::Response as HttpResponse; pub use bytes::{Buf, BufMut, Bytes, BytesMut}; -pub use futures_channel::oneshot::Canceled; use crate::error::BlockingError; use crate::extract::FromRequest; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index d164f4445..5eca14931 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -15,26 +15,30 @@ async fn test_start() { thread::spawn(move || { let sys = actix_rt::System::new("test"); - let srv = HttpServer::new(|| { - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), - ) - }) - .workers(1) - .backlog(1) - .max_connections(10) - .max_connection_rate(10) - .keep_alive(10) - .client_timeout(5000) - .client_shutdown(0) - .server_hostname("localhost") - .system_exit() - .disable_signals() - .bind(format!("{}", addr)) - .unwrap() - .run(); + sys.block_on(async { + let srv = HttpServer::new(|| { + App::new().service( + web::resource("/") + .route(web::to(|| HttpResponse::Ok().body("test"))), + ) + }) + .workers(1) + .backlog(1) + .max_connections(10) + .max_connection_rate(10) + .keep_alive(10) + .client_timeout(5000) + .client_shutdown(0) + .server_hostname("localhost") + .system_exit() + .disable_signals() + .bind(format!("{}", addr)) + .unwrap() + .run(); + + let _ = tx.send((srv, actix_rt::System::current())); + }); - let _ = tx.send((srv, actix_rt::System::current())); let _ = sys.run(); }); let (srv, sys) = rx.recv().unwrap(); @@ -101,10 +105,13 @@ async fn test_start_ssl() { .system_exit() .disable_signals() .bind_openssl(format!("{}", addr), builder) - .unwrap() - .run(); + .unwrap(); + + sys.block_on(async { + let srv = srv.run(); + let _ = tx.send((srv, actix_rt::System::current())); + }); - let _ = tx.send((srv, actix_rt::System::current())); let _ = sys.run(); }); let (srv, sys) = rx.recv().unwrap(); diff --git a/tests/test_server.rs b/tests/test_server.rs index c6c316f0d..2f8ce625e 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -45,7 +45,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ struct TestBody { data: Bytes, chunk_size: usize, - delay: actix_rt::time::Delay, + delay: Pin>, } impl TestBody { @@ -53,7 +53,7 @@ impl TestBody { TestBody { data, chunk_size, - delay: actix_rt::time::delay_for(std::time::Duration::from_millis(10)), + delay: Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))), } } } @@ -67,7 +67,8 @@ impl futures_core::stream::Stream for TestBody { ) -> Poll> { ready!(Pin::new(&mut self.delay).poll(cx)); - self.delay = actix_rt::time::delay_for(std::time::Duration::from_millis(10)); + self.delay = + Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))); let chunk_size = std::cmp::min(self.chunk_size, self.data.len()); let chunk = self.data.split_to(chunk_size); if chunk.is_empty() { From e1683313ec239a9ff6ebb303f62121e2b6c2b40c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 4 Jan 2021 08:32:41 +0800 Subject: [PATCH 087/187] optimize ServiceRequest methods (#1870) Co-authored-by: Rob Ede --- src/app_service.rs | 2 +- src/request.rs | 61 ++++++++++++++++++++++++++-------------------- src/service.rs | 26 +++++++++++--------- 3 files changed, 50 insertions(+), 39 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index f02bb831a..2f120cf13 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -238,7 +238,7 @@ where let (head, payload) = req.into_parts(); let req = if let Some(mut req) = self.pool.get_request() { - let inner = Rc::get_mut(&mut req.0).unwrap(); + let inner = Rc::get_mut(&mut req.inner).unwrap(); inner.path.get_mut().update(&head.uri); inner.path.reset(); inner.head = head; diff --git a/src/request.rs b/src/request.rs index 432134cd7..82304c1af 100644 --- a/src/request.rs +++ b/src/request.rs @@ -16,7 +16,12 @@ use crate::rmap::ResourceMap; #[derive(Clone)] /// An HTTP Request -pub struct HttpRequest(pub(crate) Rc); +pub struct HttpRequest { + // *. Rc is used exclusively and NO Weak + // is allowed anywhere in the code. Weak pointer is purposely ignored when + // doing Rc's ref counter check. + pub(crate) inner: Rc, +} pub(crate) struct HttpRequestInner { pub(crate) head: Message, @@ -42,15 +47,17 @@ impl HttpRequest { let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); - HttpRequest(Rc::new(HttpRequestInner { - head, - path, - payload, - rmap, - config, - app_data: data, - pool, - })) + HttpRequest { + inner: Rc::new(HttpRequestInner { + head, + path, + payload, + rmap, + config, + app_data: data, + pool, + }), + } } } @@ -58,14 +65,14 @@ impl HttpRequest { /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.0.head + &self.inner.head } /// This method returns mutable reference to the request head. /// panics if multiple references of http request exists. #[inline] pub(crate) fn head_mut(&mut self) -> &mut RequestHead { - &mut Rc::get_mut(&mut self.0).unwrap().head + &mut Rc::get_mut(&mut self.inner).unwrap().head } /// Request's uri. @@ -118,12 +125,12 @@ impl HttpRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { - &self.0.path + &self.inner.path } #[inline] pub(crate) fn match_info_mut(&mut self) -> &mut Path { - &mut Rc::get_mut(&mut self.0).unwrap().path + &mut Rc::get_mut(&mut self.inner).unwrap().path } /// The resource definition pattern that matched the path. Useful for logging and metrics. @@ -134,7 +141,7 @@ impl HttpRequest { /// Returns a None when no resource is fully matched, including default services. #[inline] pub fn match_pattern(&self) -> Option { - self.0.rmap.match_pattern(self.path()) + self.inner.rmap.match_pattern(self.path()) } /// The resource name that matched the path. Useful for logging and metrics. @@ -142,7 +149,7 @@ impl HttpRequest { /// Returns a None when no resource is fully matched, including default services. #[inline] pub fn match_name(&self) -> Option<&str> { - self.0.rmap.match_name(self.path()) + self.inner.rmap.match_name(self.path()) } /// Request extensions @@ -184,7 +191,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.0.rmap.url_for(&self, name, elements) + self.inner.rmap.url_for(&self, name, elements) } /// Generate url for named resource @@ -199,7 +206,7 @@ impl HttpRequest { #[inline] /// Get a reference to a `ResourceMap` of current application. pub fn resource_map(&self) -> &ResourceMap { - &self.0.rmap + &self.inner.rmap } /// Peer socket address @@ -225,7 +232,7 @@ impl HttpRequest { /// App config #[inline] pub fn app_config(&self) -> &AppConfig { - &self.0.config + &self.inner.config } /// Get an application data object stored with `App::data` or `App::app_data` @@ -237,7 +244,7 @@ impl HttpRequest { /// let opt_t = req.app_data::>(); /// ``` pub fn app_data(&self) -> Option<&T> { - for container in self.0.app_data.iter().rev() { + for container in self.inner.app_data.iter().rev() { if let Some(data) = container.get::() { return Some(data); } @@ -259,13 +266,13 @@ impl HttpMessage for HttpRequest { /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.0.head.extensions() + self.inner.head.extensions() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.0.head.extensions_mut() + self.inner.head.extensions_mut() } #[inline] @@ -279,7 +286,7 @@ impl Drop for HttpRequest { // if possible, contribute to current worker's HttpRequest allocation pool // This relies on no Weak exists anywhere.(There is none) - if let Some(inner) = Rc::get_mut(&mut self.0) { + if let Some(inner) = Rc::get_mut(&mut self.inner) { let v = &mut inner.pool.0.borrow_mut(); if v.len() < 128 { // clear additional app_data and keep the root one for reuse. @@ -287,7 +294,7 @@ impl Drop for HttpRequest { // inner is borrowed mut here. get head's Extension mutably // to reduce borrow check inner.head.extensions.get_mut().clear(); - v.push(self.0.clone()); + v.push(self.inner.clone()); } } } @@ -329,8 +336,8 @@ impl fmt::Debug for HttpRequest { writeln!( f, "\nHttpRequest {:?} {}:{}", - self.0.head.version, - self.0.head.method, + self.inner.head.version, + self.inner.head.method, self.path() )?; if !self.query_string().is_empty() { @@ -369,7 +376,7 @@ impl HttpRequestPool { /// Re-use a previously allocated (but now completed/discarded) HttpRequest object. #[inline] pub(crate) fn get_request(&self) -> Option { - self.0.borrow_mut().pop().map(HttpRequest) + self.0.borrow_mut().pop().map(|inner| HttpRequest { inner }) } /// Clears all allocated HttpRequest objects. diff --git a/src/service.rs b/src/service.rs index 85bc6123d..e6f71ed06 100644 --- a/src/service.rs +++ b/src/service.rs @@ -62,7 +62,7 @@ impl ServiceRequest { /// Deconstruct request into parts pub fn into_parts(mut self) -> (HttpRequest, Payload) { - let pl = Rc::get_mut(&mut (self.0).0).unwrap().payload.take(); + let pl = Rc::get_mut(&mut (self.0).inner).unwrap().payload.take(); (self.0, pl) } @@ -73,11 +73,12 @@ impl ServiceRequest { mut req: HttpRequest, pl: Payload, ) -> Result { - if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { - Rc::get_mut(&mut req.0).unwrap().payload = pl; - Ok(ServiceRequest(req)) - } else { - Err((req, pl)) + match Rc::get_mut(&mut req.inner) { + Some(p) => { + p.payload = pl; + Ok(ServiceRequest(req)) + } + None => Err((req, pl)), } } @@ -87,7 +88,10 @@ impl ServiceRequest { /// can be re-constructed only if rc's strong pointers count eq 1 and /// weak pointers count is 0. pub fn from_request(req: HttpRequest) -> Result { - if Rc::strong_count(&req.0) == 1 && Rc::weak_count(&req.0) == 0 { + // There is no weak pointer used on HttpRequest so intentionally + // ignore the check. + if Rc::strong_count(&req.inner) == 1 { + debug_assert!(Rc::weak_count(&req.inner) == 0); Ok(ServiceRequest(req)) } else { Err(req) @@ -227,7 +231,7 @@ impl ServiceRequest { /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). pub fn app_data(&self) -> Option<&T> { - for container in (self.0).0.app_data.iter().rev() { + for container in (self.0).inner.app_data.iter().rev() { if let Some(data) = container.get::() { return Some(data); } @@ -238,13 +242,13 @@ impl ServiceRequest { /// Set request payload. pub fn set_payload(&mut self, payload: Payload) { - Rc::get_mut(&mut (self.0).0).unwrap().payload = payload; + Rc::get_mut(&mut (self.0).inner).unwrap().payload = payload; } #[doc(hidden)] /// Add app data container to request's resolution set. pub fn add_data_container(&mut self, extensions: Rc) { - Rc::get_mut(&mut (self.0).0) + Rc::get_mut(&mut (self.0).inner) .unwrap() .app_data .push(extensions); @@ -280,7 +284,7 @@ impl HttpMessage for ServiceRequest { #[inline] fn take_payload(&mut self) -> Payload { - Rc::get_mut(&mut (self.0).0).unwrap().payload.take() + Rc::get_mut(&mut (self.0).inner).unwrap().payload.take() } } From 21f6c9d7a503726a4e35edf4b38283b8ed8de8e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 00:49:02 +0000 Subject: [PATCH 088/187] improve code readability --- actix-http/benches/status-line.rs | 2 +- actix-http/src/body.rs | 4 +- actix-http/src/builder.rs | 8 +- actix-http/src/client/connector.rs | 14 +- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/client/pool.rs | 2 +- actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/encoder.rs | 8 +- actix-http/src/h1/service.rs | 16 +-- actix-http/src/h2/dispatcher.rs | 207 +++++++++++++++-------------- actix-http/src/h2/mod.rs | 36 ++--- actix-http/src/h2/service.rs | 33 +++-- actix-http/src/service.rs | 18 +-- actix-http/src/ws/codec.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 +- actix-multipart/src/server.rs | 22 +-- actix-web-actors/tests/test_ws.rs | 2 +- awc/src/request.rs | 2 +- awc/src/response.rs | 12 +- awc/src/sender.rs | 4 +- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 4 +- src/app.rs | 8 +- src/app_service.rs | 4 +- src/handler.rs | 6 +- src/info.rs | 2 +- src/middleware/compress.rs | 4 +- src/middleware/logger.rs | 10 +- src/responder.rs | 4 +- src/server.rs | 10 +- src/types/payload.rs | 6 +- 34 files changed, 238 insertions(+), 230 deletions(-) diff --git a/actix-http/benches/status-line.rs b/actix-http/benches/status-line.rs index 51f840f89..252a54dea 100644 --- a/actix-http/benches/status-line.rs +++ b/actix-http/benches/status-line.rs @@ -176,7 +176,7 @@ mod _original { buf[5] = b'0'; buf[7] = b'9'; } - _ => (), + _ => {}, } let mut curr: isize = 12; diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index c5d831c45..9636f2941 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -371,7 +371,7 @@ impl MessageBody for String { pub struct BodyStream { #[pin] stream: S, - _t: PhantomData, + _phantom: PhantomData, } impl BodyStream @@ -382,7 +382,7 @@ where pub fn new(stream: S) -> Self { BodyStream { stream, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index ecb4327df..fa430c4fe 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -28,7 +28,7 @@ pub struct HttpServiceBuilder { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl HttpServiceBuilder @@ -49,7 +49,7 @@ where expect: ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -138,7 +138,7 @@ where expect: expect.into_factory(), upgrade: self.upgrade, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -163,7 +163,7 @@ where expect: self.expect, upgrade: Some(upgrade.into_factory()), on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index b638336f7..326a2fc60 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -52,7 +52,7 @@ pub struct Connector { config: ConnectorConfig, #[allow(dead_code)] ssl: SslConnector, - _t: PhantomData, + _phantom: PhantomData, } trait Io: AsyncRead + AsyncWrite + Unpin {} @@ -72,7 +72,7 @@ impl Connector<(), ()> { ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), connector: default_connector(), config: ConnectorConfig::default(), - _t: PhantomData, + _phantom: PhantomData, } } @@ -126,7 +126,7 @@ impl Connector { connector, config: self.config, ssl: self.ssl, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -468,11 +468,11 @@ mod connect_impl { match req.uri.scheme_str() { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { fut: self.ssl_pool.call(req), - _t: PhantomData, + _phantom: PhantomData, }), _ => Either::Left(InnerConnectorResponseA { fut: self.tcp_pool.call(req), - _t: PhantomData, + _phantom: PhantomData, }), } } @@ -486,7 +486,7 @@ mod connect_impl { { #[pin] fut: as Service>::Future, - _t: PhantomData, + _phantom: PhantomData, } impl Future for InnerConnectorResponseA @@ -513,7 +513,7 @@ mod connect_impl { { #[pin] fut: as Service>::Future, - _t: PhantomData, + _phantom: PhantomData, } impl Future for InnerConnectorResponseB diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 754c53968..3265394f0 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -72,7 +72,7 @@ where // send request body match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => (), + BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}, _ => send_body(body, Pin::new(&mut framed_inner)).await?, }; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 3f9a981f4..d35f9e24b 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -89,7 +89,7 @@ where CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, // DATE => has_date = true, - _ => (), + _ => {}, } req.headers_mut().append(key, value.clone()); } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index f9973a850..cee823a42 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -334,7 +334,7 @@ where let mut read_buf = ReadBuf::new(&mut buf); if let ConnectionType::H1(ref mut s) = io { match Pin::new(s).poll_read(cx, &mut read_buf) { - Poll::Pending => (), + Poll::Pending => {}, Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 8e891dc5c..d3ac497b6 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -137,7 +137,7 @@ pub(crate) trait MessageType: Sized { expect = true; } } - _ => (), + _ => {}, } headers.append(name, value); @@ -685,7 +685,7 @@ mod tests { match MessageDecoder::::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), - _ => (), + _ => {}, }, _ => unreachable!("Error expected"), } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 41caea902..41e923b6e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -736,7 +736,7 @@ where let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } - Poll::Pending => (), + Poll::Pending => {}, } Ok(()) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 4fadbb518..69800e861 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -21,7 +21,7 @@ const AVERAGE_HEADER_SIZE: usize = 30; pub(crate) struct MessageEncoder { pub length: BodySize, pub te: TransferEncoding, - _t: PhantomData, + _phantom: PhantomData, } impl Default for MessageEncoder { @@ -29,7 +29,7 @@ impl Default for MessageEncoder { MessageEncoder { length: BodySize::None, te: TransferEncoding::empty(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -118,7 +118,7 @@ pub(crate) trait MessageType: Sized { dst.put_slice(b"connection: close\r\n") } } - _ => (), + _ => {}, } // merging headers from head and extra headers. HeaderMap::new() does not allocate. @@ -148,7 +148,7 @@ pub(crate) trait MessageType: Sized { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, - _ => (), + _ => {}, } let k = key.as_str().as_bytes(); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 67f1127c7..34b7e31a1 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -30,7 +30,7 @@ pub struct H1Service { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl H1Service @@ -52,7 +52,7 @@ where expect: ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -211,7 +211,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -227,7 +227,7 @@ where srv: self.srv, expect: self.expect, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -270,7 +270,7 @@ where upgrade: None, on_connect_ext: self.on_connect_ext.clone(), cfg: Some(self.cfg.clone()), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -299,7 +299,7 @@ where upgrade: Option, on_connect_ext: Option>>, cfg: Option, - _t: PhantomData<(T, B)>, + _phantom: PhantomData<(T, B)>, } impl Future for H1ServiceResponse @@ -371,7 +371,7 @@ where upgrade: Option>, on_connect_ext: Option>>, cfg: ServiceConfig, - _t: PhantomData, + _phantom: PhantomData, } impl H1ServiceHandler @@ -398,7 +398,7 @@ where upgrade: upgrade.map(CloneableService::new), cfg, on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 4aeda942a..b8828edd0 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,14 +1,15 @@ -use std::convert::TryFrom; use std::future::Future; use std::marker::PhantomData; use std::net; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{cmp, convert::TryFrom}; use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{Instant, Sleep}; use actix_service::Service; use bytes::{Bytes, BytesMut}; +use futures_core::ready; use h2::server::{Connection, SendResponse}; use h2::SendStream; use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; @@ -27,7 +28,7 @@ use crate::Extensions; const CHUNK_SIZE: usize = 16_384; -/// Dispatcher for HTTP/2 protocol +/// Dispatcher for HTTP/2 protocol. #[pin_project::pin_project] pub struct Dispatcher where @@ -42,7 +43,7 @@ where peer_addr: Option, ka_expire: Instant, ka_timer: Option, - _t: PhantomData, + _phantom: PhantomData, } impl Dispatcher @@ -50,7 +51,6 @@ where T: AsyncRead + AsyncWrite + Unpin, S: Service, S::Error: Into, - // S::Future: 'static, S::Response: Into>, B: MessageBody, { @@ -86,7 +86,7 @@ where on_connect_data, ka_expire, ka_timer, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -107,10 +107,12 @@ where let this = self.get_mut(); loop { - match Pin::new(&mut this.connection).poll_accept(cx) { - Poll::Ready(None) => return Poll::Ready(Ok(())), - Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())), - Poll::Ready(Some(Ok((req, res)))) => { + match ready!(Pin::new(&mut this.connection).poll_accept(cx)) { + None => return Poll::Ready(Ok(())), + + Some(Err(err)) => return Poll::Ready(Err(err.into())), + + Some(Ok((req, res))) => { // update keep-alive expire if this.ka_timer.is_some() { if let Some(expire) = this.config.keep_alive_expire() { @@ -119,11 +121,9 @@ where } let (parts, body) = req.into_parts(); - let mut req = Request::with_payload(Payload::< - crate::payload::PayloadStream, - >::H2( - crate::h2::Payload::new(body) - )); + let pl = crate::h2::Payload::new(body); + let pl = Payload::::H2(pl); + let mut req = Request::with_payload(pl); let head = &mut req.head_mut(); head.uri = parts.uri; @@ -135,22 +135,18 @@ where // merge on_connect_ext data into request extensions req.extensions_mut().drain_from(&mut this.on_connect_data); - actix_rt::spawn(ServiceResponse::< - S::Future, - S::Response, - S::Error, - B, - > { + let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( this.service.call(req), Some(res), ), config: this.config.clone(), buffer: None, - _t: PhantomData, - }); + _phantom: PhantomData, + }; + + actix_rt::spawn(svc); } - Poll::Pending => return Poll::Pending, } } } @@ -162,7 +158,7 @@ struct ServiceResponse { state: ServiceResponseState, config: ServiceConfig, buffer: Option, - _t: PhantomData<(I, E)>, + _phantom: PhantomData<(I, E)>, } #[pin_project::pin_project(project = ServiceResponseStateProj)] @@ -199,8 +195,9 @@ where skip_len = true; *size = BodySize::Stream; } - _ => (), + _ => {} } + let _ = match size { BodySize::None | BodySize::Stream => None, BodySize::Empty => res @@ -215,11 +212,13 @@ where // copy headers for (key, value) in head.headers.iter() { match *key { - CONNECTION | TRANSFER_ENCODING => continue, // http2 specific + // omit HTTP/1 only headers + CONNECTION | TRANSFER_ENCODING => continue, CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, - _ => (), + _ => {} } + res.headers_mut().append(key, value.clone()); } @@ -251,109 +250,117 @@ where let mut this = self.as_mut().project(); match this.state.project() { - ServiceResponseStateProj::ServiceCall(call, send) => match call.poll(cx) { - Poll::Ready(Ok(res)) => { - let (res, body) = res.into().replace_body(()); + ServiceResponseStateProj::ServiceCall(call, send) => { + match ready!(call.poll(cx)) { + Ok(res) => { + let (res, body) = res.into().replace_body(()); - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); + let mut send = send.take().unwrap(); + let mut size = body.size(); + let h2_res = + self.as_mut().prepare_response(res.head(), &mut size); + this = self.as_mut().project(); - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending h2 response: {:?}", e); - return Poll::Ready(()); + let stream = match send.send_response(h2_res, size.is_eof()) { + Err(e) => { + trace!("Error sending HTTP/2 response: {:?}", e); + return Poll::Ready(()); + } + Ok(stream) => stream, + }; + + if size.is_eof() { + Poll::Ready(()) + } else { + this.state + .set(ServiceResponseState::SendPayload(stream, body)); + self.poll(cx) } - Ok(stream) => stream, - }; + } - if size.is_eof() { - Poll::Ready(()) - } else { - this.state - .set(ServiceResponseState::SendPayload(stream, body)); - self.poll(cx) + Err(e) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + + let mut send = send.take().unwrap(); + let mut size = body.size(); + let h2_res = + self.as_mut().prepare_response(res.head(), &mut size); + this = self.as_mut().project(); + + let stream = match send.send_response(h2_res, size.is_eof()) { + Err(e) => { + trace!("Error sending HTTP/2 response: {:?}", e); + return Poll::Ready(()); + } + Ok(stream) => stream, + }; + + if size.is_eof() { + Poll::Ready(()) + } else { + this.state.set(ServiceResponseState::SendPayload( + stream, + body.into_body(), + )); + self.poll(cx) + } } } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); + } - let mut send = send.take().unwrap(); - let mut size = body.size(); - let h2_res = self.as_mut().prepare_response(res.head(), &mut size); - this = self.as_mut().project(); - - let stream = match send.send_response(h2_res, size.is_eof()) { - Err(e) => { - trace!("Error sending h2 response: {:?}", e); - return Poll::Ready(()); - } - Ok(stream) => stream, - }; - - if size.is_eof() { - Poll::Ready(()) - } else { - this.state.set(ServiceResponseState::SendPayload( - stream, - body.into_body(), - )); - self.poll(cx) - } - } - }, ServiceResponseStateProj::SendPayload(ref mut stream, ref mut body) => { loop { loop { - if let Some(ref mut buffer) = this.buffer { - match stream.poll_capacity(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => return Poll::Ready(()), - Poll::Ready(Some(Ok(cap))) => { - let len = buffer.len(); - let bytes = buffer.split_to(std::cmp::min(cap, len)); + match this.buffer { + Some(ref mut buffer) => { + match ready!(stream.poll_capacity(cx)) { + None => return Poll::Ready(()), - if let Err(e) = stream.send_data(bytes, false) { + Some(Ok(cap)) => { + let len = buffer.len(); + let bytes = buffer.split_to(cmp::min(cap, len)); + + if let Err(e) = stream.send_data(bytes, false) { + warn!("{:?}", e); + return Poll::Ready(()); + } else if !buffer.is_empty() { + let cap = cmp::min(buffer.len(), CHUNK_SIZE); + stream.reserve_capacity(cap); + } else { + this.buffer.take(); + } + } + + Some(Err(e)) => { warn!("{:?}", e); return Poll::Ready(()); - } else if !buffer.is_empty() { - let cap = - std::cmp::min(buffer.len(), CHUNK_SIZE); - stream.reserve_capacity(cap); - } else { - this.buffer.take(); } } - Poll::Ready(Some(Err(e))) => { - warn!("{:?}", e); - return Poll::Ready(()); - } } - } else { - match body.as_mut().poll_next(cx) { - Poll::Pending => return Poll::Pending, - Poll::Ready(None) => { + + None => match ready!(body.as_mut().poll_next(cx)) { + None => { if let Err(e) = stream.send_data(Bytes::new(), true) { warn!("{:?}", e); } return Poll::Ready(()); } - Poll::Ready(Some(Ok(chunk))) => { - stream.reserve_capacity(std::cmp::min( + + Some(Ok(chunk)) => { + stream.reserve_capacity(cmp::min( chunk.len(), CHUNK_SIZE, )); *this.buffer = Some(chunk); } - Poll::Ready(Some(Err(e))) => { + + Some(Err(e)) => { error!("Response payload stream error: {:?}", e); return Poll::Ready(()); } - } + }, } } } diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index b00969227..c05ee609d 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -1,9 +1,12 @@ -//! HTTP/2 implementation -use std::pin::Pin; -use std::task::{Context, Poll}; +//! HTTP/2 implementation. + +use std::{ + pin::Pin, + task::{Context, Poll}, +}; use bytes::Bytes; -use futures_core::Stream; +use futures_core::{ready, Stream}; use h2::RecvStream; mod dispatcher; @@ -13,14 +16,14 @@ pub use self::dispatcher::Dispatcher; pub use self::service::H2Service; use crate::error::PayloadError; -/// H2 receive stream +/// HTTP/2 peer stream. pub struct Payload { - pl: RecvStream, + stream: RecvStream, } impl Payload { - pub(crate) fn new(pl: RecvStream) -> Self { - Self { pl } + pub(crate) fn new(stream: RecvStream) -> Self { + Self { stream } } } @@ -33,18 +36,17 @@ impl Stream for Payload { ) -> Poll> { let this = self.get_mut(); - match Pin::new(&mut this.pl).poll_data(cx) { - Poll::Ready(Some(Ok(chunk))) => { + match ready!(Pin::new(&mut this.stream).poll_data(cx)) { + Some(Ok(chunk)) => { let len = chunk.len(); - if let Err(err) = this.pl.flow_control().release_capacity(len) { - Poll::Ready(Some(Err(err.into()))) - } else { - Poll::Ready(Some(Ok(chunk))) + + match this.stream.flow_control().release_capacity(len) { + Ok(()) => Poll::Ready(Some(Ok(chunk))), + Err(err) => Poll::Ready(Some(Err(err.into()))), } } - Poll::Ready(Some(Err(err))) => Poll::Ready(Some(Err(err.into()))), - Poll::Pending => Poll::Pending, - Poll::Ready(None) => Poll::Ready(None), + Some(Err(err)) => Poll::Ready(Some(Err(err.into()))), + None => Poll::Ready(None), } } } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 719f3622c..462f5c2c1 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -26,12 +26,12 @@ use crate::{ConnectCallback, Extensions}; use super::dispatcher::Dispatcher; -/// `ServiceFactory` implementation for HTTP2 transport +/// `ServiceFactory` implementation for HTTP/2 transport pub struct H2Service { srv: S, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData<(T, B)>, + _phantom: PhantomData<(T, B)>, } impl H2Service @@ -42,7 +42,7 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create new `HttpService` instance with config. + /// Create new `H2Service` instance with config. pub(crate) fn with_config>( cfg: ServiceConfig, service: F, @@ -51,7 +51,7 @@ where cfg, on_connect_ext: None, srv: service.into_factory(), - _t: PhantomData, + _phantom: PhantomData, } } @@ -70,7 +70,7 @@ where >::Future: 'static, B: MessageBody + 'static, { - /// Create simple tcp based service + /// Create plain TCP based service pub fn tcp( self, ) -> impl ServiceFactory< @@ -106,7 +106,7 @@ mod openssl { >::Future: 'static, B: MessageBody + 'static, { - /// Create ssl based service + /// Create OpenSSL based service pub fn openssl( self, acceptor: SslAcceptor, @@ -149,7 +149,7 @@ mod rustls { >::Future: 'static, B: MessageBody + 'static, { - /// Create openssl based service + /// Create Rustls based service pub fn rustls( self, mut config: ServerConfig, @@ -200,7 +200,7 @@ where fut: self.srv.new_service(()), cfg: Some(self.cfg.clone()), on_connect_ext: self.on_connect_ext.clone(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -215,7 +215,7 @@ where fut: S::Future, cfg: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl Future for H2ServiceResponse @@ -232,14 +232,14 @@ where fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.as_mut().project(); - Poll::Ready(ready!(this.fut.poll(cx)).map(|service| { + this.fut.poll(cx).map_ok(|service| { let this = self.as_mut().project(); H2ServiceHandler::new( this.cfg.take().unwrap(), this.on_connect_ext.clone(), service, ) - })) + }) } } @@ -251,7 +251,7 @@ where srv: CloneableService, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl H2ServiceHandler @@ -271,7 +271,7 @@ where cfg, on_connect_ext, srv: CloneableService::new(srv), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -363,8 +363,8 @@ where ref peer_addr, ref mut on_connect_data, ref mut handshake, - ) => match Pin::new(handshake).poll(cx) { - Poll::Ready(Ok(conn)) => { + ) => match ready!(Pin::new(handshake).poll(cx)) { + Ok(conn) => { self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, @@ -375,11 +375,10 @@ where )); self.poll(cx) } - Poll::Ready(Err(err)) => { + Err(err) => { trace!("H2 handshake error: {}", err); Poll::Ready(Err(err.into())) } - Poll::Pending => Poll::Pending, }, } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f23115cd5..af625b1bf 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -28,7 +28,7 @@ pub struct HttpService { expect: X, upgrade: Option, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl HttpService @@ -65,7 +65,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } @@ -80,7 +80,7 @@ where expect: h1::ExpectHandler, upgrade: None, on_connect_ext: None, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -112,7 +112,7 @@ where srv: self.srv, upgrade: self.upgrade, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -133,7 +133,7 @@ where srv: self.srv, expect: self.expect, on_connect_ext: self.on_connect_ext, - _t: PhantomData, + _phantom: PhantomData, } } @@ -348,7 +348,7 @@ where upgrade: None, on_connect_ext: self.on_connect_ext.clone(), cfg: self.cfg.clone(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -371,7 +371,7 @@ where upgrade: Option, on_connect_ext: Option>>, cfg: ServiceConfig, - _t: PhantomData<(T, B)>, + _phantom: PhantomData<(T, B)>, } impl Future for HttpServiceResponse @@ -446,7 +446,7 @@ where upgrade: Option>, cfg: ServiceConfig, on_connect_ext: Option>>, - _t: PhantomData, + _phantom: PhantomData, } impl HttpServiceHandler @@ -474,7 +474,7 @@ where srv: CloneableService::new(srv), expect: CloneableService::new(expect), upgrade: upgrade.map(CloneableService::new), - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 7c9628b1a..bb68a4d76 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -184,7 +184,7 @@ impl Encoder for Codec { } } }, - Message::Nop => (), + Message::Nop => {}, } Ok(()) } diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 0598a9b4e..25231b2f3 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -125,7 +125,7 @@ impl Parser { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); return Ok(Some((true, OpCode::Close, None))); } - _ => (), + _ => {}, } // unmask diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index fc271a8f5..06db60c67 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -222,7 +222,7 @@ mod test { macro_rules! opcode_into { ($from:expr => $opcode:pat) => { match OpCode::from($from) { - e @ $opcode => (), + e @ $opcode => {}, e => unreachable!("{:?}", e), } }; @@ -232,7 +232,7 @@ mod test { ($from:expr => $opcode:pat) => { let res: u8 = $from.into(); match res { - e @ $opcode => (), + e @ $opcode => {}, e => unreachable!("{:?}", e), } }; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index b476f1791..73b2756e5 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -326,7 +326,7 @@ impl InnerMultipart { } } } - _ => (), + _ => {}, } // read field headers for next field @@ -835,7 +835,7 @@ mod tests { async fn test_boundary() { let headers = HeaderMap::new(); match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => (), + Err(MultipartError::NoContentType) => {}, _ => unreachable!("should not happen"), } @@ -846,7 +846,7 @@ mod tests { ); match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => (), + Err(MultipartError::ParseContentType) => {}, _ => unreachable!("should not happen"), } @@ -856,7 +856,7 @@ mod tests { header::HeaderValue::from_static("multipart/mixed"), ); match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => (), + Err(MultipartError::Boundary) => {}, _ => unreachable!("should not happen"), } @@ -956,17 +956,17 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); match multipart.next().await.unwrap() { - Ok(_) => (), + Ok(_) => {}, _ => unreachable!(), } match multipart.next().await.unwrap() { - Ok(_) => (), + Ok(_) => {}, _ => unreachable!(), } match multipart.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -993,7 +993,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -1010,7 +1010,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -1018,7 +1018,7 @@ mod tests { } match multipart.next().await { - None => (), + None => {}, _ => unreachable!(), } } @@ -1066,7 +1066,7 @@ mod tests { } match multipart.next().await { - None => (), + None => {}, _ => unreachable!(), } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index dda9f6f0b..a8ade9ff4 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -21,7 +21,7 @@ impl StreamHandler> for Ws { ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Close(reason) => ctx.close(reason), - _ => (), + _ => {}, } } } diff --git a/awc/src/request.rs b/awc/src/request.rs index 1e49aae3c..abfd39351 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -523,7 +523,7 @@ impl ClientRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => (), + "http" | "ws" | "https" | "wss" => {}, _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/src/response.rs b/awc/src/response.rs index 8364aa556..c9886252e 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -234,7 +234,7 @@ pub struct JsonBody { length: Option, err: Option, fut: Option>, - _t: PhantomData, + _phantom: PhantomData, } impl JsonBody @@ -255,7 +255,7 @@ where length: None, fut: None, err: Some(JsonPayloadError::ContentType), - _t: PhantomData, + _phantom: PhantomData, }; } @@ -272,7 +272,7 @@ where length: len, err: None, fut: Some(ReadBody::new(req.take_payload(), 65536)), - _t: PhantomData, + _phantom: PhantomData, } } @@ -370,14 +370,14 @@ mod tests { async fn test_body() { let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().await.err().unwrap() { - PayloadError::UnknownLength => (), + PayloadError::UnknownLength => {}, _ => unreachable!("error"), } let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().await.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } @@ -390,7 +390,7 @@ mod tests { .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index b5ff61da5..ef65c18dd 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -86,7 +86,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, response_decompress) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => (), + Poll::Pending => {}, _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } @@ -127,7 +127,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, _) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => (), + Poll::Pending => {}, _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index dd43d08b3..9065302d9 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -259,7 +259,7 @@ impl WebsocketsRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => (), + "http" | "ws" | "https" | "wss" => {}, _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 1b7413312..114563b31 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -127,7 +127,7 @@ async fn test_timeout() { let request = client.get(srv.url("/")).send(); match request.await { - Err(SendRequestError::Timeout) => (), + Err(SendRequestError::Timeout) => {}, _ => panic!(), } } @@ -149,7 +149,7 @@ async fn test_timeout_override() { .timeout(Duration::from_millis(50)) .send(); match request.await { - Err(SendRequestError::Timeout) => (), + Err(SendRequestError::Timeout) => {}, _ => panic!(), } } diff --git a/src/app.rs b/src/app.rs index d41d692ee..fc336e857 100644 --- a/src/app.rs +++ b/src/app.rs @@ -38,7 +38,7 @@ pub struct App { data_factories: Vec, external: Vec, extensions: Extensions, - _t: PhantomData, + _phantom: PhantomData, } impl App { @@ -55,7 +55,7 @@ impl App { factory_ref: fref, external: Vec::new(), extensions: Extensions::new(), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -381,7 +381,7 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _t: PhantomData, + _phantom: PhantomData, } } @@ -443,7 +443,7 @@ where factory_ref: self.factory_ref, external: self.external, extensions: self.extensions, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/src/app_service.rs b/src/app_service.rs index 2f120cf13..686be6312 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -123,7 +123,7 @@ where ), config, rmap, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -148,7 +148,7 @@ where data: Rc<[Box]>, extensions: Option, - _t: PhantomData, + _phantom: PhantomData, } impl Future for AppInitResult diff --git a/src/handler.rs b/src/handler.rs index 14e8cb40b..30cc59842 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -49,7 +49,7 @@ where R::Output: Responder, { hnd: F, - _t: PhantomData<(T, R)>, + _phantom: PhantomData<(T, R)>, } impl HandlerService @@ -62,7 +62,7 @@ where pub fn new(hnd: F) -> Self { Self { hnd, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -77,7 +77,7 @@ where fn clone(&self) -> Self { Self { hnd: self.hnd.clone(), - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/src/info.rs b/src/info.rs index 975604041..cefe87eb4 100644 --- a/src/info.rs +++ b/src/info.rs @@ -55,7 +55,7 @@ impl ConnectionInfo { host = Some(val.trim()); } } - _ => (), + _ => {}, } } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index faff5003a..b6ad2c4b5 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -104,7 +104,7 @@ where CompressResponse { encoding, fut: self.service.call(req), - _t: PhantomData, + _phantom: PhantomData, } } } @@ -119,7 +119,7 @@ where #[pin] fut: S::Future, encoding: ContentEncoding, - _t: PhantomData, + _phantom: PhantomData, } impl Future for CompressResponse diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 2a543f66f..83ff5b9ec 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -236,7 +236,7 @@ where fut: self.service.call(req), format: None, time: OffsetDateTime::now_utc(), - _t: PhantomData, + _phantom: PhantomData, } } else { let now = OffsetDateTime::now_utc(); @@ -249,7 +249,7 @@ where fut: self.service.call(req), format: Some(format), time: now, - _t: PhantomData, + _phantom: PhantomData, } } } @@ -266,7 +266,7 @@ where fut: S::Future, time: OffsetDateTime, format: Option, - _t: PhantomData, + _phantom: PhantomData, } impl Future for LoggerResponse @@ -522,7 +522,7 @@ impl FormatText { }; *self = FormatText::Str(s.to_string()) } - _ => (), + _ => {}, } } @@ -587,7 +587,7 @@ impl FormatText { *self = s; } - _ => (), + _ => {}, } } } diff --git a/src/responder.rs b/src/responder.rs index d1c22323f..58e33f39d 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -349,14 +349,14 @@ where pub struct ResponseFuture { #[pin] fut: T, - _t: PhantomData, + _phantom: PhantomData, } impl ResponseFuture { pub fn new(fut: T) -> Self { ResponseFuture { fut, - _t: PhantomData, + _phantom: PhantomData, } } } diff --git a/src/server.rs b/src/server.rs index fc80cbed8..26089ccba 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,6 +1,6 @@ use std::{ any::Any, - fmt, io, + cmp, fmt, io, marker::PhantomData, net, sync::{Arc, Mutex}, @@ -71,7 +71,7 @@ where sockets: Vec, builder: ServerBuilder, on_connect_fn: Option>, - _t: PhantomData<(S, B)>, + _phantom: PhantomData<(S, B)>, } impl HttpServer @@ -100,7 +100,7 @@ where sockets: Vec::new(), builder: ServerBuilder::default(), on_connect_fn: None, - _t: PhantomData, + _phantom: PhantomData, } } @@ -125,7 +125,7 @@ where sockets: self.sockets, builder: self.builder, on_connect_fn: Some(Arc::new(f)), - _t: PhantomData, + _phantom: PhantomData, } } @@ -653,7 +653,7 @@ fn create_tcp_listener( socket.set_reuse_address(true)?; socket.bind(&addr.into())?; // clamp backlog to max u32 that fits in i32 range - let backlog = backlog.min(i32::MAX as u32) as i32; + let backlog = cmp::min(backlog, i32::MAX as u32) as i32; socket.listen(backlog)?; Ok(socket.into_tcp_listener()) } diff --git a/src/types/payload.rs b/src/types/payload.rs index 9228b37aa..1b1c1ad73 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -539,7 +539,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::UnknownLength => (), + PayloadError::UnknownLength => {}, _ => unreachable!("error"), } @@ -548,7 +548,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } @@ -563,7 +563,7 @@ mod tests { .to_http_parts(); let res = HttpMessageBody::new(&req, &mut pl).limit(5).await; match res.err().unwrap() { - PayloadError::Overflow => (), + PayloadError::Overflow => {}, _ => unreachable!("error"), } } From 2d4a17442011fb01c685cdef298cae6887f5de56 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 01:01:35 +0000 Subject: [PATCH 089/187] fmt --- actix-http/benches/status-line.rs | 2 +- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/client/pool.rs | 2 +- actix-http/src/h1/decoder.rs | 4 ++-- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/encoder.rs | 4 ++-- actix-http/src/ws/codec.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 ++-- actix-multipart/src/server.rs | 22 +++++++++++----------- actix-web-actors/tests/test_ws.rs | 2 +- awc/src/request.rs | 2 +- awc/src/response.rs | 6 +++--- awc/src/sender.rs | 4 ++-- awc/src/ws.rs | 2 +- awc/tests/test_client.rs | 4 ++-- src/info.rs | 2 +- src/middleware/logger.rs | 4 ++-- src/types/payload.rs | 6 +++--- 20 files changed, 40 insertions(+), 40 deletions(-) diff --git a/actix-http/benches/status-line.rs b/actix-http/benches/status-line.rs index 252a54dea..f62d18ed8 100644 --- a/actix-http/benches/status-line.rs +++ b/actix-http/benches/status-line.rs @@ -176,7 +176,7 @@ mod _original { buf[5] = b'0'; buf[7] = b'9'; } - _ => {}, + _ => {} } let mut curr: isize = 12; diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 3265394f0..758ad8424 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -72,7 +72,7 @@ where // send request body match body.size() { - BodySize::None | BodySize::Empty | BodySize::Sized(0) => {}, + BodySize::None | BodySize::Empty | BodySize::Sized(0) => {} _ => send_body(body, Pin::new(&mut framed_inner)).await?, }; diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index d35f9e24b..4c609ef22 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -89,7 +89,7 @@ where CONNECTION | TRANSFER_ENCODING => continue, // http2 specific CONTENT_LENGTH if skip_len => continue, // DATE => has_date = true, - _ => {}, + _ => {} } req.headers_mut().append(key, value.clone()); } diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index cee823a42..7da2b6234 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -334,7 +334,7 @@ where let mut read_buf = ReadBuf::new(&mut buf); if let ConnectionType::H1(ref mut s) = io { match Pin::new(s).poll_read(cx, &mut read_buf) { - Poll::Pending => {}, + Poll::Pending => {} Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index d3ac497b6..85379b084 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -137,7 +137,7 @@ pub(crate) trait MessageType: Sized { expect = true; } } - _ => {}, + _ => {} } headers.append(name, value); @@ -685,7 +685,7 @@ mod tests { match MessageDecoder::::default().decode($e) { Err(err) => match err { ParseError::Io(_) => unreachable!("Parse error expected"), - _ => {}, + _ => {} }, _ => unreachable!("Error expected"), } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 41e923b6e..a9510dc1e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -736,7 +736,7 @@ where let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); } } - Poll::Pending => {}, + Poll::Pending => {} } Ok(()) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 69800e861..4427174ec 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -118,7 +118,7 @@ pub(crate) trait MessageType: Sized { dst.put_slice(b"connection: close\r\n") } } - _ => {}, + _ => {} } // merging headers from head and extra headers. HeaderMap::new() does not allocate. @@ -148,7 +148,7 @@ pub(crate) trait MessageType: Sized { CONNECTION => continue, TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, DATE => has_date = true, - _ => {}, + _ => {} } let k = key.as_str().as_bytes(); diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index bb68a4d76..ba4a48bba 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -184,7 +184,7 @@ impl Encoder for Codec { } } }, - Message::Nop => {}, + Message::Nop => {} } Ok(()) } diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 25231b2f3..8c49524e6 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -125,7 +125,7 @@ impl Parser { debug!("Received close frame with payload length exceeding 125. Morphing to protocol close frame."); return Ok(Some((true, OpCode::Close, None))); } - _ => {}, + _ => {} } // unmask diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 06db60c67..345681429 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -222,7 +222,7 @@ mod test { macro_rules! opcode_into { ($from:expr => $opcode:pat) => { match OpCode::from($from) { - e @ $opcode => {}, + e @ $opcode => {} e => unreachable!("{:?}", e), } }; @@ -232,7 +232,7 @@ mod test { ($from:expr => $opcode:pat) => { let res: u8 = $from.into(); match res { - e @ $opcode => {}, + e @ $opcode => {} e => unreachable!("{:?}", e), } }; diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 73b2756e5..2cde5e8cf 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -326,7 +326,7 @@ impl InnerMultipart { } } } - _ => {}, + _ => {} } // read field headers for next field @@ -835,7 +835,7 @@ mod tests { async fn test_boundary() { let headers = HeaderMap::new(); match Multipart::boundary(&headers) { - Err(MultipartError::NoContentType) => {}, + Err(MultipartError::NoContentType) => {} _ => unreachable!("should not happen"), } @@ -846,7 +846,7 @@ mod tests { ); match Multipart::boundary(&headers) { - Err(MultipartError::ParseContentType) => {}, + Err(MultipartError::ParseContentType) => {} _ => unreachable!("should not happen"), } @@ -856,7 +856,7 @@ mod tests { header::HeaderValue::from_static("multipart/mixed"), ); match Multipart::boundary(&headers) { - Err(MultipartError::Boundary) => {}, + Err(MultipartError::Boundary) => {} _ => unreachable!("should not happen"), } @@ -956,17 +956,17 @@ mod tests { let mut multipart = Multipart::new(&headers, payload); match multipart.next().await.unwrap() { - Ok(_) => {}, + Ok(_) => {} _ => unreachable!(), } match multipart.next().await.unwrap() { - Ok(_) => {}, + Ok(_) => {} _ => unreachable!(), } match multipart.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -993,7 +993,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -1010,7 +1010,7 @@ mod tests { _ => unreachable!(), } match field.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -1018,7 +1018,7 @@ mod tests { } match multipart.next().await { - None => {}, + None => {} _ => unreachable!(), } } @@ -1066,7 +1066,7 @@ mod tests { } match multipart.next().await { - None => {}, + None => {} _ => unreachable!(), } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a8ade9ff4..a5233e5e0 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -21,7 +21,7 @@ impl StreamHandler> for Ws { ws::Message::Text(text) => ctx.text(text), ws::Message::Binary(bin) => ctx.binary(bin), ws::Message::Close(reason) => ctx.close(reason), - _ => {}, + _ => {} } } } diff --git a/awc/src/request.rs b/awc/src/request.rs index abfd39351..51c3f5190 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -523,7 +523,7 @@ impl ClientRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => {}, + "http" | "ws" | "https" | "wss" => {} _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/src/response.rs b/awc/src/response.rs index c9886252e..a32412b23 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -370,14 +370,14 @@ mod tests { async fn test_body() { let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "xxxx").finish(); match req.body().await.err().unwrap() { - PayloadError::UnknownLength => {}, + PayloadError::UnknownLength => {} _ => unreachable!("error"), } let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().await.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } @@ -390,7 +390,7 @@ mod tests { .set_payload(Bytes::from_static(b"11111111111111")) .finish(); match req.body().limit(5).await.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index ef65c18dd..d4d3d9b72 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -86,7 +86,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, response_decompress) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {}, + Poll::Pending => {} _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } @@ -127,7 +127,7 @@ impl Future for SendClientRequest { SendClientRequest::Fut(send, delay, _) => { if delay.is_some() { match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {}, + Poll::Pending => {} _ => return Poll::Ready(Err(SendRequestError::Timeout)), } } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 9065302d9..a1fa07d8d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -259,7 +259,7 @@ impl WebsocketsRequest { return Err(InvalidUrl::MissingScheme.into()); } else if let Some(scheme) = uri.scheme() { match scheme.as_str() { - "http" | "ws" | "https" | "wss" => {}, + "http" | "ws" | "https" | "wss" => {} _ => return Err(InvalidUrl::UnknownScheme.into()), } } else { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 114563b31..6cae77a49 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -127,7 +127,7 @@ async fn test_timeout() { let request = client.get(srv.url("/")).send(); match request.await { - Err(SendRequestError::Timeout) => {}, + Err(SendRequestError::Timeout) => {} _ => panic!(), } } @@ -149,7 +149,7 @@ async fn test_timeout_override() { .timeout(Duration::from_millis(50)) .send(); match request.await { - Err(SendRequestError::Timeout) => {}, + Err(SendRequestError::Timeout) => {} _ => panic!(), } } diff --git a/src/info.rs b/src/info.rs index cefe87eb4..75ebf67eb 100644 --- a/src/info.rs +++ b/src/info.rs @@ -55,7 +55,7 @@ impl ConnectionInfo { host = Some(val.trim()); } } - _ => {}, + _ => {} } } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 83ff5b9ec..c952aeac9 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -522,7 +522,7 @@ impl FormatText { }; *self = FormatText::Str(s.to_string()) } - _ => {}, + _ => {} } } @@ -587,7 +587,7 @@ impl FormatText { *self = s; } - _ => {}, + _ => {} } } } diff --git a/src/types/payload.rs b/src/types/payload.rs index 1b1c1ad73..14457176d 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -539,7 +539,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::UnknownLength => {}, + PayloadError::UnknownLength => {} _ => unreachable!("error"), } @@ -548,7 +548,7 @@ mod tests { .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; match res.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } @@ -563,7 +563,7 @@ mod tests { .to_http_parts(); let res = HttpMessageBody::new(&req, &mut pl).limit(5).await; match res.err().unwrap() { - PayloadError::Overflow => {}, + PayloadError::Overflow => {} _ => unreachable!("error"), } } From 007a1459884ea36c97a2e8cf2f29ec9b63396b4e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 04:29:07 +0000 Subject: [PATCH 090/187] use ahash for internal hashmaps --- Cargo.toml | 2 +- src/middleware/errhandlers.rs | 8 ++++---- src/rmap.rs | 6 +++--- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 165004447..5388de4ed 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -87,12 +87,12 @@ actix-web-codegen = "0.4.0" actix-http = "2.2.0" awc = { version = "2.0.3", default-features = false } +ahash = "0.6" bytes = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } -fxhash = "0.2.1" log = "0.4" mime = "0.3" socket2 = "0.3.16" diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index 9e78bb7d0..d25b9c2ef 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -4,7 +4,7 @@ use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; -use fxhash::FxHashMap; +use ahash::AHashMap; use crate::dev::{ServiceRequest, ServiceResponse}; use crate::error::{Error, Result}; @@ -52,13 +52,13 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result { - handlers: Rc>>>, + handlers: Rc>>>, } impl Default for ErrorHandlers { fn default() -> Self { ErrorHandlers { - handlers: Rc::new(FxHashMap::default()), + handlers: Rc::new(AHashMap::default()), } } } @@ -104,7 +104,7 @@ where #[doc(hidden)] pub struct ErrorHandlersMiddleware { service: S, - handlers: Rc>>>, + handlers: Rc>>>, } impl Service for ErrorHandlersMiddleware diff --git a/src/rmap.rs b/src/rmap.rs index 6827a11b2..3c8805d57 100644 --- a/src/rmap.rs +++ b/src/rmap.rs @@ -2,7 +2,7 @@ use std::cell::RefCell; use std::rc::{Rc, Weak}; use actix_router::ResourceDef; -use fxhash::FxHashMap; +use ahash::AHashMap; use url::Url; use crate::error::UrlGenerationError; @@ -12,7 +12,7 @@ use crate::request::HttpRequest; pub struct ResourceMap { root: ResourceDef, parent: RefCell>, - named: FxHashMap, + named: AHashMap, patterns: Vec<(ResourceDef, Option>)>, } @@ -21,7 +21,7 @@ impl ResourceMap { ResourceMap { root, parent: RefCell::new(Weak::new()), - named: FxHashMap::default(), + named: AHashMap::default(), patterns: Vec::new(), } } From 36aee18c64d0c1f79b796f2eca2d499cee56e6c1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 04:33:15 +0000 Subject: [PATCH 091/187] fmt --- src/middleware/errhandlers.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/middleware/errhandlers.rs b/src/middleware/errhandlers.rs index d25b9c2ef..8503b3e1e 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/errhandlers.rs @@ -3,8 +3,8 @@ use std::rc::Rc; use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use ahash::AHashMap; +use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; use crate::dev::{ServiceRequest, ServiceResponse}; use crate::error::{Error, Result}; From 7d632d0b7b19baa761e94ba82c48cea292b31a59 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 4 Jan 2021 11:27:32 +0000 Subject: [PATCH 092/187] use ByteString as container for websocket text message (#1864) --- actix-http/CHANGES.md | 8 +-- actix-http/Cargo.toml | 3 +- actix-http/src/ws/codec.rs | 57 +++++++++++------- actix-http/src/ws/mod.rs | 99 ++++++++++++++++++------------- actix-http/src/ws/proto.rs | 8 ++- actix-http/tests/test_ws.rs | 7 +-- actix-web-actors/CHANGES.md | 2 + actix-web-actors/Cargo.toml | 1 + actix-web-actors/src/ws.rs | 32 +++++----- actix-web-actors/tests/test_ws.rs | 5 +- awc/src/lib.rs | 2 +- awc/src/ws.rs | 2 +- awc/tests/test_ws.rs | 7 +-- 13 files changed, 131 insertions(+), 102 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 147285ddf..eadbf6f46 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,14 +2,11 @@ ## Unreleased - 2021-xx-xx ### Changed -* Bumped `rand` to `0.8`. * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] +* Bumped `rand` to `0.8`. * Update `bytes` to `1.0`. [#1813] * Update `h2` to `0.3`. [#1813] - - -[#1813]: https://github.com/actix/actix-web/pull/1813 - +* The `ws::Message::Text` enum variant now contains a `bytestring::ByteString`. [#1864] ### Removed * Deprecated `on_connect` methods have been removed. Prefer the new @@ -22,6 +19,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 [#1857]: https://github.com/actix/actix-web/pull/1857 +[#1864]: https://github.com/actix/actix-web/pull/1864 ## 2.2.0 - 2020-11-25 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e98bcf76d..e80800d06 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -51,9 +51,10 @@ actix = { version = "0.11.0-beta.1", optional = true } base64 = "0.13" bitflags = "1.2" bytes = "1" +bytestring = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } copyless = "0.1.4" -derive_more = "0.99.2" +derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index ba4a48bba..84f5b3c73 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -1,47 +1,60 @@ use actix_codec::{Decoder, Encoder}; +use bitflags::bitflags; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use super::frame::Parser; use super::proto::{CloseReason, OpCode}; use super::ProtocolError; -/// `WebSocket` Message +/// A WebSocket message. #[derive(Debug, PartialEq)] pub enum Message { - /// Text message - Text(String), - /// Binary message + /// Text message. + Text(ByteString), + + /// Binary message. Binary(Bytes), - /// Continuation + + /// Continuation. Continuation(Item), - /// Ping message + + /// Ping message. Ping(Bytes), - /// Pong message + + /// Pong message. Pong(Bytes), - /// Close message with optional reason + + /// Close message with optional reason. Close(Option), - /// No-op. Useful for actix-net services + + /// No-op. Useful for low-level services. Nop, } -/// `WebSocket` frame +/// A WebSocket frame. #[derive(Debug, PartialEq)] pub enum Frame { - /// Text frame, codec does not verify utf8 encoding + /// Text frame. Note that the codec does not validate UTF-8 encoding. Text(Bytes), - /// Binary frame + + /// Binary frame. Binary(Bytes), - /// Continuation + + /// Continuation. Continuation(Item), - /// Ping message + + /// Ping message. Ping(Bytes), - /// Pong message + + /// Pong message. Pong(Bytes), - /// Close message with optional reason + + /// Close message with optional reason. Close(Option), } -/// `WebSocket` continuation item +/// A `WebSocket` continuation item. #[derive(Debug, PartialEq)] pub enum Item { FirstText(Bytes), @@ -51,13 +64,13 @@ pub enum Item { } #[derive(Debug, Copy, Clone)] -/// WebSockets protocol codec +/// WebSocket protocol codec. pub struct Codec { flags: Flags, max_size: usize, } -bitflags::bitflags! { +bitflags! { struct Flags: u8 { const SERVER = 0b0000_0001; const CONTINUATION = 0b0000_0010; @@ -66,7 +79,7 @@ bitflags::bitflags! { } impl Codec { - /// Create new websocket frames decoder + /// Create new websocket frames decoder. pub fn new() -> Codec { Codec { max_size: 65_536, @@ -74,9 +87,9 @@ impl Codec { } } - /// Set max frame size + /// Set max frame size. /// - /// By default max size is set to 64kb + /// By default max size is set to 64kb. pub fn max_size(mut self, size: usize) -> Self { self.max_size = size; self diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index cd212fb7e..a2b093ce4 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,11 +1,11 @@ //! WebSocket protocol support. //! -//! To setup a `WebSocket`, first do web socket handshake then on success -//! convert `Payload` into a `WsStream` stream and then use `WsWriter` to -//! communicate with the peer. +//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a +//! `WsStream` stream and then use `WsWriter` to communicate with the peer. + use std::io; -use derive_more::{Display, From}; +use derive_more::{Display, Error, From}; use http::{header, Method, StatusCode}; use crate::error::ResponseError; @@ -23,86 +23,103 @@ pub use self::dispatcher::Dispatcher; pub use self::frame::Parser; pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; -/// Websocket protocol errors -#[derive(Debug, Display, From)] +/// WebSocket protocol errors. +#[derive(Debug, Display, From, Error)] pub enum ProtocolError { - /// Received an unmasked frame from client - #[display(fmt = "Received an unmasked frame from client")] + /// Received an unmasked frame from client. + #[display(fmt = "Received an unmasked frame from client.")] UnmaskedFrame, - /// Received a masked frame from server - #[display(fmt = "Received a masked frame from server")] + + /// Received a masked frame from server. + #[display(fmt = "Received a masked frame from server.")] MaskedFrame, - /// Encountered invalid opcode - #[display(fmt = "Invalid opcode: {}", _0)] - InvalidOpcode(u8), + + /// Encountered invalid opcode. + #[display(fmt = "Invalid opcode: {}.", _0)] + InvalidOpcode(#[error(not(source))] u8), + /// Invalid control frame length - #[display(fmt = "Invalid control frame length: {}", _0)] - InvalidLength(usize), - /// Bad web socket op code - #[display(fmt = "Bad web socket op code")] + #[display(fmt = "Invalid control frame length: {}.", _0)] + InvalidLength(#[error(not(source))] usize), + + /// Bad opcode. + #[display(fmt = "Bad opcode.")] BadOpCode, + /// A payload reached size limit. #[display(fmt = "A payload reached size limit.")] Overflow, - /// Continuation is not started + + /// Continuation is not started. #[display(fmt = "Continuation is not started.")] ContinuationNotStarted, - /// Received new continuation but it is already started - #[display(fmt = "Received new continuation but it is already started")] + + /// Received new continuation but it is already started. + #[display(fmt = "Received new continuation but it is already started.")] ContinuationStarted, - /// Unknown continuation fragment - #[display(fmt = "Unknown continuation fragment.")] - ContinuationFragment(OpCode), - /// Io error - #[display(fmt = "io error: {}", _0)] + + /// Unknown continuation fragment. + #[display(fmt = "Unknown continuation fragment: {}.", _0)] + ContinuationFragment(#[error(not(source))] OpCode), + + /// I/O error. + #[display(fmt = "I/O error: {}", _0)] Io(io::Error), } -impl std::error::Error for ProtocolError {} - impl ResponseError for ProtocolError {} -/// Websocket handshake errors +/// WebSocket handshake errors #[derive(PartialEq, Debug, Display)] pub enum HandshakeError { - /// Only get method is allowed - #[display(fmt = "Method not allowed")] + /// Only get method is allowed. + #[display(fmt = "Method not allowed.")] GetMethodRequired, - /// Upgrade header if not set to websocket - #[display(fmt = "Websocket upgrade is expected")] + + /// Upgrade header if not set to websocket. + #[display(fmt = "WebSocket upgrade is expected.")] NoWebsocketUpgrade, - /// Connection header is not set to upgrade - #[display(fmt = "Connection upgrade is expected")] + + /// Connection header is not set to upgrade. + #[display(fmt = "Connection upgrade is expected.")] NoConnectionUpgrade, - /// Websocket version header is not set - #[display(fmt = "Websocket version header is required")] + + /// WebSocket version header is not set. + #[display(fmt = "WebSocket version header is required.")] NoVersionHeader, - /// Unsupported websocket version - #[display(fmt = "Unsupported version")] + + /// Unsupported websocket version. + #[display(fmt = "Unsupported version.")] UnsupportedVersion, - /// Websocket key is not set or wrong - #[display(fmt = "Unknown websocket key")] + + /// WebSocket key is not set or wrong. + #[display(fmt = "Unknown websocket key.")] BadWebsocketKey, } impl ResponseError for HandshakeError { fn error_response(&self) -> Response { - match *self { + match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() .header(header::ALLOW, "GET") .finish(), + HandshakeError::NoWebsocketUpgrade => Response::BadRequest() .reason("No WebSocket UPGRADE header found") .finish(), + HandshakeError::NoConnectionUpgrade => Response::BadRequest() .reason("No CONNECTION upgrade") .finish(), + HandshakeError::NoVersionHeader => Response::BadRequest() .reason("Websocket version header is required") .finish(), + HandshakeError::UnsupportedVersion => Response::BadRequest() .reason("Unsupported version") .finish(), + HandshakeError::BadWebsocketKey => { Response::BadRequest().reason("Handshake error").finish() } diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 345681429..6fa3debc5 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -2,21 +2,27 @@ use std::convert::{From, Into}; use std::fmt; use self::OpCode::*; -/// Operation codes as part of rfc6455. +/// Operation codes as part of RFC6455. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum OpCode { /// Indicates a continuation frame of a fragmented message. Continue, + /// Indicates a text data frame. Text, + /// Indicates a binary data frame. Binary, + /// Indicates a close control frame. Close, + /// Indicates a ping control frame. Ping, + /// Indicates a pong control frame. Pong, + /// Indicates an invalid opcode was received. Bad, } diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index e31f2745c..976fc9164 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -74,7 +74,7 @@ async fn service(msg: ws::Frame) -> Result { let msg = match msg { ws::Frame::Ping(msg) => ws::Message::Pong(msg), ws::Frame::Text(text) => { - ws::Message::Text(String::from_utf8_lossy(&text).to_string()) + ws::Message::Text(String::from_utf8_lossy(&text).into_owned().into()) } ws::Frame::Binary(bin) => ws::Message::Binary(bin), ws::Frame::Continuation(item) => ws::Message::Continuation(item), @@ -101,10 +101,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let (item, mut framed) = framed.into_future().await; assert_eq!( item.unwrap().unwrap(), diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index e47f09135..dab35953a 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,8 +3,10 @@ ## Unreleased - 2021-xx-xx * Update `pin-project` to `1.0`. * Update `bytes` to `1.0`. [#1813] +* `WebsocketContext::text` now takes an `Into`. [#1864] [#1813]: https://github.com/actix/actix-web/pull/1813 +[#1864]: https://github.com/actix/actix-web/pull/1864 ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 28b9d6fa2..dac4060ba 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -22,6 +22,7 @@ actix-http = "2.0.0" actix-web = { version = "3.0.0", default-features = false } bytes = "1" +bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 9dd7bf500..60942c6c6 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -1,9 +1,10 @@ -//! Websocket integration -use std::collections::VecDeque; +//! Websocket integration. + use std::future::Future; use std::io; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{collections::VecDeque, convert::TryFrom}; use actix::dev::{ AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, @@ -24,10 +25,11 @@ use actix_web::error::{Error, PayloadError}; use actix_web::http::{header, Method, StatusCode}; use actix_web::{HttpRequest, HttpResponse}; use bytes::{Bytes, BytesMut}; +use bytestring::ByteString; use futures_core::Stream; use tokio::sync::oneshot::Sender; -/// Do websocket handshake and start ws actor. +/// Perform WebSocket handshake and start actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where A: Actor> @@ -38,7 +40,7 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } -/// Do websocket handshake and start ws actor. +/// Perform WebSocket handshake and start actor. /// /// `req` is an HTTP Request that should be requesting a websocket protocol /// change. `stream` should be a `Bytes` stream (such as @@ -338,13 +340,13 @@ where /// Send text frame #[inline] - pub fn text>(&mut self, text: T) { + pub fn text(&mut self, text: impl Into) { self.write_raw(Message::Text(text.into())); } /// Send binary frame #[inline] - pub fn binary>(&mut self, data: B) { + pub fn binary(&mut self, data: impl Into) { self.write_raw(Message::Binary(data.into())); } @@ -528,16 +530,14 @@ where } Some(frm) => { let msg = match frm { - Frame::Text(data) => Message::Text( - std::str::from_utf8(&data) - .map_err(|e| { - ProtocolError::Io(io::Error::new( - io::ErrorKind::Other, - format!("{}", e), - )) - })? - .to_string(), - ), + Frame::Text(data) => { + Message::Text(ByteString::try_from(data).map_err(|e| { + ProtocolError::Io(io::Error::new( + io::ErrorKind::Other, + format!("{}", e), + )) + })?) + } Frame::Binary(data) => Message::Binary(data), Frame::Ping(s) => Message::Ping(s), Frame::Pong(s) => Message::Pong(s), diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index a5233e5e0..7fd59a4a7 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -38,10 +38,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index fb6ed086a..d9db7a2cf 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -76,7 +76,7 @@ //! .await?; //! //! connection -//! .send(awc::ws::Message::Text("Echo".to_string())) +//! .send(awc::ws::Message::Text("Echo".into())) //! .await?; //! let response = connection.next().await.unwrap()?; //! # assert_eq!(response, awc::ws::Frame::Text("Echo".as_bytes().into())); diff --git a/awc/src/ws.rs b/awc/src/ws.rs index a1fa07d8d..b90d0942b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -17,7 +17,7 @@ //! .unwrap(); //! //! connection -//! .send(ws::Message::Text("Echo".to_string())) +//! .send(ws::Message::Text("Echo".into())) //! .await //! .unwrap(); //! let response = connection.next().await.unwrap().unwrap(); diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 1c1068668..8eb912dac 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -11,7 +11,7 @@ async fn ws_service(req: ws::Frame) -> Result { match req { ws::Frame::Ping(msg) => Ok(ws::Message::Pong(msg)), ws::Frame::Text(text) => Ok(ws::Message::Text( - String::from_utf8(Vec::from(text.as_ref())).unwrap(), + String::from_utf8(Vec::from(text.as_ref())).unwrap().into(), )), ws::Frame::Binary(bin) => Ok(ws::Message::Binary(bin)), ws::Frame::Close(reason) => Ok(ws::Message::Close(reason)), @@ -43,10 +43,7 @@ async fn test_simple() { // client service let mut framed = srv.ws().await.unwrap(); - framed - .send(ws::Message::Text("text".to_string())) - .await - .unwrap(); + framed.send(ws::Message::Text("text".into())).await.unwrap(); let item = framed.next().await.unwrap().unwrap(); assert_eq!(item, ws::Frame::Text(Bytes::from_static(b"text"))); From e5678733269decfeb9e1a4a9a22e8ef7da0b000b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 4 Jan 2021 21:03:46 +0800 Subject: [PATCH 093/187] optimize message pool release (#1871) --- actix-http/src/message.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 5e53f73b6..3673017bc 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -67,7 +67,7 @@ impl Head for RequestHead { fn clear(&mut self) { self.flags = Flags::empty(); self.headers.clear(); - self.extensions.borrow_mut().clear(); + self.extensions.get_mut().clear(); } fn pool() -> &'static MessagePool { @@ -440,9 +440,11 @@ impl MessagePool { #[inline] fn get_message(&'static self) -> Message { if let Some(mut msg) = self.0.borrow_mut().pop() { - if let Some(r) = Rc::get_mut(&mut msg) { - r.clear(); - } + // Message is put in pool only when it's the last copy. + // which means it's guaranteed to be unique when popped out. + Rc::get_mut(&mut msg) + .expect("Multiple copies exist") + .clear(); Message { head: msg } } else { Message { From 93161df141544277b1f7c983a5c3a616cfd44dd3 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 5 Jan 2021 07:47:38 +0800 Subject: [PATCH 094/187] clean up body type (#1872) --- actix-http/src/body.rs | 52 ++++++++++++++++-------------------------- 1 file changed, 20 insertions(+), 32 deletions(-) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 9636f2941..5d1cf7329 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -1,4 +1,3 @@ -use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; use std::{fmt, mem}; @@ -68,7 +67,7 @@ impl MessageBody for Box { #[pin_project(project = ResponseBodyProj)] pub enum ResponseBody { Body(#[pin] B), - Other(#[pin] Body), + Other(Body), } impl ResponseBody { @@ -110,7 +109,7 @@ impl MessageBody for ResponseBody { ) -> Poll>> { match self.project() { ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), } } } @@ -124,12 +123,11 @@ impl Stream for ResponseBody { ) -> Poll> { match self.project() { ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), } } } -#[pin_project(project = BodyProj)] /// Represents various types of http message body. pub enum Body { /// Empty response. `Content-Length` header is not set. @@ -168,10 +166,10 @@ impl MessageBody for Body { self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - match self.project() { - BodyProj::None => Poll::Ready(None), - BodyProj::Empty => Poll::Ready(None), - BodyProj::Bytes(ref mut bin) => { + match self.get_mut() { + Body::None => Poll::Ready(None), + Body::Empty => Poll::Ready(None), + Body::Bytes(ref mut bin) => { let len = bin.len(); if len == 0 { Poll::Ready(None) @@ -179,7 +177,7 @@ impl MessageBody for Body { Poll::Ready(Some(Ok(mem::take(bin)))) } } - BodyProj::Message(ref mut body) => Pin::new(body.as_mut()).poll_next(cx), + Body::Message(body) => Pin::new(&mut **body).poll_next(cx), } } } @@ -266,12 +264,12 @@ where } } -impl From> for Body +impl From> for Body where S: Stream> + Unpin + 'static, E: Into + 'static, { - fn from(s: BodyStream) -> Body { + fn from(s: BodyStream) -> Body { Body::from_message(s) } } @@ -367,27 +365,21 @@ impl MessageBody for String { /// Type represent streaming body. /// Response does not contain `content-length` header and appropriate transfer encoding is used. -#[pin_project] -pub struct BodyStream { - #[pin] +pub struct BodyStream { stream: S, - _phantom: PhantomData, } -impl BodyStream +impl BodyStream where S: Stream> + Unpin, E: Into, { pub fn new(stream: S) -> Self { - BodyStream { - stream, - _phantom: PhantomData, - } + BodyStream { stream } } } -impl MessageBody for BodyStream +impl MessageBody for BodyStream where S: Stream> + Unpin, E: Into, @@ -402,13 +394,12 @@ where /// ended on a zero-length chunk, but rather proceed until the underlying /// [`Stream`] ends. fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - let mut stream = self.project().stream; loop { - let stream = stream.as_mut(); - return Poll::Ready(match ready!(stream.poll_next(cx)) { + let stream = &mut self.as_mut().stream; + return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, opt => opt.map(|res| res.map_err(Into::into)), }); @@ -418,10 +409,8 @@ where /// Type represent streaming body. This body implementation should be used /// if total size of stream is known. Data get sent as is without using transfer encoding. -#[pin_project] pub struct SizedStream { size: u64, - #[pin] stream: S, } @@ -448,13 +437,12 @@ where /// ended on a zero-length chunk, but rather proceed until the underlying /// [`Stream`] ends. fn poll_next( - self: Pin<&mut Self>, + mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll>> { - let mut stream: Pin<&mut S> = self.project().stream; loop { - let stream = stream.as_mut(); - return Poll::Ready(match ready!(stream.poll_next(cx)) { + let stream = &mut self.as_mut().stream; + return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { Some(Ok(ref bytes)) if bytes.is_empty() => continue, val => val, }); From 4f5971d79ef7d3217cfaa9b82169bee950b945b9 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 5 Jan 2021 08:22:57 +0800 Subject: [PATCH 095/187] add Compat middleware (#1865) --- CHANGES.md | 7 +- src/middleware/compat.rs | 192 ++++++++++++++++++++++++++++++++++++ src/middleware/condition.rs | 4 +- src/middleware/mod.rs | 2 + 4 files changed, 203 insertions(+), 2 deletions(-) create mode 100644 src/middleware/compat.rs diff --git a/CHANGES.md b/CHANGES.md index 32f444ec1..0077cd518 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `Compat` middleware enabling generic response body/error type of middlewares + like `Logger` and `Compress` to be used in `middleware::Condition` + and `Resource`, `Scope` services. [#1865] + ### Changed * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. @@ -9,7 +14,7 @@ * MSRV is now 1.46.0. [#1813]: https://github.com/actix/actix-web/pull/1813 - +[#1865]: https://github.com/actix/actix-web/pull/1865 ### Fixed * added the actual parsing error to `test::read_body_json` [#1812] diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs new file mode 100644 index 000000000..66346b6d6 --- /dev/null +++ b/src/middleware/compat.rs @@ -0,0 +1,192 @@ +//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + +use actix_http::body::Body; +use actix_http::body::{MessageBody, ResponseBody}; +use actix_service::{Service, Transform}; +use futures_core::future::LocalBoxFuture; +use futures_core::ready; + +use crate::error::Error; +use crate::service::ServiceResponse; + +/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. +/// +/// +/// ## Usage +/// +/// ```rust +/// use actix_web::middleware::{Logger, Compat}; +/// use actix_web::{App, web}; +/// +/// let logger = Logger::default(); +/// +/// // this would not compile +/// // let app = App::new().service(web::scope("scoped").wrap(logger)); +/// +/// // by using scoped middleware we can use logger in scope. +/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger))); +/// ``` +pub struct Compat { + transform: T, +} + +impl Compat { + pub fn new(transform: T) -> Self { + Self { transform } + } +} + +impl Transform for Compat +where + S: Service, + T: Transform, + T::Future: 'static, + T::Response: MapServiceResponseBody, + Error: From, +{ + type Response = ServiceResponse; + type Error = Error; + type Transform = CompatMiddleware; + type InitError = T::InitError; + type Future = LocalBoxFuture<'static, Result>; + + fn new_transform(&self, service: S) -> Self::Future { + let fut = self.transform.new_transform(service); + Box::pin(async move { + let service = fut.await?; + Ok(CompatMiddleware { service }) + }) + } +} + +pub struct CompatMiddleware { + service: S, +} + +impl Service for CompatMiddleware +where + S: Service, + S::Response: MapServiceResponseBody, + Error: From, +{ + type Response = ServiceResponse; + type Error = Error; + type Future = CompatMiddlewareFuture; + + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + self.service.poll_ready(cx).map_err(From::from) + } + + fn call(&mut self, req: Req) -> Self::Future { + let fut = self.service.call(req); + CompatMiddlewareFuture { fut } + } +} + +#[pin_project::pin_project] +pub struct CompatMiddlewareFuture { + #[pin] + fut: Fut, +} + +impl Future for CompatMiddlewareFuture +where + Fut: Future>, + T: MapServiceResponseBody, + Error: From, +{ + type Output = Result; + + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let res = ready!(self.project().fut.poll(cx))?; + Poll::Ready(Ok(res.map_body())) + } +} + +// trait for convert ServiceResponse's ResponseBody generic type +// to ResponseBody +pub trait MapServiceResponseBody { + fn map_body(self) -> ServiceResponse; +} + +impl MapServiceResponseBody for ServiceResponse { + fn map_body(self) -> ServiceResponse { + self.map_body(|_, body| ResponseBody::Other(Body::from_message(body))) + } +} + +#[cfg(test)] +mod tests { + use super::*; + + use actix_service::IntoService; + + use crate::dev::ServiceRequest; + use crate::http::StatusCode; + use crate::middleware::{Compress, Condition, Logger}; + use crate::test::{call_service, init_service, TestRequest}; + use crate::{web, App, HttpResponse}; + + #[actix_rt::test] + async fn test_scope_middleware() { + let logger = Logger::default(); + let compress = Compress::default(); + + let mut srv = init_service( + App::new().service( + web::scope("app") + .wrap(Compat::new(logger)) + .wrap(Compat::new(compress)) + .service( + web::resource("/test").route(web::get().to(HttpResponse::Ok)), + ), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_resource_scope_middleware() { + let logger = Logger::default(); + let compress = Compress::default(); + + let mut srv = init_service( + App::new().service( + web::resource("app/test") + .wrap(Compat::new(logger)) + .wrap(Compat::new(compress)) + .route(web::get().to(HttpResponse::Ok)), + ), + ) + .await; + + let req = TestRequest::with_uri("/app/test").to_request(); + let resp = call_service(&mut srv, req).await; + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_condition_scope_middleware() { + let srv = |req: ServiceRequest| { + Box::pin(async move { + Ok(req.into_response(HttpResponse::InternalServerError().finish())) + }) + }; + + let logger = Logger::default(); + + let mut mw = Condition::new(true, Compat::new(logger)) + .new_transform(srv.into_service()) + .await + .unwrap(); + let resp = call_service(&mut mw, TestRequest::default().to_srv_request()).await; + assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + } +} diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 87323e325..04173e053 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -6,7 +6,9 @@ use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture}; /// `Middleware` for conditionally enables another middleware. /// The controlled middleware must not change the `Service` interfaces. -/// This means you cannot control such middlewares like `Logger` or `Compress`. +/// +/// This means you cannot control such middlewares like `Logger` or `Compress` directly. +/// *. See `Compat` middleware for alternative. /// /// ## Usage /// diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index 12c12a98c..af44fd8c7 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -5,12 +5,14 @@ mod compress; #[cfg(feature = "compress")] pub use self::compress::Compress; +mod compat; mod condition; mod defaultheaders; pub mod errhandlers; mod logger; pub mod normalize; +pub use self::compat::Compat; pub use self::condition::Condition; pub use self::defaultheaders::DefaultHeaders; pub use self::logger::Logger; From 68117543ea0ec020af7a5a7581eb4139595da90c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 5 Jan 2021 09:51:58 +0000 Subject: [PATCH 096/187] major cleanup of middleware module (#1875) * major cleanup of middleware module * update changelog --- CHANGES.md | 20 ++- MIGRATION.md | 10 ++ examples/basic.rs | 3 +- src/middleware/compat.rs | 44 ++--- src/middleware/compress.rs | 61 ++++--- src/middleware/condition.rs | 69 ++++---- .../{defaultheaders.rs => default_headers.rs} | 88 +++++---- .../{errhandlers.rs => err_handlers.rs} | 41 ++--- src/middleware/logger.rs | 167 ++++++++---------- src/middleware/mod.rs | 29 +-- src/middleware/normalize.rs | 69 ++++---- tests/test_server.rs | 21 ++- 12 files changed, 319 insertions(+), 303 deletions(-) rename src/middleware/{defaultheaders.rs => default_headers.rs} (79%) rename src/middleware/{errhandlers.rs => err_handlers.rs} (85%) diff --git a/CHANGES.md b/CHANGES.md index 0077cd518..f0b55801b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -2,25 +2,31 @@ ## Unreleased - 2021-xx-xx ### Added -* `Compat` middleware enabling generic response body/error type of middlewares - like `Logger` and `Compress` to be used in `middleware::Condition` - and `Resource`, `Scope` services. [#1865] +* `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and + `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] ### Changed * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. * Update `rust-tls` to `0.19`. [#1813] * Rename `Handler` to `HandlerService` and rename `Factory` to `Handler`. [#1852] +* The default `TrailingSlash` is now `Trim`, in line with existing documentation. See migration + guide for implications. [#1875] +* Rename `DefaultHeaders::{content_type => add_content_type}`. [#1875] * MSRV is now 1.46.0. -[#1813]: https://github.com/actix/actix-web/pull/1813 -[#1865]: https://github.com/actix/actix-web/pull/1865 - ### Fixed -* added the actual parsing error to `test::read_body_json` [#1812] +* Added the underlying parse error to `test::read_body_json`'s panic message. [#1812] + +### Removed +* Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now + exposed directly by the `middleware` module. [#1812]: https://github.com/actix/actix-web/pull/1812 +[#1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 +[#1865]: https://github.com/actix/actix-web/pull/1865 +[#1875]: https://github.com/actix/actix-web/pull/1875 ## 3.3.2 - 2020-12-01 diff --git a/MIGRATION.md b/MIGRATION.md index 5c4650194..e01702868 100644 --- a/MIGRATION.md +++ b/MIGRATION.md @@ -1,5 +1,15 @@ ## Unreleased +* The default `NormalizePath` behavior now strips trailing slashes by default. This was + previously documented to be the case in v3 but the behavior now matches. The effect is that + routes defined with trailing slashes will become inaccessible when + using `NormalizePath::default()`. + + Before: `#[get("/test/")` + After: `#[get("/test")` + + Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`. + ## 3.0.0 diff --git a/examples/basic.rs b/examples/basic.rs index 8b2bf2319..e8ad5fcdb 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -18,8 +18,7 @@ async fn no_params() -> &'static str { #[actix_web::main] async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); HttpServer::new(|| { App::new() diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 66346b6d6..eabd1190d 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -1,41 +1,45 @@ -//! `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`Compat`]. -use actix_http::body::Body; -use actix_http::body::{MessageBody, ResponseBody}; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; + +use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_service::{Service, Transform}; -use futures_core::future::LocalBoxFuture; -use futures_core::ready; +use futures_core::{future::LocalBoxFuture, ready}; -use crate::error::Error; -use crate::service::ServiceResponse; +use crate::{error::Error, service::ServiceResponse}; -/// `Middleware` for enabling any middleware to be used in `Resource`, `Scope` and `Condition`. -/// -/// -/// ## Usage +/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap), +/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). /// +/// # Usage /// ```rust /// use actix_web::middleware::{Logger, Compat}; /// use actix_web::{App, web}; /// /// let logger = Logger::default(); /// -/// // this would not compile -/// // let app = App::new().service(web::scope("scoped").wrap(logger)); +/// // this would not compile because of incompatible body types +/// // let app = App::new() +/// // .service(web::scope("scoped").wrap(logger)); /// -/// // by using scoped middleware we can use logger in scope. -/// let app = App::new().service(web::scope("scoped").wrap(Compat::new(logger))); +/// // by using this middleware we can use the logger on a scope +/// let app = App::new() +/// .service(web::scope("scoped").wrap(Compat::new(logger))); /// ``` pub struct Compat { transform: T, } impl Compat { - pub fn new(transform: T) -> Self { - Self { transform } + /// Wrap a middleware to give it broader compatibility. + pub fn new(middleware: T) -> Self { + Self { + transform: middleware, + } } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index b6ad2c4b5..376719ab2 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -1,45 +1,47 @@ -//! `Middleware` for compressing response body. -use std::cmp; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::str::FromStr; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`Compress`]. -use actix_http::body::MessageBody; -use actix_http::encoding::Encoder; -use actix_http::http::header::{ContentEncoding, ACCEPT_ENCODING}; -use actix_http::Error; +use std::{ + cmp, + future::Future, + marker::PhantomData, + pin::Pin, + str::FromStr, + task::{Context, Poll}, +}; + +use actix_http::{ + body::MessageBody, + encoding::Encoder, + http::header::{ContentEncoding, ACCEPT_ENCODING}, + Error, +}; use actix_service::{Service, Transform}; use futures_util::future::{ok, Ready}; use pin_project::pin_project; -use crate::dev::BodyEncoding; -use crate::service::{ServiceRequest, ServiceResponse}; +use crate::{ + dev::BodyEncoding, + service::{ServiceRequest, ServiceResponse}, +}; -#[derive(Debug, Clone)] -/// `Middleware` for compressing response body. +/// Middleware for compressing response payloads. /// -/// Use `BodyEncoding` trait for overriding response compression. -/// To disable compression set encoding to `ContentEncoding::Identity` value. +/// Use `BodyEncoding` trait for overriding response compression. To disable compression set +/// encoding to `ContentEncoding::Identity`. /// +/// # Usage /// ```rust /// use actix_web::{web, middleware, App, HttpResponse}; /// -/// fn main() { -/// let app = App::new() -/// .wrap(middleware::Compress::default()) -/// .service( -/// web::resource("/test") -/// .route(web::get().to(|| HttpResponse::Ok())) -/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) -/// ); -/// } +/// let app = App::new() +/// .wrap(middleware::Compress::default()) +/// .default_service(web::to(|| HttpResponse::NotFound())); /// ``` +#[derive(Debug, Clone)] pub struct Compress(ContentEncoding); impl Compress { - /// Create new `Compress` middleware with default encoding. + /// Create new `Compress` middleware with the specified encoding. pub fn new(encoding: ContentEncoding) -> Self { Compress(encoding) } @@ -84,9 +86,7 @@ where type Error = Error; type Future = CompressResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); #[allow(clippy::borrow_interior_mutable_const)] fn call(&mut self, req: ServiceRequest) -> Self::Future { @@ -109,7 +109,6 @@ where } } -#[doc(hidden)] #[pin_project] pub struct CompressResponse where diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 04173e053..d61e7d576 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -1,35 +1,36 @@ -//! `Middleware` for conditionally enables another middleware. +//! For middleware documentation, see [`Condition`]. + use std::task::{Context, Poll}; use actix_service::{Service, Transform}; -use futures_util::future::{ok, Either, FutureExt, LocalBoxFuture}; +use futures_util::future::{Either, FutureExt, LocalBoxFuture}; -/// `Middleware` for conditionally enables another middleware. -/// The controlled middleware must not change the `Service` interfaces. +/// Middleware for conditionally enabling other middleware. /// -/// This means you cannot control such middlewares like `Logger` or `Compress` directly. -/// *. See `Compat` middleware for alternative. -/// -/// ## Usage +/// The controlled middleware must not change the `Service` interfaces. This means you cannot +/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat) +/// middleware for a workaround. /// +/// # Usage /// ```rust /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; /// -/// # fn main() { -/// let enable_normalize = std::env::var("NORMALIZE_PATH") == Ok("true".into()); +/// let enable_normalize = std::env::var("NORMALIZE_PATH").is_ok(); /// let app = App::new() /// .wrap(Condition::new(enable_normalize, NormalizePath::default())); -/// # } /// ``` pub struct Condition { - trans: T, + transformer: T, enable: bool, } impl Condition { - pub fn new(enable: bool, trans: T) -> Self { - Self { trans, enable } + pub fn new(enable: bool, transformer: T) -> Self { + Self { + transformer, + enable, + } } } @@ -49,16 +50,15 @@ where fn new_transform(&self, service: S) -> Self::Future { if self.enable { - let f = self.trans.new_transform(service).map(|res| { - res.map( - ConditionMiddleware::Enable as fn(T::Transform) -> Self::Transform, - ) - }); - Either::Left(f) + let fut = self.transformer.new_transform(service); + async move { + let wrapped_svc = fut.await?; + Ok(ConditionMiddleware::Enable(wrapped_svc)) + } + .boxed_local() } else { - Either::Right(ok(ConditionMiddleware::Disable(service))) + async move { Ok(ConditionMiddleware::Disable(service)) }.boxed_local() } - .boxed_local() } } @@ -77,18 +77,16 @@ where type Future = Either; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - use ConditionMiddleware::*; match self { - Enable(service) => service.poll_ready(cx), - Disable(service) => service.poll_ready(cx), + ConditionMiddleware::Enable(service) => service.poll_ready(cx), + ConditionMiddleware::Disable(service) => service.poll_ready(cx), } } fn call(&mut self, req: Req) -> Self::Future { - use ConditionMiddleware::*; match self { - Enable(service) => Either::Left(service.call(req)), - Disable(service) => Either::Right(service.call(req)), + ConditionMiddleware::Enable(service) => Either::Left(service.call(req)), + ConditionMiddleware::Disable(service) => Either::Right(service.call(req)), } } } @@ -96,14 +94,17 @@ where #[cfg(test)] mod tests { use actix_service::IntoService; + use futures_util::future::ok; use super::*; - use crate::dev::{ServiceRequest, ServiceResponse}; - use crate::error::Result; - use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; - use crate::middleware::errhandlers::*; - use crate::test::{self, TestRequest}; - use crate::HttpResponse; + use crate::{ + dev::{ServiceRequest, ServiceResponse}, + error::Result, + http::{header::CONTENT_TYPE, HeaderValue, StatusCode}, + middleware::err_handlers::*, + test::{self, TestRequest}, + HttpResponse, + }; #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { diff --git a/src/middleware/defaultheaders.rs b/src/middleware/default_headers.rs similarity index 79% rename from src/middleware/defaultheaders.rs rename to src/middleware/default_headers.rs index d648ad70f..6f027124f 100644 --- a/src/middleware/defaultheaders.rs +++ b/src/middleware/default_headers.rs @@ -1,24 +1,34 @@ -//! Middleware for setting default response headers -use std::convert::TryFrom; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`DefaultHeaders`]. -use actix_service::{Service, Transform}; -use futures_util::future::{ready, Ready}; -use futures_util::ready; +use std::{ + convert::TryFrom, + future::Future, + marker::PhantomData, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; -use crate::http::header::{HeaderName, HeaderValue, CONTENT_TYPE}; -use crate::http::{Error as HttpError, HeaderMap}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::Error; +use futures_util::{ + future::{ready, Ready}, + ready, +}; -/// `Middleware` for setting default response headers. +use crate::{ + dev::{Service, Transform}, + http::{ + header::{HeaderName, HeaderValue, CONTENT_TYPE}, + Error as HttpError, HeaderMap, + }, + service::{ServiceRequest, ServiceResponse}, + Error, +}; + +/// Middleware for setting default response headers. /// -/// This middleware does not set header if response headers already contains it. +/// Headers with the same key that are already set in a response will *not* be overwritten. /// +/// # Usage /// ```rust /// use actix_web::{web, http, middleware, App, HttpResponse}; /// @@ -38,7 +48,6 @@ pub struct DefaultHeaders { } struct Inner { - ct: bool, headers: HeaderMap, } @@ -46,7 +55,6 @@ impl Default for DefaultHeaders { fn default() -> Self { DefaultHeaders { inner: Rc::new(Inner { - ct: false, headers: HeaderMap::new(), }), } @@ -54,12 +62,12 @@ impl Default for DefaultHeaders { } impl DefaultHeaders { - /// Construct `DefaultHeaders` middleware. + /// Constructs an empty `DefaultHeaders` middleware. pub fn new() -> DefaultHeaders { DefaultHeaders::default() } - /// Set a header. + /// Adds a header to the default set. #[inline] pub fn header(mut self, key: K, value: V) -> Self where @@ -84,11 +92,18 @@ impl DefaultHeaders { self } - /// Set *CONTENT-TYPE* header if response does not contain this header. - pub fn content_type(mut self) -> Self { + /// Adds a default *Content-Type* header if response does not contain one. + /// + /// Default is `application/octet-stream`. + pub fn add_content_type(mut self) -> Self { Rc::get_mut(&mut self.inner) - .expect("Multiple copies exist") - .ct = true; + .expect("Multiple `Inner` copies exist.") + .headers + .insert( + CONTENT_TYPE, + HeaderValue::from_static("application/octet-stream"), + ); + self } } @@ -126,9 +141,7 @@ where type Error = Error; type Future = DefaultHeaderFuture; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); @@ -160,19 +173,14 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); let mut res = ready!(this.fut.poll(cx))?; + // set response headers for (key, value) in this.inner.headers.iter() { if !res.headers().contains_key(key) { res.headers_mut().insert(key.clone(), value.clone()); } } - // default content-type - if this.inner.ct && !res.headers().contains_key(&CONTENT_TYPE) { - res.headers_mut().insert( - CONTENT_TYPE, - HeaderValue::from_static("application/octet-stream"), - ); - } + Poll::Ready(Ok(res)) } } @@ -183,10 +191,12 @@ mod tests { use futures_util::future::ok; use super::*; - use crate::dev::ServiceRequest; - use crate::http::header::CONTENT_TYPE; - use crate::test::{ok_service, TestRequest}; - use crate::HttpResponse; + use crate::{ + dev::ServiceRequest, + http::header::CONTENT_TYPE, + test::{ok_service, TestRequest}, + HttpResponse, + }; #[actix_rt::test] async fn test_default_headers() { @@ -219,7 +229,7 @@ mod tests { let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let mut mw = DefaultHeaders::new() - .content_type() + .add_content_type() .new_transform(srv.into_service()) .await .unwrap(); diff --git a/src/middleware/errhandlers.rs b/src/middleware/err_handlers.rs similarity index 85% rename from src/middleware/errhandlers.rs rename to src/middleware/err_handlers.rs index 8503b3e1e..dfd9a7dc5 100644 --- a/src/middleware/errhandlers.rs +++ b/src/middleware/err_handlers.rs @@ -1,35 +1,36 @@ -//! Custom handlers service for responses. +//! For middleware documentation, see [`ErrorHandlers`]. + use std::rc::Rc; -use std::task::{Context, Poll}; use actix_service::{Service, Transform}; use ahash::AHashMap; use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; -use crate::dev::{ServiceRequest, ServiceResponse}; -use crate::error::{Error, Result}; -use crate::http::StatusCode; +use crate::{ + dev::{ServiceRequest, ServiceResponse}, + error::{Error, Result}, + http::StatusCode, +}; -/// Error handler response +/// Return type for [`ErrorHandlers`] custom handlers. pub enum ErrorHandlerResponse { - /// New http response got generated + /// Immediate HTTP response. Response(ServiceResponse), - /// Result is a future that resolves to a new http response + + /// A future that resolves to an HTTP response. Future(LocalBoxFuture<'static, Result, Error>>), } type ErrorHandler = dyn Fn(ServiceResponse) -> Result>; -/// `Middleware` for allowing custom handlers for responses. +/// Middleware for registering custom status code based error handlers. /// -/// You can use `ErrorHandlers::handler()` method to register a custom error -/// handler for specific status code. You can modify existing response or -/// create completely new one. -/// -/// ## Example +/// Register handlers with the `ErrorHandlers::handler()` method to register a custom error handler +/// for a given status code. Handlers can modify existing responses or create completely new ones. /// +/// # Usage /// ```rust -/// use actix_web::middleware::errhandlers::{ErrorHandlers, ErrorHandlerResponse}; +/// use actix_web::middleware::{ErrorHandlers, ErrorHandlerResponse}; /// use actix_web::{web, http, dev, App, HttpRequest, HttpResponse, Result}; /// /// fn render_500(mut res: dev::ServiceResponse) -> Result> { @@ -39,7 +40,6 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result = dyn Fn(ServiceResponse) -> Result { handlers: Rc>>>, @@ -64,12 +63,12 @@ impl Default for ErrorHandlers { } impl ErrorHandlers { - /// Construct new `ErrorHandlers` instance + /// Construct new `ErrorHandlers` instance. pub fn new() -> Self { ErrorHandlers::default() } - /// Register error handler for specified status code + /// Register error handler for specified status code. pub fn handler(mut self, status: StatusCode, handler: F) -> Self where F: Fn(ServiceResponse) -> Result> + 'static, @@ -117,9 +116,7 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index c952aeac9..cdbd5e485 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -1,13 +1,16 @@ -//! Request logging middleware -use std::collections::HashSet; -use std::convert::TryFrom; -use std::env; -use std::fmt::{self, Display, Formatter}; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; +//! For middleware documentation, see [`Logger`]. + +use std::{ + collections::HashSet, + convert::TryFrom, + env, + fmt::{self, Display as _}, + future::Future, + marker::PhantomData, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_service::{Service, Transform}; use bytes::Bytes; @@ -16,78 +19,69 @@ use log::debug; use regex::{Regex, RegexSet}; use time::OffsetDateTime; -use crate::dev::{BodySize, MessageBody, ResponseBody}; -use crate::error::{Error, Result}; -use crate::http::{HeaderName, StatusCode}; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::HttpResponse; +use crate::{ + dev::{BodySize, MessageBody, ResponseBody}, + error::{Error, Result}, + http::{HeaderName, StatusCode}, + service::{ServiceRequest, ServiceResponse}, + HttpResponse, +}; -/// `Middleware` for logging request and response info to the terminal. +/// Middleware for logging request and response summaries to the terminal. /// -/// `Logger` middleware uses standard log crate to log information. You should -/// enable logger for `actix_web` package to see access log. -/// ([`env_logger`](https://docs.rs/env_logger/*/env_logger/) or similar) +/// This middleware uses the `log` crate to output information. Enable `log`'s output for the +/// "actix_web" scope using [`env_logger`](https://docs.rs/env_logger) or similar crate. /// -/// ## Usage -/// -/// Create `Logger` middleware with the specified `format`. -/// Default `Logger` could be created with `default` method, it uses the -/// default format: +/// # Default Format +/// The [`default`](Logger::default) Logger uses the following format: /// /// ```plain /// %a "%r" %s %b "%{Referer}i" "%{User-Agent}i" %T +/// +/// Example Output: +/// 127.0.0.1:54278 "GET /test HTTP/1.1" 404 20 "-" "HTTPie/2.2.0" 0.001074 /// ``` /// +/// # Usage /// ```rust /// use actix_web::{middleware::Logger, App}; /// -/// std::env::set_var("RUST_LOG", "actix_web=info"); -/// env_logger::init(); +/// // access logs are printed with the INFO level so ensure it is enabled by default +/// env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); /// /// let app = App::new() -/// .wrap(Logger::default()) +/// // .wrap(Logger::default()) /// .wrap(Logger::new("%a %{User-Agent}i")); /// ``` /// -/// ## Format -/// -/// `%%` The percent sign -/// -/// `%a` Remote IP-address (IP-address of proxy if using reverse proxy) -/// -/// `%t` Time when the request was started to process (in rfc3339 format) -/// -/// `%r` First line of request -/// -/// `%s` Response status code -/// -/// `%b` Size of response in bytes, including HTTP headers -/// -/// `%T` Time taken to serve the request, in seconds with floating fraction in -/// .06f format -/// -/// `%D` Time taken to serve the request, in milliseconds -/// -/// `%U` Request URL -/// -/// `%{r}a` Real IP remote address **\*** -/// -/// `%{FOO}i` request.headers['FOO'] -/// -/// `%{FOO}o` response.headers['FOO'] -/// -/// `%{FOO}e` os.environ['FOO'] -/// -/// `%{FOO}xi` [custom request replacement](Logger::custom_request_replace) labelled "FOO" +/// # Format +/// Variable | Description +/// -------- | ----------- +/// `%%` | The percent sign +/// `%a` | Peer IP address (or IP address of reverse proxy if used) +/// `%t` | Time when the request started processing (in RFC 3339 format) +/// `%r` | First line of request (Example: `GET /test HTTP/1.1`) +/// `%s` | Response status code +/// `%b` | Size of response in bytes, including HTTP headers +/// `%T` | Time taken to serve the request, in seconds to 6 decimal places +/// `%D` | Time taken to serve the request, in milliseconds +/// `%U` | Request URL +/// `%{r}a` | "Real IP" remote address **\*** +/// `%{FOO}i` | `request.headers["FOO"]` +/// `%{FOO}o` | `response.headers["FOO"]` +/// `%{FOO}e` | `env_var["FOO"]` +/// `%{FOO}xi` | [Custom request replacement](Logger::custom_request_replace) labelled "FOO" /// /// # Security -/// **\*** It is calculated using -/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) +/// **\*** "Real IP" remote address is calculated using +/// [`ConnectionInfo::realip_remote_addr()`](crate::dev::ConnectionInfo::realip_remote_addr()) /// -/// If you use this value ensure that all requests come from trusted hosts, since it is trivial -/// for the remote client to simulate being another client. +/// If you use this value, ensure that all requests come from trusted hosts. Otherwise, it is +/// trivial for the remote client to falsify their source IP address. +#[derive(Debug)] pub struct Logger(Rc); +#[derive(Debug, Clone)] struct Inner { format: Format, exclude: HashSet, @@ -113,7 +107,7 @@ impl Logger { self } - /// Ignore and do not log access info for paths that match regex + /// Ignore and do not log access info for paths that match regex. pub fn exclude_regex>(mut self, path: T) -> Self { let inner = Rc::get_mut(&mut self.0).unwrap(); let mut patterns = inner.exclude_regex.patterns().to_vec(); @@ -209,7 +203,7 @@ where } } -/// Logger middleware +/// Logger middleware service. pub struct LoggerMiddleware { inner: Rc, service: S, @@ -224,9 +218,7 @@ where type Error = Error; type Future = LoggerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { if self.inner.exclude.contains(req.path()) @@ -255,7 +247,6 @@ where } } -#[doc(hidden)] #[pin_project::pin_project] pub struct LoggerResponse where @@ -325,7 +316,7 @@ pub struct StreamLog { impl PinnedDrop for StreamLog { fn drop(self: Pin<&mut Self>) { if let Some(ref format) = self.format { - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, self.size, self.time)?; } @@ -356,9 +347,8 @@ impl MessageBody for StreamLog { } } -/// A formatting style for the `Logger`, consisting of multiple -/// `FormatText`s concatenated into one line. -#[derive(Clone)] +/// A formatting style for the `Logger` consisting of multiple concatenated `FormatText` items. +#[derive(Debug, Clone)] struct Format(Vec); impl Default for Format { @@ -430,13 +420,12 @@ impl Format { } } -/// A string of text to be logged. This is either one of the data -/// fields supported by the `Logger`, or a custom `String`. -#[doc(hidden)] +/// A string of text to be logged. +/// +/// This is either one of the data fields supported by the `Logger`, or a custom `String`. #[non_exhaustive] #[derive(Debug, Clone)] -// TODO: remove pub on next breaking change -pub enum FormatText { +enum FormatText { Str(String), Percent, RequestLine, @@ -454,10 +443,8 @@ pub enum FormatText { CustomRequest(String, Option), } -// TODO: remove pub on next breaking change -#[doc(hidden)] #[derive(Clone)] -pub struct CustomRequestFn { +struct CustomRequestFn { inner_fn: Rc String>, } @@ -476,11 +463,11 @@ impl fmt::Debug for CustomRequestFn { impl FormatText { fn render( &self, - fmt: &mut Formatter<'_>, + fmt: &mut fmt::Formatter<'_>, size: usize, entry_time: OffsetDateTime, ) -> Result<(), fmt::Error> { - match *self { + match self { FormatText::Str(ref string) => fmt.write_str(string), FormatText::Percent => "%".fmt(fmt), FormatText::ResponseSize => size.fmt(fmt), @@ -506,7 +493,7 @@ impl FormatText { } fn render_response(&mut self, res: &HttpResponse) { - match *self { + match self { FormatText::ResponseStatus => { *self = FormatText::Str(format!("{}", res.status().as_u16())) } @@ -527,7 +514,7 @@ impl FormatText { } fn render_request(&mut self, now: OffsetDateTime, req: &ServiceRequest) { - match &*self { + match self { FormatText::RequestLine => { *self = if req.query_string().is_empty() { FormatText::Str(format!( @@ -594,11 +581,11 @@ impl FormatText { /// Converter to get a String from something that writes to a Formatter. pub(crate) struct FormatDisplay<'a>( - &'a dyn Fn(&mut Formatter<'_>) -> Result<(), fmt::Error>, + &'a dyn Fn(&mut fmt::Formatter<'_>) -> Result<(), fmt::Error>, ); impl<'a> fmt::Display for FormatDisplay<'a> { - fn fmt(&self, fmt: &mut Formatter<'_>) -> Result<(), fmt::Error> { + fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { (self.0)(fmt) } } @@ -675,7 +662,7 @@ mod tests { unit.render_response(&resp); } - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, now)?; } @@ -708,7 +695,7 @@ mod tests { } let entry_time = OffsetDateTime::now_utc(); - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, entry_time)?; } @@ -736,7 +723,7 @@ mod tests { unit.render_response(&resp); } - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, now)?; } @@ -769,7 +756,7 @@ mod tests { } let entry_time = OffsetDateTime::now_utc(); - let render = |fmt: &mut Formatter<'_>| { + let render = |fmt: &mut fmt::Formatter<'_>| { for unit in &format.0 { unit.render(fmt, 1024, entry_time)?; } @@ -800,7 +787,7 @@ mod tests { unit.render_request(now, &req); - let render = |fmt: &mut Formatter<'_>| unit.render(fmt, 1024, now); + let render = |fmt: &mut fmt::Formatter<'_>| unit.render(fmt, 1024, now); let log_output = FormatDisplay(&render).to_string(); assert_eq!(log_output, "custom_log"); diff --git a/src/middleware/mod.rs b/src/middleware/mod.rs index af44fd8c7..e24782f07 100644 --- a/src/middleware/mod.rs +++ b/src/middleware/mod.rs @@ -1,19 +1,20 @@ -//! Middlewares +//! Commonly used middleware. + +mod compat; +mod condition; +mod default_headers; +mod err_handlers; +mod logger; +mod normalize; + +pub use self::compat::Compat; +pub use self::condition::Condition; +pub use self::default_headers::DefaultHeaders; +pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers}; +pub use self::logger::Logger; +pub use self::normalize::{NormalizePath, TrailingSlash}; #[cfg(feature = "compress")] mod compress; #[cfg(feature = "compress")] pub use self::compress::Compress; - -mod compat; -mod condition; -mod defaultheaders; -pub mod errhandlers; -mod logger; -pub mod normalize; - -pub use self::compat::Compat; -pub use self::condition::Condition; -pub use self::defaultheaders::DefaultHeaders; -pub use self::logger::Logger; -pub use self::normalize::NormalizePath; diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 4109364bf..89cab9073 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -1,60 +1,63 @@ //! For middleware documentation, see [`NormalizePath`]. -use std::task::{Context, Poll}; - use actix_http::http::{PathAndQuery, Uri}; use actix_service::{Service, Transform}; use bytes::Bytes; use futures_util::future::{ready, Ready}; use regex::Regex; -use crate::service::{ServiceRequest, ServiceResponse}; -use crate::Error; +use crate::{ + service::{ServiceRequest, ServiceResponse}, + Error, +}; -/// To be used when constructing `NormalizePath` to define it's behavior. +/// Determines the behavior of the [`NormalizePath`] middleware. +/// +/// The default is `TrailingSlash::Trim`. #[non_exhaustive] -#[derive(Clone, Copy)] +#[derive(Debug, Clone, Copy)] pub enum TrailingSlash { - /// Always add a trailing slash to the end of the path. - /// This will require all routes to end in a trailing slash for them to be accessible. - Always, + /// Trim trailing slashes from the end of the path. + /// + /// Using this will require all routes to omit trailing slashes for them to be accessible. + Trim, /// Only merge any present multiple trailing slashes. /// - /// Note: This option provides the best compatibility with the v2 version of this middleware. + /// This option provides the best compatibility with behavior in actix-web v2.0. MergeOnly, - /// Trim trailing slashes from the end of the path. - Trim, + /// Always add a trailing slash to the end of the path. + /// + /// Using this will require all routes have a trailing slash for them to be accessible. + Always, } impl Default for TrailingSlash { fn default() -> Self { - TrailingSlash::Always + TrailingSlash::Trim } } -#[derive(Default, Clone, Copy)] -/// Middleware to normalize a request's path so that routes can be matched less strictly. +/// Middleware for normalizing a request's path so that routes can be matched more flexibly. /// /// # Normalization Steps -/// - Merges multiple consecutive slashes into one. (For example, `/path//one` always -/// becomes `/path/one`.) +/// - Merges consecutive slashes into one. (For example, `/path//one` always becomes `/path/one`.) /// - Appends a trailing slash if one is not present, removes one if present, or keeps trailing /// slashes as-is, depending on which [`TrailingSlash`] variant is supplied /// to [`new`](NormalizePath::new()). /// /// # Default Behavior -/// The default constructor chooses to strip trailing slashes from the end -/// ([`TrailingSlash::Trim`]), the effect is that route definitions should be defined without -/// trailing slashes or else they will be inaccessible. +/// The default constructor chooses to strip trailing slashes from the end of paths with them +/// ([`TrailingSlash::Trim`]). The implication is that route definitions should be defined without +/// trailing slashes or else they will be inaccessible (or vice versa when using the +/// `TrailingSlash::Always` behavior), as shown in the example tests below. /// -/// # Example +/// # Usage /// ```rust /// use actix_web::{web, middleware, App}; /// -/// # #[actix_rt::test] -/// # async fn normalize() { +/// # actix_web::rt::System::new("doctest").block_on(async { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) /// .route("/test", web::get().to(|| async { "test" })) @@ -80,8 +83,9 @@ impl Default for TrailingSlash { /// let req = TestRequest::with_uri("/unmatchable/").to_request(); /// let res = call_service(&mut app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); -/// # } +/// # }) /// ``` +#[derive(Debug, Clone, Copy, Default)] pub struct NormalizePath(TrailingSlash); impl NormalizePath { @@ -111,7 +115,6 @@ where } } -#[doc(hidden)] pub struct NormalizePathNormalization { service: S, merge_slash: Regex, @@ -127,9 +130,7 @@ where type Error = Error; type Future = S::Future; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let head = req.head_mut(); @@ -198,7 +199,7 @@ mod tests { App::new() .wrap(NormalizePath::default()) .service(web::resource("/").to(HttpResponse::Ok)) - .service(web::resource("/v1/something/").to(HttpResponse::Ok)), + .service(web::resource("/v1/something").to(HttpResponse::Ok)), ) .await; @@ -306,7 +307,7 @@ mod tests { #[actix_rt::test] async fn test_in_place_normalization() { let srv = |req: ServiceRequest| { - assert_eq!("/v1/something/", req.path()); + assert_eq!("/v1/something", req.path()); ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; @@ -334,7 +335,7 @@ mod tests { #[actix_rt::test] async fn should_normalize_nothing() { - const URI: &str = "/v1/something/"; + const URI: &str = "/v1/something"; let srv = |req: ServiceRequest| { assert_eq!(URI, req.path()); @@ -353,10 +354,8 @@ mod tests { #[actix_rt::test] async fn should_normalize_no_trail() { - const URI: &str = "/v1/something"; - let srv = |req: ServiceRequest| { - assert_eq!(URI.to_string() + "/", req.path()); + assert_eq!("/v1/something", req.path()); ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; @@ -365,7 +364,7 @@ mod tests { .await .unwrap(); - let req = TestRequest::with_uri(URI).to_srv_request(); + let req = TestRequest::with_uri("/v1/something/").to_srv_request(); let res = normalize.call(req).await.unwrap(); assert!(res.status().is_success()); } diff --git a/tests/test_server.rs b/tests/test_server.rs index 2f8ce625e..43ee1230d 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,7 +1,9 @@ -use std::future::Future; -use std::io::{Read, Write}; -use std::pin::Pin; -use std::task::{Context, Poll}; +use std::{ + future::Future, + io::{Read, Write}, + pin::Pin, + task::{Context, Poll}, +}; use actix_http::http::header::{ ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, @@ -9,15 +11,16 @@ use actix_http::http::header::{ }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; -use flate2::read::GzDecoder; -use flate2::write::{GzEncoder, ZlibDecoder, ZlibEncoder}; -use flate2::Compression; +use flate2::{ + read::GzDecoder, + write::{GzEncoder, ZlibDecoder, ZlibEncoder}, + Compression, +}; use futures_util::ready; use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; -use actix_web::middleware::normalize::TrailingSlash; -use actix_web::middleware::{Compress, NormalizePath}; +use actix_web::middleware::{Compress, NormalizePath, TrailingSlash}; use actix_web::{dev, test, web, App, Error, HttpResponse}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ From 57da1d3c0fc91c197b71e92b94f52618f39dd3ff Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 6 Jan 2021 19:35:30 +0800 Subject: [PATCH 097/187] refactor app_service (#1877) --- src/app.rs | 5 +- src/app_service.rs | 178 +++++++++++++++------------------------------ 2 files changed, 60 insertions(+), 123 deletions(-) diff --git a/src/app.rs b/src/app.rs index fc336e857..fcb491a21 100644 --- a/src/app.rs +++ b/src/app.rs @@ -458,11 +458,12 @@ where Error = Error, InitError = (), >, + T::Future: 'static, { fn into_factory(self) -> AppInit { AppInit { - data: self.data.into_boxed_slice().into(), - data_factories: self.data_factories.into_boxed_slice().into(), + data_factories: self.data.into_boxed_slice().into(), + async_data_factories: self.data_factories.into_boxed_slice().into(), endpoint: self.endpoint, services: Rc::new(RefCell::new(self.services)), external: RefCell::new(self.external), diff --git a/src/app_service.rs b/src/app_service.rs index 686be6312..96040b8fb 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,15 +1,15 @@ use std::cell::RefCell; use std::future::Future; -use std::marker::PhantomData; use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; use actix_http::{Extensions, Request, Response}; -use actix_router::{Path, ResourceDef, ResourceInfo, Router, Url}; +use actix_router::{Path, ResourceDef, Router, Url}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{fn_service, Service, ServiceFactory}; -use futures_util::future::{join_all, ok, FutureExt, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; +use futures_util::future::join_all; use crate::config::{AppConfig, AppService}; use crate::data::{DataFactory, FnDataFactory}; @@ -22,7 +22,6 @@ use crate::service::{AppServiceFactory, ServiceRequest, ServiceResponse}; type Guards = Vec>; type HttpService = BoxService; type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; -type BoxResponse = LocalBoxFuture<'static, Result>; /// Service factory to convert `Request` to a `ServiceRequest`. /// It also executes data factories. @@ -38,8 +37,8 @@ where { pub(crate) endpoint: T, pub(crate) extensions: RefCell>, - pub(crate) data: Rc<[Box]>, - pub(crate) data_factories: Rc<[FnDataFactory]>, + pub(crate) data_factories: Rc<[Box]>, + pub(crate) async_data_factories: Rc<[FnDataFactory]>, pub(crate) services: Rc>>>, pub(crate) default: Option>, pub(crate) factory_ref: Rc>>, @@ -55,24 +54,26 @@ where Error = Error, InitError = (), >, + T::Future: 'static, { type Response = ServiceResponse; type Error = T::Error; type Config = AppConfig; type Service = AppInitService; type InitError = T::InitError; - type Future = AppInitResult; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, config: AppConfig) -> Self::Future { // update resource default service let default = self.default.clone().unwrap_or_else(|| { - Rc::new(boxed::factory(fn_service(|req: ServiceRequest| { - ok(req.into_response(Response::NotFound().finish())) + Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async { + Ok(req.into_response(Response::NotFound().finish())) }))) }); // App config - let mut config = AppService::new(config, default.clone(), self.data.clone()); + let mut config = + AppService::new(config, default.clone(), self.data_factories.clone()); // register services std::mem::take(&mut *self.services.borrow_mut()) @@ -83,7 +84,7 @@ where let (config, services) = config.into_services(); - // complete pipeline creation + // complete pipeline creation. *self.factory_ref.borrow_mut() = Some(AppRoutingFactory { default, services: services @@ -106,107 +107,48 @@ where let rmap = Rc::new(rmap); rmap.finish(rmap.clone()); - // start all data factory futures - let factory_futs = join_all(self.data_factories.iter().map(|f| f())); + // construct all async data factory futures + let factory_futs = join_all(self.async_data_factories.iter().map(|f| f())); - AppInitResult { - endpoint: None, - endpoint_fut: self.endpoint.new_service(()), - data: self.data.clone(), - data_factories: None, - data_factories_fut: factory_futs.boxed_local(), - extensions: Some( - self.extensions - .borrow_mut() - .take() - .unwrap_or_else(Extensions::new), - ), - config, - rmap, - _phantom: PhantomData, - } - } -} + // construct app service and middleware service factory future. + let endpoint_fut = self.endpoint.new_service(()); -#[pin_project::pin_project] -pub struct AppInitResult -where - T: ServiceFactory, -{ - #[pin] - endpoint_fut: T::Future, - // a Some signals completion of endpoint creation - endpoint: Option, + // take extensions or create new one as app data container. + let mut app_data = self + .extensions + .borrow_mut() + .take() + .unwrap_or_else(Extensions::new); - #[pin] - data_factories_fut: LocalBoxFuture<'static, Vec, ()>>>, - // a Some signals completion of factory futures - data_factories: Option>>, + let data_factories = self.data_factories.clone(); - rmap: Rc, - config: AppConfig, - data: Rc<[Box]>, - extensions: Option, + Box::pin(async move { + // async data factories + let async_data_factories = factory_futs + .await + .into_iter() + .collect::, _>>() + .map_err(|_| ())?; - _phantom: PhantomData, -} + // app service and middleware + let service = endpoint_fut.await?; -impl Future for AppInitResult -where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - InitError = (), - >, -{ - type Output = Result, ()>; + // populate app data container from (async) data factories. + data_factories + .iter() + .chain(&async_data_factories) + .for_each(|factory| { + factory.create(&mut app_data); + }); - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - // async data factories - if let Poll::Ready(factories) = this.data_factories_fut.poll(cx) { - let factories: Result, ()> = factories.into_iter().collect(); - - if let Ok(factories) = factories { - this.data_factories.replace(factories); - } else { - return Poll::Ready(Err(())); - } - } - - // app service and middleware - if this.endpoint.is_none() { - if let Poll::Ready(srv) = this.endpoint_fut.poll(cx)? { - *this.endpoint = Some(srv); - } - } - - // not using if let so condition only needs shared ref - if this.endpoint.is_some() && this.data_factories.is_some() { - // create app data container - let mut data = this.extensions.take().unwrap(); - - for f in this.data.iter() { - f.create(&mut data); - } - - for f in this.data_factories.take().unwrap().iter() { - f.create(&mut data); - } - - return Poll::Ready(Ok(AppInitService { - service: this.endpoint.take().unwrap(), - rmap: this.rmap.clone(), - config: this.config.clone(), - data: Rc::new(data), + Ok(AppInitService { + service, + rmap, + config, + app_data: Rc::new(app_data), pool: HttpRequestPool::create(), - })); - } - - Poll::Pending + }) + }) } } @@ -218,7 +160,7 @@ where service: T, rmap: Rc, config: AppConfig, - data: Rc, + app_data: Rc, pool: &'static HttpRequestPool, } @@ -251,7 +193,7 @@ where payload, self.rmap.clone(), self.config.clone(), - self.data.clone(), + self.app_data.clone(), self.pool, ) }; @@ -290,7 +232,7 @@ impl ServiceFactory for AppRoutingFactory { CreateAppRoutingItem::Future( Some(path.clone()), guards.borrow_mut().take(), - service.new_service(()).boxed_local(), + Box::pin(service.new_service(())), ) }) .collect(), @@ -307,7 +249,7 @@ type HttpServiceFut = LocalBoxFuture<'static, Result>; pub struct AppRoutingFactoryResponse { fut: Vec, default: Option, - default_fut: Option>>, + default_fut: Option, } enum CreateAppRoutingItem { @@ -367,7 +309,6 @@ impl Future for AppRoutingFactoryResponse { router }); Poll::Ready(Ok(AppRouting { - ready: None, router: router.finish(), default: self.default.take(), })) @@ -379,22 +320,15 @@ impl Future for AppRoutingFactoryResponse { pub struct AppRouting { router: Router, - ready: Option<(ServiceRequest, ResourceInfo)>, default: Option, } impl Service for AppRouting { type Response = ServiceResponse; type Error = Error; - type Future = BoxResponse; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - if self.ready.is_none() { - Poll::Ready(Ok(())) - } else { - Poll::Pending - } - } + actix_service::always_ready!(); fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_mut_checked(&mut req, |req, guards| { @@ -414,7 +348,9 @@ impl Service for AppRouting { default.call(req) } else { let req = req.into_parts().0; - ok(ServiceResponse::new(req, Response::NotFound().finish())).boxed_local() + Box::pin(async { + Ok(ServiceResponse::new(req, Response::NotFound().finish())) + }) } } } @@ -431,11 +367,11 @@ impl AppEntry { } impl ServiceFactory for AppEntry { - type Config = (); type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = AppRouting; + type InitError = (); type Future = AppRoutingFactoryResponse; fn new_service(&self, _: ()) -> Self::Future { From 57a3722146b399aa57f34f0bdb94d61cb02aabc7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 7 Jan 2021 02:11:20 +0800 Subject: [PATCH 098/187] More refactor of app_service (#1879) --- src/app_service.rs | 135 ++++++++++++--------------------------------- 1 file changed, 35 insertions(+), 100 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index 96040b8fb..442de9362 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,6 +1,4 @@ use std::cell::RefCell; -use std::future::Future; -use std::pin::Pin; use std::rc::Rc; use std::task::{Context, Poll}; @@ -216,111 +214,52 @@ pub struct AppRoutingFactory { } impl ServiceFactory for AppRoutingFactory { - type Config = (); type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = AppRouting; - type Future = AppRoutingFactoryResponse; + type InitError = (); + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - AppRoutingFactoryResponse { - fut: self - .services - .iter() - .map(|(path, service, guards)| { - CreateAppRoutingItem::Future( - Some(path.clone()), - guards.borrow_mut().take(), - Box::pin(service.new_service(())), - ) - }) - .collect(), - default: None, - default_fut: Some(self.default.new_service(())), - } - } -} + // construct all services factory future with it's resource def and guards. + let factory_fut = + join_all(self.services.iter().map(|(path, factory, guards)| { + let path = path.clone(); + let guards = guards.borrow_mut().take(); + let factory_fut = factory.new_service(()); + async move { + let service = factory_fut.await?; + Ok((path, guards, service)) + } + })); -type HttpServiceFut = LocalBoxFuture<'static, Result>; + // construct default service factory future + let default_fut = self.default.new_service(()); -/// Create app service -#[doc(hidden)] -pub struct AppRoutingFactoryResponse { - fut: Vec, - default: Option, - default_fut: Option, -} + Box::pin(async move { + let default = default_fut.await?; -enum CreateAppRoutingItem { - Future(Option, Option, HttpServiceFut), - Service(ResourceDef, Option, HttpService), -} - -impl Future for AppRoutingFactoryResponse { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match Pin::new(fut).poll(cx)? { - Poll::Ready(default) => self.default = Some(default), - Poll::Pending => done = false, - } - } - - // poll http services - for item in &mut self.fut { - let res = match item { - CreateAppRoutingItem::Future( - ref mut path, - ref mut guards, - ref mut fut, - ) => match Pin::new(fut).poll(cx) { - Poll::Ready(Ok(service)) => { - Some((path.take().unwrap(), guards.take(), service)) - } - Poll::Ready(Err(_)) => return Poll::Ready(Err(())), - Poll::Pending => { - done = false; - None - } - }, - CreateAppRoutingItem::Service(_, _, _) => continue, - }; - - if let Some((path, guards, service)) = res { - *item = CreateAppRoutingItem::Service(path, guards, service); - } - } - - if done { - let router = self - .fut + // build router from the factory future result. + let router = factory_fut + .await + .into_iter() + .collect::, _>>()? .drain(..) - .fold(Router::build(), |mut router, item| { - match item { - CreateAppRoutingItem::Service(path, guards, service) => { - router.rdef(path, service).2 = guards; - } - CreateAppRoutingItem::Future(_, _, _) => unreachable!(), - } + .fold(Router::build(), |mut router, (path, guards, service)| { + router.rdef(path, service).2 = guards; router - }); - Poll::Ready(Ok(AppRouting { - router: router.finish(), - default: self.default.take(), - })) - } else { - Poll::Pending - } + }) + .finish(); + + Ok(AppRouting { router, default }) + }) } } pub struct AppRouting { router: Router, - default: Option, + default: HttpService, } impl Service for AppRouting { @@ -344,13 +283,8 @@ impl Service for AppRouting { if let Some((srv, _info)) = res { srv.call(req) - } else if let Some(ref mut default) = self.default { - default.call(req) } else { - let req = req.into_parts().0; - Box::pin(async { - Ok(ServiceResponse::new(req, Response::NotFound().finish())) - }) + self.default.call(req) } } } @@ -372,7 +306,7 @@ impl ServiceFactory for AppEntry { type Config = (); type Service = AppRouting; type InitError = (); - type Future = AppRoutingFactoryResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(()) @@ -384,9 +318,10 @@ mod tests { use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; + use actix_service::Service; + use crate::test::{init_service, TestRequest}; use crate::{web, App, HttpResponse}; - use actix_service::Service; struct DropData(Arc); From a03dbe2dcfc02ad6c8d3e81d47f9b2defccce2db Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 7 Jan 2021 02:43:52 +0800 Subject: [PATCH 099/187] replace cloneable service with httpflow abstraction (#1876) --- actix-http/src/cloneable.rs | 39 ---------- actix-http/src/h1/dispatcher.rs | 128 ++++++++++++++++---------------- actix-http/src/h1/service.rs | 39 ++++------ actix-http/src/h2/dispatcher.rs | 27 +++---- actix-http/src/h2/service.rs | 45 +++++------ actix-http/src/lib.rs | 34 ++++++++- actix-http/src/service.rs | 65 +++++++++------- 7 files changed, 190 insertions(+), 187 deletions(-) delete mode 100644 actix-http/src/cloneable.rs diff --git a/actix-http/src/cloneable.rs b/actix-http/src/cloneable.rs deleted file mode 100644 index 5f0b1ea28..000000000 --- a/actix-http/src/cloneable.rs +++ /dev/null @@ -1,39 +0,0 @@ -use std::cell::RefCell; -use std::rc::Rc; -use std::task::{Context, Poll}; - -use actix_service::Service; - -/// Service that allows to turn non-clone service to a service with `Clone` impl -/// -/// # Panics -/// CloneableService might panic with some creative use of thread local storage. -/// See https://github.com/actix/actix-web/issues/1295 for example -#[doc(hidden)] -pub(crate) struct CloneableService(Rc>); - -impl CloneableService { - pub(crate) fn new(service: T) -> Self { - Self(Rc::new(RefCell::new(service))) - } -} - -impl Clone for CloneableService { - fn clone(&self) -> Self { - Self(self.0.clone()) - } -} - -impl, Req> Service for CloneableService { - type Response = T::Response; - type Error = T::Error; - type Future = T::Future; - - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.0.borrow_mut().poll_ready(cx) - } - - fn call(&mut self, req: Req) -> Self::Future { - self.0.borrow_mut().call(req) - } -} diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index a9510dc1e..60552d102 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,9 +1,11 @@ use std::{ + cell::RefCell, collections::VecDeque, fmt, future::Future, io, mem, net, pin::Pin, + rc::Rc, task::{Context, Poll}, }; @@ -15,17 +17,14 @@ use bytes::{Buf, BytesMut}; use log::{error, trace}; use pin_project::pin_project; -use crate::cloneable::CloneableService; +use crate::body::{Body, BodySize, MessageBody, ResponseBody}; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::error::{ParseError, PayloadError}; -use crate::httpmessage::HttpMessage; use crate::request::Request; use crate::response::Response; -use crate::{ - body::{Body, BodySize, MessageBody, ResponseBody}, - Extensions, -}; +use crate::service::HttpFlow; +use crate::OnConnectData; use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus}; @@ -78,7 +77,7 @@ where U::Error: fmt::Display, { Normal(#[pin] InnerDispatcher), - Upgrade(Pin>), + Upgrade(#[pin] U::Future), } #[pin_project(project = InnerDispatcherProj)] @@ -92,10 +91,8 @@ where U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { - service: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect_data: Extensions, + services: Rc>>, + on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, error: Option, @@ -180,10 +177,8 @@ where pub(crate) fn new( stream: T, config: ServiceConfig, - service: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect_data: Extensions, + services: Rc>>, + on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { Dispatcher::with_timeout( @@ -192,9 +187,7 @@ where config, BytesMut::with_capacity(HW_BUFFER_SIZE), None, - service, - expect, - upgrade, + services, on_connect_data, peer_addr, ) @@ -207,10 +200,8 @@ where config: ServiceConfig, read_buf: BytesMut, timeout: Option, - service: CloneableService, - expect: CloneableService, - upgrade: Option>, - on_connect_data: Extensions, + services: Rc>>, + on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { let keepalive = config.keep_alive_enabled(); @@ -239,9 +230,7 @@ where io: Some(io), codec, read_buf, - service, - expect, - upgrade, + services, on_connect_data, flags, peer_addr, @@ -395,7 +384,8 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); this = self.as_mut().project(); - this.state.set(State::ServiceCall(this.service.call(req))); + let fut = this.services.borrow_mut().service.call(req); + this.state.set(State::ServiceCall(fut)); continue; } Poll::Ready(Err(e)) => { @@ -483,12 +473,14 @@ where // Handle `EXPECT: 100-Continue` header if req.head().expect() { // set dispatcher state so the future is pinned. - let task = self.as_mut().project().expect.call(req); - self.as_mut().project().state.set(State::ExpectCall(task)); + let mut this = self.as_mut().project(); + let task = this.services.borrow_mut().expect.call(req); + this.state.set(State::ExpectCall(task)); } else { // the same as above. - let task = self.as_mut().project().service.call(req); - self.as_mut().project().state.set(State::ServiceCall(task)); + let mut this = self.as_mut().project(); + let task = this.services.borrow_mut().service.call(req); + this.state.set(State::ServiceCall(task)); }; // eagerly poll the future for once(or twice if expect is resolved immediately). @@ -499,8 +491,9 @@ where // expect is resolved. continue loop and poll the service call branch. Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); - let task = self.as_mut().project().service.call(req); - self.as_mut().project().state.set(State::ServiceCall(task)); + let mut this = self.as_mut().project(); + let task = this.services.borrow_mut().service.call(req); + this.state.set(State::ServiceCall(task)); continue; } // future is pending. return Ok(()) to notify that a new state is @@ -568,9 +561,11 @@ where req.head_mut().peer_addr = *this.peer_addr; // merge on_connect_ext data into request extensions - req.extensions_mut().drain_from(this.on_connect_data); + this.on_connect_data.merge_into(&mut req); - if pl == MessageType::Stream && this.upgrade.is_some() { + if pl == MessageType::Stream + && this.services.borrow().upgrade.is_some() + { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; } @@ -834,12 +829,17 @@ where ); parts.write_buf = mem::take(inner_p.write_buf); let framed = Framed::from_parts(parts); - let upgrade = - inner_p.upgrade.take().unwrap().call((req, framed)); + let upgrade = inner_p + .services + .borrow_mut() + .upgrade + .take() + .unwrap() + .call((req, framed)); self.as_mut() .project() .inner - .set(DispatcherState::Upgrade(Box::pin(upgrade))); + .set(DispatcherState::Upgrade(upgrade)); return self.poll(cx); } @@ -890,7 +890,7 @@ where } } } - DispatcherStateProj::Upgrade(fut) => fut.as_mut().poll(cx).map_err(|e| { + DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| { error!("Upgrade handler error: {}", e); DispatchError::Upgrade }), @@ -1028,13 +1028,13 @@ mod tests { lazy(|cx| { let buf = TestBuffer::new("GET /test HTTP/1\r\n\r\n"); + let services = HttpFlow::new(ok_service(), ExpectHandler, None); + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, ServiceConfig::default(), - CloneableService::new(ok_service()), - CloneableService::new(ExpectHandler), - None, - Extensions::new(), + services, + OnConnectData::default(), None, ); @@ -1068,13 +1068,13 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, - CloneableService::new(echo_path_service()), - CloneableService::new(ExpectHandler), - None, - Extensions::new(), + services, + OnConnectData::default(), None, ); @@ -1122,13 +1122,13 @@ mod tests { let cfg = ServiceConfig::new(KeepAlive::Disabled, 1, 1, false, None); + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf, cfg, - CloneableService::new(echo_path_service()), - CloneableService::new(ExpectHandler), - None, - Extensions::new(), + services, + OnConnectData::default(), None, ); @@ -1171,13 +1171,14 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(echo_payload_service(), ExpectHandler, None); + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, - CloneableService::new(echo_payload_service()), - CloneableService::new(ExpectHandler), - None, - Extensions::new(), + services, + OnConnectData::default(), None, ); @@ -1242,13 +1243,14 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = HttpFlow::new(echo_path_service(), ExpectHandler, None); + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, - CloneableService::new(echo_path_service()), - CloneableService::new(ExpectHandler), - None, - Extensions::new(), + services, + OnConnectData::default(), None, ); @@ -1301,13 +1303,15 @@ mod tests { lazy(|cx| { let mut buf = TestSeqBuffer::empty(); let cfg = ServiceConfig::new(KeepAlive::Disabled, 0, 0, false, None); + + let services = + HttpFlow::new(ok_service(), ExpectHandler, Some(UpgradeHandler)); + let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( buf.clone(), cfg, - CloneableService::new(ok_service()), - CloneableService::new(ExpectHandler), - Some(CloneableService::new(UpgradeHandler)), - Extensions::new(), + services, + OnConnectData::default(), None, ); diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 34b7e31a1..19272c133 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -12,12 +13,12 @@ use futures_core::ready; use futures_util::future::ready; use crate::body::MessageBody; -use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; -use crate::{ConnectCallback, Extensions}; +use crate::service::HttpFlow; +use crate::{ConnectCallback, OnConnectData}; use super::codec::Codec; use super::dispatcher::Dispatcher; @@ -299,7 +300,7 @@ where upgrade: Option, on_connect_ext: Option>>, cfg: Option, - _phantom: PhantomData<(T, B)>, + _phantom: PhantomData, } impl Future for H1ServiceResponse @@ -366,9 +367,7 @@ where X: Service, U: Service<(Request, Framed)>, { - srv: CloneableService, - expect: CloneableService, - upgrade: Option>, + services: Rc>>, on_connect_ext: Option>>, cfg: ServiceConfig, _phantom: PhantomData, @@ -387,15 +386,13 @@ where { fn new( cfg: ServiceConfig, - srv: S, + service: S, expect: X, upgrade: Option, on_connect_ext: Option>>, ) -> H1ServiceHandler { H1ServiceHandler { - srv: CloneableService::new(srv), - expect: CloneableService::new(expect), - upgrade: upgrade.map(CloneableService::new), + services: HttpFlow::new(service, expect, upgrade), cfg, on_connect_ext, _phantom: PhantomData, @@ -421,7 +418,8 @@ where type Future = Dispatcher; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let ready = self + let mut services = self.services.borrow_mut(); + let ready = services .expect .poll_ready(cx) .map_err(|e| { @@ -431,8 +429,8 @@ where })? .is_ready(); - let ready = self - .srv + let ready = services + .service .poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -442,7 +440,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = self.upgrade { + let ready = if let Some(ref mut upg) = services.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -463,19 +461,14 @@ where } fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { - let mut connect_extensions = Extensions::new(); - if let Some(ref handler) = self.on_connect_ext { - // run on_connect_ext callback, populating connect extensions - handler(&io, &mut connect_extensions); - } + let on_connect_data = + OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); Dispatcher::new( io, self.cfg.clone(), - self.srv.clone(), - self.expect.clone(), - self.upgrade.clone(), - connect_extensions, + self.services.clone(), + on_connect_data, addr, ) } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index b8828edd0..621035869 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,7 +1,9 @@ +use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::net; use std::pin::Pin; +use std::rc::Rc; use std::task::{Context, Poll}; use std::{cmp, convert::TryFrom}; @@ -16,29 +18,28 @@ use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCOD use log::{error, trace}; use crate::body::{BodySize, MessageBody, ResponseBody}; -use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; -use crate::httpmessage::HttpMessage; use crate::message::ResponseHead; use crate::payload::Payload; use crate::request::Request; use crate::response::Response; -use crate::Extensions; +use crate::service::HttpFlow; +use crate::OnConnectData; const CHUNK_SIZE: usize = 16_384; /// Dispatcher for HTTP/2 protocol. #[pin_project::pin_project] -pub struct Dispatcher +pub struct Dispatcher where T: AsyncRead + AsyncWrite + Unpin, S: Service, B: MessageBody, { - service: CloneableService, + services: Rc>>, connection: Connection, - on_connect_data: Extensions, + on_connect_data: OnConnectData, config: ServiceConfig, peer_addr: Option, ka_expire: Instant, @@ -46,7 +47,7 @@ where _phantom: PhantomData, } -impl Dispatcher +impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, S: Service, @@ -55,9 +56,9 @@ where B: MessageBody, { pub(crate) fn new( - service: CloneableService, + services: Rc>>, connection: Connection, - on_connect_data: Extensions, + on_connect_data: OnConnectData, config: ServiceConfig, timeout: Option, peer_addr: Option, @@ -79,7 +80,7 @@ where }; Dispatcher { - service, + services, config, peer_addr, connection, @@ -91,7 +92,7 @@ where } } -impl Future for Dispatcher +impl Future for Dispatcher where T: AsyncRead + AsyncWrite + Unpin, S: Service, @@ -133,11 +134,11 @@ where head.peer_addr = this.peer_addr; // merge on_connect_ext data into request extensions - req.extensions_mut().drain_from(&mut this.on_connect_data); + this.on_connect_data.merge_into(&mut req); let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( - this.service.call(req), + this.services.borrow_mut().service.call(req), Some(res), ), config: this.config.clone(), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 462f5c2c1..f94aae79e 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -17,12 +18,12 @@ use h2::server::{self, Handshake}; use log::error; use crate::body::MessageBody; -use crate::cloneable::CloneableService; use crate::config::ServiceConfig; use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; -use crate::{ConnectCallback, Extensions}; +use crate::service::HttpFlow; +use crate::{ConnectCallback, OnConnectData}; use super::dispatcher::Dispatcher; @@ -248,7 +249,7 @@ pub struct H2ServiceHandler where S: Service, { - srv: CloneableService, + services: Rc>>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -265,12 +266,12 @@ where fn new( cfg: ServiceConfig, on_connect_ext: Option>>, - srv: S, + service: S, ) -> H2ServiceHandler { H2ServiceHandler { + services: HttpFlow::new(service, (), None), cfg, on_connect_ext, - srv: CloneableService::new(srv), _phantom: PhantomData, } } @@ -290,26 +291,27 @@ where type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.srv.poll_ready(cx).map_err(|e| { - let e = e.into(); - error!("Service readiness error: {:?}", e); - DispatchError::Service(e) - }) + self.services + .borrow_mut() + .service + .poll_ready(cx) + .map_err(|e| { + let e = e.into(); + error!("Service readiness error: {:?}", e); + DispatchError::Service(e) + }) } fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { - let mut connect_extensions = Extensions::new(); - if let Some(ref handler) = self.on_connect_ext { - // run on_connect_ext callback, populating connect extensions - handler(&io, &mut connect_extensions); - } + let on_connect_data = + OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); H2ServiceHandlerResponse { state: State::Handshake( - Some(self.srv.clone()), + Some(self.services.clone()), Some(self.cfg.clone()), addr, - Some(connect_extensions), + on_connect_data, server::handshake(io), ), } @@ -321,12 +323,12 @@ where T: AsyncRead + AsyncWrite + Unpin, S::Future: 'static, { - Incoming(Dispatcher), + Incoming(Dispatcher), Handshake( - Option>, + Option>>>, Option, Option, - Option, + OnConnectData, Handshake, ), } @@ -365,10 +367,11 @@ where ref mut handshake, ) => match ready!(Pin::new(handshake).poll(cx)) { Ok(conn) => { + let on_connect_data = std::mem::take(on_connect_data); self.state = State::Incoming(Dispatcher::new( srv.take().unwrap(), conn, - on_connect_data.take().unwrap(), + on_connect_data, config.take().unwrap(), None, *peer_addr, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 94cc50a76..0c58df2ed 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -19,7 +19,6 @@ mod macros; pub mod body; mod builder; pub mod client; -mod cloneable; mod config; #[cfg(feature = "compress")] pub mod encoding; @@ -81,3 +80,36 @@ pub enum Protocol { } type ConnectCallback = dyn Fn(&IO, &mut Extensions); + +/// Container for data that extract with ConnectCallback. +pub(crate) struct OnConnectData(Option); + +impl Default for OnConnectData { + fn default() -> Self { + Self(None) + } +} + +impl OnConnectData { + // construct self from io. + pub(crate) fn from_io( + io: &T, + on_connect_ext: Option<&ConnectCallback>, + ) -> Self { + let ext = on_connect_ext.map(|handler| { + let mut extensions = Extensions::new(); + handler(io, &mut extensions); + extensions + }); + + Self(ext) + } + + // merge self to given request's head extension. + #[inline] + pub(crate) fn merge_into(&mut self, req: &mut Request) { + if let Some(ref mut ext) = self.0 { + req.head.extensions.get_mut().drain_from(ext); + } + } +} diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index af625b1bf..eb16a6e70 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,3 +1,4 @@ +use std::cell::RefCell; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; @@ -14,12 +15,11 @@ use pin_project::pin_project; use crate::body::MessageBody; use crate::builder::HttpServiceBuilder; -use crate::cloneable::CloneableService; use crate::config::{KeepAlive, ServiceConfig}; use crate::error::{DispatchError, Error}; use crate::request::Request; use crate::response::Response; -use crate::{h1, h2::Dispatcher, ConnectCallback, Extensions, Protocol}; +use crate::{h1, h2::Dispatcher, ConnectCallback, OnConnectData, Protocol}; /// A `ServiceFactory` for HTTP/1.1 or HTTP/2 protocol. pub struct HttpService { @@ -371,7 +371,7 @@ where upgrade: Option, on_connect_ext: Option>>, cfg: ServiceConfig, - _phantom: PhantomData<(T, B)>, + _phantom: PhantomData, } impl Future for HttpServiceResponse @@ -441,14 +441,29 @@ where X: Service, U: Service<(Request, Framed)>, { - srv: CloneableService, - expect: CloneableService, - upgrade: Option>, + services: Rc>>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, } +// a collection of service for http. +pub(super) struct HttpFlow { + pub(super) service: S, + pub(super) expect: X, + pub(super) upgrade: Option, +} + +impl HttpFlow { + pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc> { + Rc::new(RefCell::new(Self { + service, + expect, + upgrade, + })) + } +} + impl HttpServiceHandler where S: Service, @@ -463,7 +478,7 @@ where { fn new( cfg: ServiceConfig, - srv: S, + service: S, expect: X, upgrade: Option, on_connect_ext: Option>>, @@ -471,9 +486,7 @@ where HttpServiceHandler { cfg, on_connect_ext, - srv: CloneableService::new(srv), - expect: CloneableService::new(expect), - upgrade: upgrade.map(CloneableService::new), + services: HttpFlow::new(service, expect, upgrade), _phantom: PhantomData, } } @@ -498,7 +511,8 @@ where type Future = HttpServiceHandlerResponse; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let ready = self + let mut services = self.services.borrow_mut(); + let ready = services .expect .poll_ready(cx) .map_err(|e| { @@ -508,8 +522,8 @@ where })? .is_ready(); - let ready = self - .srv + let ready = services + .service .poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -519,7 +533,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = self.upgrade { + let ready = if let Some(ref mut upg) = services.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -543,19 +557,16 @@ where &mut self, (io, proto, peer_addr): (T, Protocol, Option), ) -> Self::Future { - let mut connect_extensions = Extensions::new(); - - if let Some(ref handler) = self.on_connect_ext { - handler(&io, &mut connect_extensions); - } + let on_connect_data = + OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); match proto { Protocol::Http2 => HttpServiceHandlerResponse { state: State::H2Handshake(Some(( server::handshake(io), self.cfg.clone(), - self.srv.clone(), - connect_extensions, + self.services.clone(), + on_connect_data, peer_addr, ))), }, @@ -564,10 +575,8 @@ where state: State::H1(h1::Dispatcher::new( io, self.cfg.clone(), - self.srv.clone(), - self.expect.clone(), - self.upgrade.clone(), - connect_extensions, + self.services.clone(), + on_connect_data, peer_addr, )), }, @@ -589,13 +598,13 @@ where U::Error: fmt::Display, { H1(#[pin] h1::Dispatcher), - H2(#[pin] Dispatcher), + H2(#[pin] Dispatcher), H2Handshake( Option<( Handshake, ServiceConfig, - CloneableService, - Extensions, + Rc>>, + OnConnectData, Option, )>, ), From 51e9e1500b18a5ce55f90085f1397ef8f6c3d1cf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 6 Jan 2021 18:52:06 +0000 Subject: [PATCH 100/187] add docs to recent additions --- actix-http/src/h1/dispatcher.rs | 16 ++++++++-------- actix-http/src/h1/service.rs | 14 +++++++------- actix-http/src/h2/dispatcher.rs | 6 +++--- actix-http/src/h2/service.rs | 8 ++++---- actix-http/src/lib.rs | 9 ++++++--- actix-http/src/service.rs | 18 +++++++++--------- 6 files changed, 37 insertions(+), 34 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 60552d102..a914880ce 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -91,7 +91,7 @@ where U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { - services: Rc>>, + flow: Rc>>, on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, @@ -230,7 +230,7 @@ where io: Some(io), codec, read_buf, - services, + flow: services, on_connect_data, flags, peer_addr, @@ -384,7 +384,7 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); this = self.as_mut().project(); - let fut = this.services.borrow_mut().service.call(req); + let fut = this.flow.borrow_mut().service.call(req); this.state.set(State::ServiceCall(fut)); continue; } @@ -474,12 +474,12 @@ where if req.head().expect() { // set dispatcher state so the future is pinned. let mut this = self.as_mut().project(); - let task = this.services.borrow_mut().expect.call(req); + let task = this.flow.borrow_mut().expect.call(req); this.state.set(State::ExpectCall(task)); } else { // the same as above. let mut this = self.as_mut().project(); - let task = this.services.borrow_mut().service.call(req); + let task = this.flow.borrow_mut().service.call(req); this.state.set(State::ServiceCall(task)); }; @@ -492,7 +492,7 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); let mut this = self.as_mut().project(); - let task = this.services.borrow_mut().service.call(req); + let task = this.flow.borrow_mut().service.call(req); this.state.set(State::ServiceCall(task)); continue; } @@ -564,7 +564,7 @@ where this.on_connect_data.merge_into(&mut req); if pl == MessageType::Stream - && this.services.borrow().upgrade.is_some() + && this.flow.borrow().upgrade.is_some() { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; @@ -830,7 +830,7 @@ where parts.write_buf = mem::take(inner_p.write_buf); let framed = Framed::from_parts(parts); let upgrade = inner_p - .services + .flow .borrow_mut() .upgrade .take() diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 19272c133..067c8b647 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -367,7 +367,7 @@ where X: Service, U: Service<(Request, Framed)>, { - services: Rc>>, + flow: Rc>>, on_connect_ext: Option>>, cfg: ServiceConfig, _phantom: PhantomData, @@ -392,7 +392,7 @@ where on_connect_ext: Option>>, ) -> H1ServiceHandler { H1ServiceHandler { - services: HttpFlow::new(service, expect, upgrade), + flow: HttpFlow::new(service, expect, upgrade), cfg, on_connect_ext, _phantom: PhantomData, @@ -418,8 +418,8 @@ where type Future = Dispatcher; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut services = self.services.borrow_mut(); - let ready = services + let mut flow = self.flow.borrow_mut(); + let ready = flow .expect .poll_ready(cx) .map_err(|e| { @@ -429,7 +429,7 @@ where })? .is_ready(); - let ready = services + let ready = flow .service .poll_ready(cx) .map_err(|e| { @@ -440,7 +440,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = services.upgrade { + let ready = if let Some(ref mut upg) = flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -467,7 +467,7 @@ where Dispatcher::new( io, self.cfg.clone(), - self.services.clone(), + self.flow.clone(), on_connect_data, addr, ) diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 621035869..959c34f13 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -37,7 +37,7 @@ where S: Service, B: MessageBody, { - services: Rc>>, + flow: Rc>>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -80,7 +80,7 @@ where }; Dispatcher { - services, + flow: services, config, peer_addr, connection, @@ -138,7 +138,7 @@ where let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( - this.services.borrow_mut().service.call(req), + this.flow.borrow_mut().service.call(req), Some(res), ), config: this.config.clone(), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index f94aae79e..95ff3de26 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -249,7 +249,7 @@ pub struct H2ServiceHandler where S: Service, { - services: Rc>>, + flow: Rc>>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -269,7 +269,7 @@ where service: S, ) -> H2ServiceHandler { H2ServiceHandler { - services: HttpFlow::new(service, (), None), + flow: HttpFlow::new(service, (), None), cfg, on_connect_ext, _phantom: PhantomData, @@ -291,7 +291,7 @@ where type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.services + self.flow .borrow_mut() .service .poll_ready(cx) @@ -308,7 +308,7 @@ where H2ServiceHandlerResponse { state: State::Handshake( - Some(self.services.clone()), + Some(self.flow.clone()), Some(self.cfg.clone()), addr, on_connect_data, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 0c58df2ed..3879bae81 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -72,7 +72,7 @@ pub mod http { pub use crate::message::ConnectionType; } -/// Http protocol +/// HTTP protocol #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] pub enum Protocol { Http1, @@ -82,6 +82,9 @@ pub enum Protocol { type ConnectCallback = dyn Fn(&IO, &mut Extensions); /// Container for data that extract with ConnectCallback. +/// +/// # Implementation Details +/// Uses Option to reduce necessary allocations when merging with request extensions. pub(crate) struct OnConnectData(Option); impl Default for OnConnectData { @@ -91,7 +94,7 @@ impl Default for OnConnectData { } impl OnConnectData { - // construct self from io. + /// Construct by calling the on-connect callback with the underlying transport I/O. pub(crate) fn from_io( io: &T, on_connect_ext: Option<&ConnectCallback>, @@ -105,7 +108,7 @@ impl OnConnectData { Self(ext) } - // merge self to given request's head extension. + /// Merge self into given request's extensions. #[inline] pub(crate) fn merge_into(&mut self, req: &mut Request) { if let Some(ref mut ext) = self.0 { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index eb16a6e70..e137ab6fa 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -441,13 +441,13 @@ where X: Service, U: Service<(Request, Framed)>, { - services: Rc>>, + flow: Rc>>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, } -// a collection of service for http. +/// A collection of services that describe an HTTP request flow. pub(super) struct HttpFlow { pub(super) service: S, pub(super) expect: X, @@ -486,7 +486,7 @@ where HttpServiceHandler { cfg, on_connect_ext, - services: HttpFlow::new(service, expect, upgrade), + flow: HttpFlow::new(service, expect, upgrade), _phantom: PhantomData, } } @@ -511,8 +511,8 @@ where type Future = HttpServiceHandlerResponse; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut services = self.services.borrow_mut(); - let ready = services + let mut flow = self.flow.borrow_mut(); + let ready = flow .expect .poll_ready(cx) .map_err(|e| { @@ -522,7 +522,7 @@ where })? .is_ready(); - let ready = services + let ready = flow .service .poll_ready(cx) .map_err(|e| { @@ -533,7 +533,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = services.upgrade { + let ready = if let Some(ref mut upg) = flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -565,7 +565,7 @@ where state: State::H2Handshake(Some(( server::handshake(io), self.cfg.clone(), - self.services.clone(), + self.flow.clone(), on_connect_data, peer_addr, ))), @@ -575,7 +575,7 @@ where state: State::H1(h1::Dispatcher::new( io, self.cfg.clone(), - self.services.clone(), + self.flow.clone(), on_connect_data, peer_addr, )), From 00ba8d55492284581695d824648590715a8bd386 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 6 Jan 2021 18:58:24 +0000 Subject: [PATCH 101/187] add http3 variant to protocol enum --- actix-http/CHANGES.md | 3 +++ actix-http/src/lib.rs | 4 +++- actix-http/src/service.rs | 2 ++ 3 files changed, 8 insertions(+), 1 deletion(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index eadbf6f46..6daed67a0 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. + ### Changed * Update `actix-*` dependencies to tokio `1.0` based versions. [#1813] * Bumped `rand` to `0.8`. diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 3879bae81..e17b7de0a 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -72,11 +72,13 @@ pub mod http { pub use crate::message::ConnectionType; } -/// HTTP protocol +/// A major HTTP protocol version. #[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)] +#[non_exhaustive] pub enum Protocol { Http1, Http2, + Http3, } type ConnectCallback = dyn Fn(&IO, &mut Extensions); diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index e137ab6fa..392a1426f 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -580,6 +580,8 @@ where peer_addr, )), }, + + proto => unimplemented!("Unsupported HTTP version: {:?}.", proto) } } } From 85753130d98c1e766b729f22d1537097a0cf8a72 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 7 Jan 2021 00:35:19 +0000 Subject: [PATCH 102/187] fmt --- actix-http/src/h2/service.rs | 14 +++++--------- actix-http/src/service.rs | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 95ff3de26..36f7dc311 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -291,15 +291,11 @@ where type Future = H2ServiceHandlerResponse; fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.flow - .borrow_mut() - .service - .poll_ready(cx) - .map_err(|e| { - let e = e.into(); - error!("Service readiness error: {:?}", e); - DispatchError::Service(e) - }) + self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| { + let e = e.into(); + error!("Service readiness error: {:?}", e); + DispatchError::Service(e) + }) } fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 392a1426f..6236e9fbe 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -581,7 +581,7 @@ where )), }, - proto => unimplemented!("Unsupported HTTP version: {:?}.", proto) + proto => unimplemented!("Unsupported HTTP version: {:?}.", proto), } } } From 6d710629afeb7b83725ce56141bded20d7e931e7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 7 Jan 2021 08:57:34 +0800 Subject: [PATCH 103/187] fix bug where upgrade future is not reset properly (#1880) --- actix-http/src/body.rs | 3 +- actix-http/src/h1/service.rs | 2 +- actix-http/src/service.rs | 80 +++++++++++++----------------------- 3 files changed, 30 insertions(+), 55 deletions(-) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 5d1cf7329..d5d1989d4 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -3,8 +3,7 @@ use std::task::{Context, Poll}; use std::{fmt, mem}; use bytes::{Bytes, BytesMut}; -use futures_core::Stream; -use futures_util::ready; +use futures_core::{ready, Stream}; use pin_project::pin_project; use crate::error::Error; diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 067c8b647..aed700eed 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -338,7 +338,7 @@ where .map_err(|e| log::error!("Init http service error: {:?}", e)))?; this = self.as_mut().project(); *this.upgrade = Some(upgrade); - this.fut_ex.set(None); + this.fut_upg.set(None); } let result = ready!(this diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 6236e9fbe..357ac4c53 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -9,7 +9,6 @@ use actix_rt::net::TcpStream; use actix_service::{pipeline_factory, IntoServiceFactory, Service, ServiceFactory}; use bytes::Bytes; use futures_core::{ready, Future}; -use futures_util::future::ok; use h2::server::{self, Handshake}; use pin_project::pin_project; @@ -175,9 +174,9 @@ where Error = DispatchError, InitError = (), > { - pipeline_factory(|io: TcpStream| { + pipeline_factory(|io: TcpStream| async { let peer_addr = io.peer_addr().ok(); - ok((io, Protocol::Http1, peer_addr)) + Ok((io, Protocol::Http1, peer_addr)) }) .and_then(self) } @@ -227,7 +226,7 @@ mod openssl { .map_err(TlsError::Tls) .map_init_err(|_| panic!()), ) - .and_then(|io: SslStream| { + .and_then(|io: SslStream| async { let proto = if let Some(protos) = io.ssl().selected_alpn_protocol() { if protos.windows(2).any(|window| window == b"h2") { Protocol::Http2 @@ -238,7 +237,7 @@ mod openssl { Protocol::Http1 }; let peer_addr = io.get_ref().peer_addr().ok(); - ok((io, proto, peer_addr)) + Ok((io, proto, peer_addr)) }) .and_then(self.map_err(TlsError::Service)) } @@ -295,7 +294,7 @@ mod rustls { .map_err(TlsError::Tls) .map_init_err(|_| panic!()), ) - .and_then(|io: TlsStream| { + .and_then(|io: TlsStream| async { let proto = if let Some(protos) = io.get_ref().1.get_alpn_protocol() { if protos.windows(2).any(|window| window == b"h2") { Protocol::Http2 @@ -306,7 +305,7 @@ mod rustls { Protocol::Http1 }; let peer_addr = io.get_ref().0.peer_addr().ok(); - ok((io, proto, peer_addr)) + Ok((io, proto, peer_addr)) }) .and_then(self.map_err(TlsError::Service)) } @@ -413,7 +412,7 @@ where .map_err(|e| log::error!("Init http service error: {:?}", e)))?; this = self.as_mut().project(); *this.upgrade = Some(upgrade); - this.fut_ex.set(None); + this.fut_upg.set(None); } let result = ready!(this @@ -645,53 +644,30 @@ where { type Output = Result<(), DispatchError>; - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - self.project().state.poll(cx) - } -} - -impl State -where - T: AsyncRead + AsyncWrite + Unpin, - S: Service, - S::Error: Into + 'static, - S::Response: Into> + 'static, - B: MessageBody + 'static, - X: Service, - X::Error: Into, - U: Service<(Request, Framed), Response = ()>, - U::Error: fmt::Display, -{ - fn poll( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.as_mut().project() { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project().state.project() { StateProj::H1(disp) => disp.poll(cx), StateProj::H2(disp) => disp.poll(cx), - StateProj::H2Handshake(ref mut data) => { - let conn = if let Some(ref mut item) = data { - match Pin::new(&mut item.0).poll(cx) { - Poll::Ready(Ok(conn)) => conn, - Poll::Ready(Err(err)) => { - trace!("H2 handshake error: {}", err); - return Poll::Ready(Err(err.into())); - } - Poll::Pending => return Poll::Pending, + StateProj::H2Handshake(data) => { + match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { + Ok(conn) => { + let (_, cfg, srv, on_connect_data, peer_addr) = + data.take().unwrap(); + self.as_mut().project().state.set(State::H2(Dispatcher::new( + srv, + conn, + on_connect_data, + cfg, + None, + peer_addr, + ))); + self.poll(cx) } - } else { - panic!() - }; - let (_, cfg, srv, on_connect_data, peer_addr) = data.take().unwrap(); - self.set(State::H2(Dispatcher::new( - srv, - conn, - on_connect_data, - cfg, - None, - peer_addr, - ))); - self.poll(cx) + Err(err) => { + trace!("H2 handshake error: {}", err); + Poll::Ready(Err(err.into())) + } + } } } } From dc23559f23f9a14f9ff48d6fa71735e70de8edb6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 7 Jan 2021 01:13:46 +0000 Subject: [PATCH 104/187] address clippy lints --- actix-http/src/ws/proto.rs | 20 ++++++++++++-------- awc/src/sender.rs | 12 ++++++------ src/service.rs | 6 +++--- src/types/either.rs | 6 +++--- 4 files changed, 24 insertions(+), 20 deletions(-) diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 6fa3debc5..93468d232 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -1,7 +1,6 @@ use std::convert::{From, Into}; use std::fmt; -use self::OpCode::*; /// Operation codes as part of RFC6455. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum OpCode { @@ -29,6 +28,7 @@ pub enum OpCode { impl fmt::Display for OpCode { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + use self::OpCode::*; match *self { Continue => write!(f, "CONTINUE"), Text => write!(f, "TEXT"), @@ -41,9 +41,10 @@ impl fmt::Display for OpCode { } } -impl Into for OpCode { - fn into(self) -> u8 { - match self { +impl From for u8 { + fn from(op: OpCode) -> u8 { + use self::OpCode::*; + match op { Continue => 0, Text => 1, Binary => 2, @@ -60,6 +61,7 @@ impl Into for OpCode { impl From for OpCode { fn from(byte: u8) -> OpCode { + use self::OpCode::*; match byte { 0 => Continue, 1 => Text, @@ -72,7 +74,6 @@ impl From for OpCode { } } -use self::CloseCode::*; /// Status code used to indicate why an endpoint is closing the `WebSocket` /// connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] @@ -138,9 +139,10 @@ pub enum CloseCode { Other(u16), } -impl Into for CloseCode { - fn into(self) -> u16 { - match self { +impl From for u16 { + fn from(code: CloseCode) -> u16 { + use self::CloseCode::*; + match code { Normal => 1000, Away => 1001, Protocol => 1002, @@ -161,6 +163,7 @@ impl Into for CloseCode { impl From for CloseCode { fn from(code: u16) -> CloseCode { + use self::CloseCode::*; match code { 1000 => Normal, 1001 => Away, @@ -185,6 +188,7 @@ impl From for CloseCode { pub struct CloseReason { /// Exit code pub code: CloseCode, + /// Optional description of the exit code pub description: Option, } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index d4d3d9b72..ebf87e23b 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -33,18 +33,18 @@ pub(crate) enum PrepForSendingError { Http(HttpError), } -impl Into for PrepForSendingError { - fn into(self) -> FreezeRequestError { - match self { +impl From for FreezeRequestError { + fn from(err: PrepForSendingError) -> FreezeRequestError { + match err { PrepForSendingError::Url(e) => FreezeRequestError::Url(e), PrepForSendingError::Http(e) => FreezeRequestError::Http(e), } } } -impl Into for PrepForSendingError { - fn into(self) -> SendRequestError { - match self { +impl From for SendRequestError { + fn from(err: PrepForSendingError) -> SendRequestError { + match err { PrepForSendingError::Url(e) => SendRequestError::Url(e), PrepForSendingError::Http(e) => SendRequestError::Http(e), } diff --git a/src/service.rs b/src/service.rs index e6f71ed06..b88dac465 100644 --- a/src/service.rs +++ b/src/service.rs @@ -416,9 +416,9 @@ impl ServiceResponse { } } -impl Into> for ServiceResponse { - fn into(self) -> Response { - self.response +impl From> for Response { + fn into(res: ServiceResponse) -> Response { + res.response } } diff --git a/src/types/either.rs b/src/types/either.rs index 9f1d81a0b..3d4d6bf05 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -121,13 +121,13 @@ pub enum EitherExtractError { Extract(A, B), } -impl Into for EitherExtractError +impl From> for Error where A: Into, B: Into, { - fn into(self) -> Error { - match self { + fn into(err: EitherExtractError) -> Error { + match err { EitherExtractError::Bytes(err) => err, EitherExtractError::Extract(a_err, _b_err) => a_err.into(), } From d3c476b8c2d7daaa9a221b242bda3070db7f8eb0 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 7 Jan 2021 02:41:05 +0000 Subject: [PATCH 105/187] use env_logger builders in examples --- examples/on_connect.rs | 8 ++------ examples/uds.rs | 3 +-- src/service.rs | 2 +- src/types/either.rs | 2 +- 4 files changed, 5 insertions(+), 10 deletions(-) diff --git a/examples/on_connect.rs b/examples/on_connect.rs index bdad7e67e..ba5a18f3f 100644 --- a/examples/on_connect.rs +++ b/examples/on_connect.rs @@ -4,7 +4,7 @@ //! For an example of extracting a client TLS certificate, see: //! -use std::{any::Any, env, io, net::SocketAddr}; +use std::{any::Any, io, net::SocketAddr}; use actix_web::{dev::Extensions, rt::net::TcpStream, web, App, HttpServer}; @@ -36,11 +36,7 @@ fn get_conn_info(connection: &dyn Any, data: &mut Extensions) { #[actix_web::main] async fn main() -> io::Result<()> { - if env::var("RUST_LOG").is_err() { - env::set_var("RUST_LOG", "info"); - } - - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); HttpServer::new(|| App::new().default_service(web::to(route_whoami))) .on_connect(get_conn_info) diff --git a/examples/uds.rs b/examples/uds.rs index e34fa5ac9..c0a5d76a6 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -22,8 +22,7 @@ async fn no_params() -> &'static str { #[cfg(unix)] #[actix_web::main] async fn main() -> std::io::Result<()> { - std::env::set_var("RUST_LOG", "actix_server=info,actix_web=info"); - env_logger::init(); + env_logger::init_from_env(env_logger::Env::new().default_filter_or("info")); HttpServer::new(|| { App::new() diff --git a/src/service.rs b/src/service.rs index b88dac465..c6a961efc 100644 --- a/src/service.rs +++ b/src/service.rs @@ -417,7 +417,7 @@ impl ServiceResponse { } impl From> for Response { - fn into(res: ServiceResponse) -> Response { + fn from(res: ServiceResponse) -> Response { res.response } } diff --git a/src/types/either.rs b/src/types/either.rs index 3d4d6bf05..8a046d291 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -126,7 +126,7 @@ where A: Into, B: Into, { - fn into(err: EitherExtractError) -> Error { + fn from(err: EitherExtractError) -> Error { match err { EitherExtractError::Bytes(err) => err, EitherExtractError::Extract(a_err, _b_err) => a_err.into(), From c09186a2c07076a02d53137f31fef59f78fe95d5 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 7 Jan 2021 20:02:08 +0000 Subject: [PATCH 106/187] prepare v4 beta releases (#1881) --- CHANGES.md | 3 +++ Cargo.toml | 9 +++++---- actix-files/CHANGES.md | 3 +++ actix-files/Cargo.toml | 6 +++--- actix-http-test/CHANGES.md | 3 +++ actix-http-test/Cargo.toml | 8 ++++---- actix-http/CHANGES.md | 3 +++ actix-http/Cargo.toml | 4 ++-- actix-multipart/CHANGES.md | 12 ++++++++---- actix-multipart/Cargo.toml | 8 ++++---- actix-web-actors/CHANGES.md | 4 ++++ actix-web-actors/Cargo.toml | 6 +++--- actix-web-codegen/Cargo.toml | 2 +- awc/CHANGES.md | 3 +++ awc/Cargo.toml | 12 ++++++------ 15 files changed, 55 insertions(+), 31 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index f0b55801b..25fd10952 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.1 - 2021-01-07 ### Added * `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and `Compress` to be used in `middleware::Condition` and `Resource`, `Scope` services. [#1865] diff --git a/Cargo.toml b/Cargo.toml index 5388de4ed..87183c327 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "3.3.2" +version = "4.0.0-beta.1" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" @@ -84,8 +84,8 @@ actix-threadpool = "0.3.1" actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } actix-web-codegen = "0.4.0" -actix-http = "2.2.0" -awc = { version = "2.0.3", default-features = false } +actix-http = "3.0.0-beta.1" +awc = { version = "3.0.0-beta.1", default-features = false } ahash = "0.6" bytes = "1" @@ -109,7 +109,7 @@ smallvec = "1.6" [dev-dependencies] actix = "0.11.0-beta.1" -actix-http = { version = "2.2.0", features = ["actors"] } +actix-http = { version = "3.0.0-beta.1", features = ["actors"] } rand = "0.8" env_logger = "0.8" serde_derive = "1.0" @@ -126,6 +126,7 @@ codegen-units = 1 actix-web = { path = "." } actix-http = { path = "actix-http" } actix-http-test = { path = "actix-http-test" } +actix-web-actors = { path = "actix-web-actors" } actix-web-codegen = { path = "actix-web-codegen" } actix-multipart = { path = "actix-multipart" } actix-files = { path = "actix-files" } diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 6dcf4f66f..ff8ccd640 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.1 - 2021-01-07 * `HttpRange::parse` now has its own error type. * Update `bytes` to `1.0`. [#1813] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 17e1a4888..f93450ff8 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.5.0" +version = "0.6.0-beta.1" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "3.0.0", default-features = false } +actix-web = { version = "4.0.0-beta.1", default-features = false } actix-service = "2.0.0-beta.2" bitflags = "1" bytes = "1" @@ -32,4 +32,4 @@ v_htmlescape = "0.12" [dev-dependencies] actix-rt = "2.0.0-beta.1" -actix-web = "3.0.0" +actix-web = "4.0.0-beta.1" diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index 6ed6a0603..ed6d87a60 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.1 - 2021-01-07 * Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 910fbab73..a056b833e 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "2.1.0" +version = "3.0.0-beta.1" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.2" actix-utils = "3.0.0-beta.1" actix-rt = "2.0.0-beta.1" actix-server = "2.0.0-beta.2" -awc = "2.0.0" +awc = "3.0.0-beta.1" base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ time = { version = "0.2.7", default-features = false, features = ["std"] } open-ssl = { version = "0.10", package = "openssl", optional = true } [dev-dependencies] -actix-web = "3.0.0" -actix-http = "2.0.0" +actix-web = "4.0.0-beta.1" +actix-http = "3.0.0-beta.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6daed67a0..6abd0ba76 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.1 - 2021-01-07 ### Added * Add `Http3` to `Protocol` enum for future compatibility and also mark `#[non_exhaustive]`. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e80800d06..1c8206ef2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "2.2.0" +version = "3.0.0-beta.1" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" @@ -87,7 +87,7 @@ flate2 = { version = "1.0.13", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.2" -actix-http-test = { version = "2.0.0", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.7" diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 4c6f01d29..e1fe9c4af 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,17 +1,21 @@ # Changes ## Unreleased - 2021-xx-xx -* Fix multipart consuming payload before header checks #1513 + + +## 0.4.0-beta.1 - 2021-01-07 +* Fix multipart consuming payload before header checks. [#1513] * Update `bytes` to `1.0`. [#1813] [#1813]: https://github.com/actix/actix-web/pull/1813 +[#1513]: https://github.com/actix/actix-web/pull/1513 -## 3.0.0 - 2020-09-11 -* No significant changes from `3.0.0-beta.2`. +## 0.3.0 - 2020-09-11 +* No significant changes from `0.3.0-beta.2`. -## 3.0.0-beta.2 - 2020-09-10 +## 0.3.0-beta.2 - 2020-09-10 * Update `actix-*` dependencies to latest versions. diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index ed572a700..d22cf7ef0 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.3.0" +version = "0.4.0-beta.1" authors = ["Nikolay Kim "] description = "Multipart support for actix web framework." readme = "README.md" @@ -16,11 +16,11 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "3.0.0", default-features = false } +actix-web = { version = "4.0.0-beta.1", default-features = false } actix-utils = "3.0.0-beta.1" bytes = "1" -derive_more = "0.99.2" +derive_more = "0.99.5" httparse = "1.3" futures-util = { version = "0.3.7", default-features = false } log = "0.4" @@ -29,4 +29,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2.0.0-beta.1" -actix-http = "2.0.0" +actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index dab35953a..f75c6805f 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.1 - 2021-01-07 * Update `pin-project` to `1.0`. * Update `bytes` to `1.0`. [#1813] * `WebsocketContext::text` now takes an `Into`. [#1864] @@ -8,6 +11,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 [#1864]: https://github.com/actix/actix-web/pull/1864 + ## 3.0.0 - 2020-09-11 * No significant changes from `3.0.0-beta.2`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index dac4060ba..331363543 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "3.0.0" +version = "4.0.0-beta.1" authors = ["Nikolay Kim "] description = "Actix actors support for actix web framework." readme = "README.md" @@ -18,8 +18,8 @@ path = "src/lib.rs" [dependencies] actix = "0.11.0-beta.1" actix-codec = "0.4.0-beta.1" -actix-http = "2.0.0" -actix-web = { version = "3.0.0", default-features = false } +actix-http = "3.0.0-beta.1" +actix-web = { version = "4.0.0-beta.1", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 3fc4ae1be..25e88d9e1 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2.0.0-beta.1" -actix-web = "3.0.0" +actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 45a38259c..89b6121f3 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.1 - 2021-01-07 ### Changed * Update `rand` to `0.8` * Update `bytes` to `1.0`. [#1813] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b80f1ba6b..b92df8247 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "2.0.3" +version = "3.0.0-beta.1" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" @@ -39,13 +39,13 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.2" -actix-http = "2.2.0" +actix-http = "3.0.0-beta.1" actix-rt = "2.0.0-beta.1" base64 = "0.13" bytes = "1" cfg-if = "1.0" -derive_more = "0.99.2" +derive_more = "0.99.5" futures-core = { version = "0.3.7", default-features = false } log =" 0.4" mime = "0.3" @@ -61,9 +61,9 @@ rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = # TODO: actix is temporary added as dev dep for actix-macro reason. # Can be removed when it does not impact tests. actix = "0.11.0-beta.1" -actix-web = { version = "3.0.0", features = ["openssl"] } -actix-http = { version = "2.0.0", features = ["openssl"] } -actix-http-test = { version = "2.0.0", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.1", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.2" actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } From a4c9aaf337459d77edbf58df36ad4ad75b1d1746 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 8 Jan 2021 04:42:09 +0800 Subject: [PATCH 107/187] fix extra branch in h1 dispatcher timer (#1882) --- actix-http/src/h1/dispatcher.rs | 25 ++++++++++--------------- 1 file changed, 10 insertions(+), 15 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index a914880ce..8ef96fbef 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -687,15 +687,11 @@ where if let Some(deadline) = this.codec.config().client_disconnect_timer() { - if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() + if let Some(mut timer) = + this.ka_timer.as_mut().as_pin_mut() { - timer.reset(deadline); - let _ = this - .ka_timer - .as_mut() - .as_pin_mut() - .unwrap() - .poll(cx); + timer.as_mut().reset(deadline); + let _ = timer.poll(cx); } } else { // no shutdown timeout, drop socket @@ -720,15 +716,14 @@ where } else if let Some(deadline) = this.codec.config().keep_alive_expire() { - if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() { - timer.reset(deadline); - let _ = - this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); + if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() { + timer.as_mut().reset(deadline); + let _ = timer.poll(cx); } } - } else if let Some(timer) = this.ka_timer.as_mut().as_pin_mut() { - timer.reset(*this.ka_expire); - let _ = this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx); + } else if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() { + timer.as_mut().reset(*this.ka_expire); + let _ = timer.poll(cx); } } Poll::Pending => {} From 188ee44f819670944c0af92730f863e5683e7045 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 8 Jan 2021 05:55:00 +0800 Subject: [PATCH 108/187] remove copyless dependency (#1884) --- actix-http/Cargo.toml | 1 - actix-http/src/message.rs | 7 +++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 1c8206ef2..b64c71a8a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -53,7 +53,6 @@ bitflags = "1.2" bytes = "1" bytestring = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } -copyless = "0.1.4" derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 3673017bc..1a5500c31 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -3,7 +3,6 @@ use std::net; use std::rc::Rc; use bitflags::bitflags; -use copyless::BoxHelper; use crate::extensions::Extensions; use crate::header::HeaderMap; @@ -480,17 +479,17 @@ impl BoxedResponsePool { BoxedResponseHead { head: Some(head) } } else { BoxedResponseHead { - head: Some(Box::alloc().init(ResponseHead::new(status))), + head: Some(Box::new(ResponseHead::new(status))), } } } #[inline] /// Release request instance - fn release(&self, msg: Box) { + fn release(&self, mut msg: Box) { let v = &mut self.0.borrow_mut(); if v.len() < 128 { - msg.extensions.borrow_mut().clear(); + msg.extensions.get_mut().clear(); v.push(msg); } } From 2204614134a95e1a904091fcde614d44829c1499 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 8 Jan 2021 12:00:58 +0000 Subject: [PATCH 109/187] don't run awc doctests that rely on external public endpoints (#1888) --- awc/src/lib.rs | 10 +++++----- awc/src/ws.rs | 2 +- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/awc/src/lib.rs b/awc/src/lib.rs index d9db7a2cf..aad6ec38b 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -2,7 +2,7 @@ //! //! ## Making a GET request //! -//! ```rust +//! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let mut client = awc::Client::default(); @@ -20,7 +20,7 @@ //! //! ### Raw body contents //! -//! ```rust +//! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let mut client = awc::Client::default(); @@ -33,7 +33,7 @@ //! //! ### Forms //! -//! ```rust +//! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let params = [("foo", "bar"), ("baz", "quux")]; @@ -48,7 +48,7 @@ //! //! ### JSON //! -//! ```rust +//! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let request = serde_json::json!({ @@ -66,7 +66,7 @@ //! //! ## WebSocket support //! -//! ``` +//! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { //! use futures_util::{sink::SinkExt, stream::StreamExt}; diff --git a/awc/src/ws.rs b/awc/src/ws.rs index b90d0942b..fda2aefca 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -4,7 +4,7 @@ //! //! # Example //! -//! ``` +//! ```no_run //! use awc::{Client, ws}; //! use futures_util::{sink::SinkExt, stream::StreamExt}; //! From d40ae8c8cadb7230c68f21bec571d1ec28a411c9 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 9 Jan 2021 06:17:19 +0800 Subject: [PATCH 110/187] use sync method on Responder trait (#1891) --- CHANGES.md | 6 +- Cargo.toml | 4 + actix-files/src/named.rs | 24 ++-- actix-files/src/service.rs | 14 +- benches/responder.rs | 113 ++++++++++++++++ src/handler.rs | 8 +- src/responder.rs | 260 +++++++++---------------------------- src/types/either.rs | 47 +------ src/types/form.rs | 23 ++-- src/types/json.rs | 22 ++-- 10 files changed, 221 insertions(+), 300 deletions(-) create mode 100644 benches/responder.rs diff --git a/CHANGES.md b/CHANGES.md index 25fd10952..4eb2b6e1b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,7 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx - +### Changed +* Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. + Making it more simple and performant. [#1891] + +[#1891]: https://github.com/actix/actix-web/pull/1891 ## 4.0.0-beta.1 - 2021-01-07 ### Added diff --git a/Cargo.toml b/Cargo.toml index 87183c327..24a8573f5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -139,3 +139,7 @@ harness = false [[bench]] name = "service" harness = false + +[[bench]] +name = "responder" +harness = false \ No newline at end of file diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index a9b95bad1..b3c247b1f 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -16,10 +16,9 @@ use actix_web::{ }, ContentEncoding, StatusCode, }, - Error, HttpMessage, HttpRequest, HttpResponse, Responder, + HttpMessage, HttpRequest, HttpResponse, Responder, }; use bitflags::bitflags; -use futures_util::future::{ready, Ready}; use mime_guess::from_path; use crate::ChunkedReadFile; @@ -277,7 +276,7 @@ impl NamedFile { } /// Creates an `HttpResponse` with file as a streaming body. - pub fn into_response(self, req: &HttpRequest) -> Result { + pub fn into_response(self, req: &HttpRequest) -> HttpResponse { if self.status_code != StatusCode::OK { let mut res = HttpResponse::build(self.status_code); @@ -307,7 +306,7 @@ impl NamedFile { counter: 0, }; - return Ok(res.streaming(reader)); + return res.streaming(reader); } let etag = if self.flags.contains(Flags::ETAG) { @@ -411,17 +410,17 @@ impl NamedFile { ); } else { resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); - return Ok(resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish()); + return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { - return Ok(resp.status(StatusCode::BAD_REQUEST).finish()); + return resp.status(StatusCode::BAD_REQUEST).finish(); }; }; if precondition_failed { - return Ok(resp.status(StatusCode::PRECONDITION_FAILED).finish()); + return resp.status(StatusCode::PRECONDITION_FAILED).finish(); } else if not_modified { - return Ok(resp.status(StatusCode::NOT_MODIFIED).finish()); + return resp.status(StatusCode::NOT_MODIFIED).finish(); } let reader = ChunkedReadFile { @@ -436,7 +435,7 @@ impl NamedFile { resp.status(StatusCode::PARTIAL_CONTENT); } - Ok(resp.body(SizedStream::new(length, reader))) + resp.body(SizedStream::new(length, reader)) } } @@ -495,10 +494,7 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool { } impl Responder for NamedFile { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - ready(self.into_response(req)) + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + self.into_response(req) } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 1e3d64a0d..05431db38 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -120,10 +120,8 @@ impl Service for FilesService { named_file.flags = self.file_flags; let (req, _) = req.into_parts(); - Either::Left(ok(match named_file.into_response(&req) { - Ok(item) => ServiceResponse::new(req, item), - Err(e) => ServiceResponse::from_err(e, req), - })) + let res = named_file.into_response(&req); + Either::Left(ok(ServiceResponse::new(req, res))) } Err(e) => self.handle_err(e, req), } @@ -154,12 +152,8 @@ impl Service for FilesService { named_file.flags = self.file_flags; let (req, _) = req.into_parts(); - match named_file.into_response(&req) { - Ok(item) => { - Either::Left(ok(ServiceResponse::new(req.clone(), item))) - } - Err(e) => Either::Left(ok(ServiceResponse::from_err(e, req))), - } + let res = named_file.into_response(&req); + Either::Left(ok(ServiceResponse::new(req, res))) } Err(e) => self.handle_err(e, req), } diff --git a/benches/responder.rs b/benches/responder.rs new file mode 100644 index 000000000..61180d575 --- /dev/null +++ b/benches/responder.rs @@ -0,0 +1,113 @@ +use std::future::Future; +use std::time::Instant; + +use actix_http::Response; +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}; + +// responder simulate the old responder trait. +trait FutureResponder { + type Error; + type Future: Future>; + + fn future_respond_to(self, req: &HttpRequest) -> Self::Future; +} + +// a simple option responder type. +struct OptionResponder(Option); + +// a simple wrapper type around string +struct StringResponder(String); + +impl FutureResponder for StringResponder { + type Error = Error; + type Future = Ready>; + + fn future_respond_to(self, _: &HttpRequest) -> Self::Future { + // this is default builder for string response in both new and old responder trait. + ready(Ok(Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self.0))) + } +} + +impl FutureResponder for OptionResponder +where + T: FutureResponder, + T::Future: Future>, +{ + type Error = Error; + type Future = Either>>; + + fn future_respond_to(self, req: &HttpRequest) -> Self::Future { + match self.0 { + Some(t) => Either::Left(t.future_respond_to(req)), + None => Either::Right(ready(Err(error::ErrorInternalServerError("err")))), + } + } +} + +impl Responder for StringResponder { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + Response::build(StatusCode::OK) + .content_type("text/plain; charset=utf-8") + .body(self.0) + } +} + +impl Responder for OptionResponder { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { + match self.0 { + Some(t) => t.respond_to(req), + None => Response::from_error(error::ErrorInternalServerError("err")), + } + } +} + +fn future_responder(c: &mut Criterion) { + let rt = actix_rt::System::new("test"); + let req = TestRequest::default().to_http_request(); + + c.bench_function("future_responder", move |b| { + b.iter_custom(|_| { + let futs = (0..100_000).map(|_| async { + StringResponder(String::from("Hello World!!")) + .future_respond_to(&req) + .await + }); + + let futs = futures_util::future::join_all(futs); + + let start = Instant::now(); + + let _res = rt.block_on(async { futs.await }); + + start.elapsed() + }) + }); +} + +fn responder(c: &mut Criterion) { + let rt = actix_rt::System::new("test"); + let req = TestRequest::default().to_http_request(); + c.bench_function("responder", move |b| { + b.iter_custom(|_| { + let responders = + (0..100_000).map(|_| StringResponder(String::from("Hello World!!"))); + + let start = Instant::now(); + let _res = rt.block_on(async { + // don't need runtime block on but to be fair. + responders.map(|r| r.respond_to(&req)).collect::>() + }); + + start.elapsed() + }) + }); +} + +criterion_group!(responder_bench, future_responder, responder); +criterion_main!(responder_bench); diff --git a/src/handler.rs b/src/handler.rs index 30cc59842..47656cd84 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -135,7 +135,6 @@ where { Extract(#[pin] T::Future, Option, F), Handle(#[pin] R, Option), - Respond(#[pin] ::Future, Option), } impl Future for HandlerServiceFuture @@ -168,13 +167,8 @@ where } HandlerProj::Handle(fut, req) => { let res = ready!(fut.poll(cx)); - let fut = res.respond_to(req.as_ref().unwrap()); - let state = HandlerServiceFuture::Respond(fut, req.take()); - self.as_mut().set(state); - } - HandlerProj::Respond(fut, req) => { - let res = ready!(fut.poll(cx)).unwrap_or_else(|e| e.into().into()); let req = req.take().unwrap(); + let res = res.respond_to(&req); return Poll::Ready(Ok(ServiceResponse::new(req, res))); } } diff --git a/src/responder.rs b/src/responder.rs index 58e33f39d..b61dd1016 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,8 +1,4 @@ use std::convert::TryFrom; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; use actix_http::error::InternalError; use actix_http::http::{ @@ -10,9 +6,6 @@ use actix_http::http::{ }; use actix_http::{Error, Response, ResponseBuilder}; use bytes::{Bytes, BytesMut}; -use futures_util::future::{err, ok, Either as EitherFuture, Ready}; -use futures_util::ready; -use pin_project::pin_project; use crate::request::HttpRequest; @@ -20,14 +13,8 @@ use crate::request::HttpRequest; /// /// Types that implement this trait can be used as the return type of a handler. pub trait Responder { - /// The associated error which can be returned. - type Error: Into; - - /// The future response value. - type Future: Future>; - - /// Convert itself to `AsyncResult` or `Error`. - fn respond_to(self, req: &HttpRequest) -> Self::Future; + /// Convert self to `Response`. + fn respond_to(self, req: &HttpRequest) -> Response; /// Override a status code for a Responder. /// @@ -77,28 +64,17 @@ pub trait Responder { } impl Responder for Response { - type Error = Error; - type Future = Ready>; - #[inline] - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(self) + fn respond_to(self, _: &HttpRequest) -> Response { + self } } -impl Responder for Option -where - T: Responder, -{ - type Error = T::Error; - type Future = EitherFuture>>; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { +impl Responder for Option { + fn respond_to(self, req: &HttpRequest) -> Response { match self { - Some(t) => EitherFuture::Left(t.respond_to(req)), - None => { - EitherFuture::Right(ok(Response::build(StatusCode::NOT_FOUND).finish())) - } + Some(t) => t.respond_to(req), + None => Response::build(StatusCode::NOT_FOUND).finish(), } } } @@ -108,109 +84,74 @@ where T: Responder, E: Into, { - type Error = Error; - type Future = EitherFuture< - ResponseFuture, - Ready>, - >; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { + fn respond_to(self, req: &HttpRequest) -> Response { match self { - Ok(val) => EitherFuture::Left(ResponseFuture::new(val.respond_to(req))), - Err(e) => EitherFuture::Right(err(e.into())), + Ok(val) => val.respond_to(req), + Err(e) => Response::from_error(e.into()), } } } impl Responder for ResponseBuilder { - type Error = Error; - type Future = Ready>; - #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Self::Future { - ok(self.finish()) + fn respond_to(mut self, _: &HttpRequest) -> Response { + self.finish() } } -impl Responder for (T, StatusCode) -where - T: Responder, -{ - type Error = T::Error; - type Future = CustomResponderFut; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { - CustomResponderFut { - fut: self.0.respond_to(req), - status: Some(self.1), - headers: None, - } +impl Responder for (T, StatusCode) { + fn respond_to(self, req: &HttpRequest) -> Response { + let mut res = self.0.respond_to(req); + *res.status_mut() = self.1; + res } } impl Responder for &'static str { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") - .body(self)) + .body(self) } } impl Responder for &'static [u8] { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("application/octet-stream") - .body(self)) + .body(self) } } impl Responder for String { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") - .body(self)) + .body(self) } } impl<'a> Responder for &'a String { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("text/plain; charset=utf-8") - .body(self)) + .body(self) } } impl Responder for Bytes { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("application/octet-stream") - .body(self)) + .body(self) } } impl Responder for BytesMut { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - ok(Response::build(StatusCode::OK) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::build(StatusCode::OK) .content_type("application/octet-stream") - .body(self)) + .body(self) } } @@ -290,45 +231,20 @@ impl CustomResponder { } impl Responder for CustomResponder { - type Error = T::Error; - type Future = CustomResponderFut; + fn respond_to(self, req: &HttpRequest) -> Response { + let mut res = self.responder.respond_to(req); - fn respond_to(self, req: &HttpRequest) -> Self::Future { - CustomResponderFut { - fut: self.responder.respond_to(req), - status: self.status, - headers: self.headers, - } - } -} - -#[pin_project] -pub struct CustomResponderFut { - #[pin] - fut: T::Future, - status: Option, - headers: Option, -} - -impl Future for CustomResponderFut { - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - let mut res = match ready!(this.fut.poll(cx)) { - Ok(res) => res, - Err(e) => return Poll::Ready(Err(e)), - }; - if let Some(status) = this.status.take() { + if let Some(status) = self.status { *res.status_mut() = status; } - if let Some(ref headers) = this.headers { + + if let Some(ref headers) = self.headers { for (k, v) in headers { res.headers_mut().insert(k.clone(), v.clone()); } } - Poll::Ready(Ok(res)) + + res } } @@ -336,40 +252,8 @@ impl Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let err: Error = self.into(); - ok(err.into()) - } -} - -#[pin_project] -pub struct ResponseFuture { - #[pin] - fut: T, - _phantom: PhantomData, -} - -impl ResponseFuture { - pub fn new(fut: T) -> Self { - ResponseFuture { - fut, - _phantom: PhantomData, - } - } -} - -impl Future for ResponseFuture -where - T: Future>, - E: Into, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready(ready!(self.project().fut.poll(cx)).map_err(|e| e.into())) + fn respond_to(self, _: &HttpRequest) -> Response { + Response::from_error(self.into()) } } @@ -382,7 +266,7 @@ pub(crate) mod tests { use crate::dev::{Body, ResponseBody}; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; use crate::test::{init_service, TestRequest}; - use crate::{error, web, App, HttpResponse}; + use crate::{error, web, App}; #[actix_rt::test] async fn test_option_responder() { @@ -441,7 +325,7 @@ pub(crate) mod tests { async fn test_responder() { let req = TestRequest::default().to_http_request(); - let resp: HttpResponse = "test".respond_to(&req).await.unwrap(); + let resp = "test".respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -449,7 +333,7 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let resp: HttpResponse = b"test".respond_to(&req).await.unwrap(); + let resp = b"test".respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -457,7 +341,7 @@ pub(crate) mod tests { HeaderValue::from_static("application/octet-stream") ); - let resp: HttpResponse = "test".to_string().respond_to(&req).await.unwrap(); + let resp = "test".to_string().respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -465,7 +349,7 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let resp: HttpResponse = (&"test".to_string()).respond_to(&req).await.unwrap(); + let resp = (&"test".to_string()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -473,8 +357,7 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let resp: HttpResponse = - Bytes::from_static(b"test").respond_to(&req).await.unwrap(); + let resp = Bytes::from_static(b"test").respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -482,10 +365,7 @@ pub(crate) mod tests { HeaderValue::from_static("application/octet-stream") ); - let resp: HttpResponse = BytesMut::from(b"test".as_ref()) - .respond_to(&req) - .await - .unwrap(); + let resp = BytesMut::from(b"test".as_ref()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -494,11 +374,8 @@ pub(crate) mod tests { ); // InternalError - let resp: HttpResponse = - error::InternalError::new("err", StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); + let resp = + error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -507,10 +384,7 @@ pub(crate) mod tests { let req = TestRequest::default().to_http_request(); // Result - let resp: HttpResponse = Ok::<_, Error>("test".to_string()) - .respond_to(&req) - .await - .unwrap(); + let resp = Ok::<_, Error>("test".to_string()).respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!(resp.body().bin_ref(), b"test"); assert_eq!( @@ -520,9 +394,9 @@ pub(crate) mod tests { let res = Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req) - .await; - assert!(res.is_err()); + .respond_to(&req); + + assert_eq!(res.status(), StatusCode::BAD_REQUEST); } #[actix_rt::test] @@ -531,18 +405,15 @@ pub(crate) mod tests { let res = "test" .to_string() .with_status(StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); + .respond_to(&req); + assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.body().bin_ref(), b"test"); let res = "test" .to_string() .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); + .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.body().bin_ref(), b"test"); @@ -555,19 +426,14 @@ pub(crate) mod tests { #[actix_rt::test] async fn test_tuple_responder_with_status_code() { let req = TestRequest::default().to_http_request(); - let res = ("test".to_string(), StatusCode::BAD_REQUEST) - .respond_to(&req) - .await - .unwrap(); + let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(res.status(), StatusCode::BAD_REQUEST); assert_eq!(res.body().bin_ref(), b"test"); let req = TestRequest::default().to_http_request(); let res = ("test".to_string(), StatusCode::OK) .with_header("content-type", "json") - .respond_to(&req) - .await - .unwrap(); + .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.body().bin_ref(), b"test"); assert_eq!( diff --git a/src/types/either.rs b/src/types/either.rs index 8a046d291..ca3ab3556 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,13 +1,6 @@ -use std::{ - future::Future, - pin::Pin, - task::{Context, Poll}, -}; - use actix_http::{Error, Response}; use bytes::Bytes; -use futures_util::{future::LocalBoxFuture, ready, FutureExt, TryFutureExt}; -use pin_project::pin_project; +use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt}; use crate::{dev, request::HttpRequest, FromRequest, Responder}; @@ -68,42 +61,10 @@ where A: Responder, B: Responder, { - type Error = Error; - type Future = EitherResponder; - - fn respond_to(self, req: &HttpRequest) -> Self::Future { + fn respond_to(self, req: &HttpRequest) -> Response { match self { - Either::A(a) => EitherResponder::A(a.respond_to(req)), - Either::B(b) => EitherResponder::B(b.respond_to(req)), - } - } -} - -#[pin_project(project = EitherResponderProj)] -pub enum EitherResponder -where - A: Responder, - B: Responder, -{ - A(#[pin] A::Future), - B(#[pin] B::Future), -} - -impl Future for EitherResponder -where - A: Responder, - B: Responder, -{ - type Output = Result; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - match self.project() { - EitherResponderProj::A(fut) => { - Poll::Ready(ready!(fut.poll(cx)).map_err(|e| e.into())) - } - EitherResponderProj::B(fut) => { - Poll::Ready(ready!(fut.poll(cx).map_err(|e| e.into()))) - } + Either::A(a) => a.respond_to(req), + Either::B(b) => b.respond_to(req), } } } diff --git a/src/types/form.rs b/src/types/form.rs index 82ea73216..cdd280491 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -9,7 +9,7 @@ use std::{fmt, ops}; use actix_http::{Error, HttpMessage, Payload, Response}; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_util::future::{err, ok, FutureExt, LocalBoxFuture, Ready}; +use futures_util::future::{FutureExt, LocalBoxFuture}; use futures_util::StreamExt; use serde::de::DeserializeOwned; use serde::Serialize; @@ -158,18 +158,13 @@ impl fmt::Display for Form { } impl Responder for Form { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_urlencoded::to_string(&self.0) { - Ok(body) => body, - Err(e) => return err(e.into()), - }; - - ok(Response::build(StatusCode::OK) - .set(ContentType::form_url_encoded()) - .body(body)) + fn respond_to(self, _: &HttpRequest) -> Response { + match serde_urlencoded::to_string(&self.0) { + Ok(body) => Response::build(StatusCode::OK) + .set(ContentType::form_url_encoded()) + .body(body), + Err(e) => Response::from_error(e.into()), + } } } @@ -493,7 +488,7 @@ mod tests { hello: "world".to_string(), counter: 123, }); - let resp = form.respond_to(&req).await.unwrap(); + let resp = form.respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(CONTENT_TYPE).unwrap(), diff --git a/src/types/json.rs b/src/types/json.rs index 74138ca56..452cb58c1 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -8,7 +8,6 @@ use std::task::{Context, Poll}; use std::{fmt, ops}; use bytes::BytesMut; -use futures_util::future::{ready, Ready}; use futures_util::ready; use futures_util::stream::Stream; use serde::de::DeserializeOwned; @@ -123,18 +122,13 @@ where } impl Responder for Json { - type Error = Error; - type Future = Ready>; - - fn respond_to(self, _: &HttpRequest) -> Self::Future { - let body = match serde_json::to_string(&self.0) { - Ok(body) => body, - Err(e) => return ready(Err(e.into())), - }; - - ready(Ok(Response::build(StatusCode::OK) - .content_type("application/json") - .body(body))) + fn respond_to(self, _: &HttpRequest) -> Response { + match serde_json::to_string(&self.0) { + Ok(body) => Response::build(StatusCode::OK) + .content_type("application/json") + .body(body), + Err(e) => Response::from_error(e.into()), + } } } @@ -498,7 +492,7 @@ mod tests { let j = Json(MyObject { name: "test".to_string(), }); - let resp = j.respond_to(&req).await.unwrap(); + let resp = j.respond_to(&req); assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), From 530d03791d3ee9673e3e061d318fcfd9b5f45b20 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 9 Jan 2021 11:36:58 +0800 Subject: [PATCH 111/187] refactor Resource (#1883) --- src/resource.rs | 172 +++++++++++++++--------------------------------- 1 file changed, 53 insertions(+), 119 deletions(-) diff --git a/src/resource.rs b/src/resource.rs index 7d53ef936..843237079 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -1,18 +1,18 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; -use std::pin::Pin; use std::rc::Rc; -use std::task::{Context, Poll}; +use std::task::Poll; use actix_http::{Error, Extensions, Response}; use actix_router::IntoPattern; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, + apply, apply_fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; +use futures_util::future::join_all; use crate::data::Data; use crate::dev::{insert_slash, AppService, HttpServiceFactory, ResourceDef}; @@ -20,7 +20,7 @@ use crate::extract::FromRequest; use crate::guard::Guard; use crate::handler::Handler; use crate::responder::Responder; -use crate::route::{CreateRouteService, Route, RouteService}; +use crate::route::{Route, RouteService}; use crate::service::{ServiceRequest, ServiceResponse}; type HttpService = BoxService; @@ -53,9 +53,9 @@ pub struct Resource { rdef: Vec, name: Option, routes: Vec, - data: Option, + app_data: Option, guards: Vec>, - default: Rc>>>, + default: HttpNewService, factory_ref: Rc>>, } @@ -70,8 +70,10 @@ impl Resource { endpoint: ResourceEndpoint::new(fref.clone()), factory_ref: fref, guards: Vec::new(), - data: None, - default: Rc::new(RefCell::new(None)), + app_data: None, + default: boxed::factory(fn_service(|req: ServiceRequest| async { + Ok(req.into_response(Response::MethodNotAllowed().finish())) + })), } } } @@ -201,10 +203,10 @@ where /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { - if self.data.is_none() { - self.data = Some(Extensions::new()); + if self.app_data.is_none() { + self.app_data = Some(Extensions::new()); } - self.data.as_mut().unwrap().insert(data); + self.app_data.as_mut().unwrap().insert(data); self } @@ -274,7 +276,7 @@ where guards: self.guards, routes: self.routes, default: self.default, - data: self.data, + app_data: self.app_data, factory_ref: self.factory_ref, } } @@ -336,7 +338,7 @@ where guards: self.guards, routes: self.routes, default: self.default, - data: self.data, + app_data: self.app_data, factory_ref: self.factory_ref, } } @@ -356,11 +358,9 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( - f.into_factory().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - }), - ))))); + self.default = boxed::factory(f.into_factory().map_init_err(|e| { + log::error!("Can not construct default service: {:?}", e) + })); self } @@ -391,7 +391,7 @@ where *rdef.name_mut() = name.clone(); } // custom app data storage - if let Some(ref mut ext) = self.data { + if let Some(ref mut ext) = self.app_data { config.set_service_data(ext); } @@ -412,7 +412,7 @@ where fn into_factory(self) -> T { *self.factory_ref.borrow_mut() = Some(ResourceFactory { routes: self.routes, - data: self.data.map(Rc::new), + app_data: self.app_data.map(Rc::new), default: self.default, }); @@ -422,8 +422,8 @@ where pub struct ResourceFactory { routes: Vec, - data: Option>, - default: Rc>>>, + app_data: Option>, + default: HttpNewService, } impl ServiceFactory for ResourceFactory { @@ -432,126 +432,60 @@ impl ServiceFactory for ResourceFactory { type Config = (); type Service = ResourceService; type InitError = (); - type Future = CreateResourceService; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - let default_fut = if let Some(ref default) = *self.default.borrow() { - Some(default.new_service(())) - } else { - None - }; + // construct default service factory future. + let default_fut = self.default.new_service(()); - CreateResourceService { - fut: self - .routes - .iter() - .map(|route| CreateRouteServiceItem::Future(route.new_service(()))) - .collect(), - data: self.data.clone(), - default: None, - default_fut, - } - } -} + // construct route service factory futures + let factory_fut = + join_all(self.routes.iter().map(|route| route.new_service(()))); -enum CreateRouteServiceItem { - Future(CreateRouteService), - Service(RouteService), -} + let app_data = self.app_data.clone(); -pub struct CreateResourceService { - fut: Vec, - data: Option>, - default: Option, - default_fut: Option>>, -} + Box::pin(async move { + let default = default_fut.await?; + let routes = factory_fut + .await + .into_iter() + .collect::, _>>()?; -impl Future for CreateResourceService { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match Pin::new(fut).poll(cx)? { - Poll::Ready(default) => self.default = Some(default), - Poll::Pending => done = false, - } - } - - // poll http services - for item in &mut self.fut { - match item { - CreateRouteServiceItem::Future(ref mut fut) => match Pin::new(fut) - .poll(cx)? - { - Poll::Ready(route) => *item = CreateRouteServiceItem::Service(route), - Poll::Pending => { - done = false; - } - }, - CreateRouteServiceItem::Service(_) => continue, - }; - } - - if done { - let routes = self - .fut - .drain(..) - .map(|item| match item { - CreateRouteServiceItem::Service(service) => service, - CreateRouteServiceItem::Future(_) => unreachable!(), - }) - .collect(); - Poll::Ready(Ok(ResourceService { + Ok(ResourceService { + app_data, + default, routes, - data: self.data.clone(), - default: self.default.take(), - })) - } else { - Poll::Pending - } + }) + }) } } pub struct ResourceService { routes: Vec, - data: Option>, - default: Option, + app_data: Option>, + default: HttpService, } impl Service for ResourceService { type Response = ServiceResponse; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, mut req: ServiceRequest) -> Self::Future { for route in self.routes.iter_mut() { if route.check(&mut req) { - if let Some(ref data) = self.data { - req.add_data_container(data.clone()); + if let Some(ref app_data) = self.app_data { + req.add_data_container(app_data.clone()); } return route.call(req); } } - if let Some(ref mut default) = self.default { - if let Some(ref data) = self.data { - req.add_data_container(data.clone()); - } - default.call(req) - } else { - let req = req.into_parts().0; - Box::pin(async { - Ok(ServiceResponse::new( - req, - Response::MethodNotAllowed().finish(), - )) - }) + if let Some(ref app_data) = self.app_data { + req.add_data_container(app_data.clone()); } + self.default.call(req) } } @@ -567,15 +501,15 @@ impl ResourceEndpoint { } impl ServiceFactory for ResourceEndpoint { - type Config = (); type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = ResourceService; - type Future = CreateResourceService; + type InitError = (); + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - self.factory.borrow_mut().as_mut().unwrap().new_service(()) + self.factory.borrow().as_ref().unwrap().new_service(()) } } From 6575ee93f2a739f77986d13c2bbea675e314d83d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 9 Jan 2021 13:17:19 +0000 Subject: [PATCH 112/187] big clean up and docs improvmenet of types mod (#1894) --- CHANGES.md | 12 +- Cargo.toml | 3 +- actix-http/CHANGES.md | 3 + actix-http/src/error.rs | 53 +++++--- actix-http/src/response.rs | 19 ++- actix-http/src/ws/codec.rs | 2 +- awc/src/response.rs | 4 +- awc/src/ws.rs | 2 +- src/error.rs | 46 +++---- src/extract.rs | 33 ++--- src/lib.rs | 6 +- src/middleware/logger.rs | 7 +- src/request.rs | 7 +- src/responder.rs | 66 +++++----- src/types/either.rs | 195 +++++++++++++++++++--------- src/types/form.rs | 209 +++++++++++++++--------------- src/types/json.rs | 253 ++++++++++++++++--------------------- src/types/mod.rs | 3 +- src/types/path.rs | 126 +++++------------- src/types/payload.rs | 174 +++++++++---------------- src/types/query.rs | 156 ++++++++++------------- src/types/readlines.rs | 38 +++--- 22 files changed, 674 insertions(+), 743 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 4eb2b6e1b..733f28ca7 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,11 +1,21 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* The method `Either, web::Form>::into_inner()` which returns the inner type for + whichever variant was created. Also works for `Either, web::Json>`. [#1894] + ### Changed * Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. Making it more simple and performant. [#1891] - +* Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] + +### Removed +* Public field of `web::Path` has been made private. [#1894] +* Public field of `web::Query` has been made private. [#1894] + [#1891]: https://github.com/actix/actix-web/pull/1891 +[#1894]: https://github.com/actix/actix-web/pull/1894 ## 4.0.0-beta.1 - 2021-01-07 ### Added diff --git a/Cargo.toml b/Cargo.toml index 24a8573f5..fb0a876b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,6 +90,7 @@ awc = { version = "3.0.0-beta.1", default-features = false } ahash = "0.6" bytes = "1" derive_more = "0.99.5" +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 } @@ -142,4 +143,4 @@ harness = false [[bench]] name = "responder" -harness = false \ No newline at end of file +harness = false diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 6abd0ba76..622ed55ea 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894] + +[#1894]: https://github.com/actix/actix-web/pull/1894 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 03e5467c5..852cf8e5c 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -100,10 +100,6 @@ impl fmt::Debug for Error { } impl std::error::Error for Error { - fn cause(&self) -> Option<&dyn std::error::Error> { - None - } - fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { None } @@ -309,28 +305,45 @@ impl From for ParseError { pub enum PayloadError { /// A payload reached EOF, but is not complete. #[display( - fmt = "A payload reached EOF, but is not complete. With error: {:?}", + fmt = "A payload reached EOF, but is not complete. Inner error: {:?}", _0 )] Incomplete(Option), - /// Content encoding stream corruption + + /// Content encoding stream corruption. #[display(fmt = "Can not decode content-encoding.")] EncodingCorrupted, - /// A payload reached size limit. - #[display(fmt = "A payload reached size limit.")] + + /// Payload reached size limit. + #[display(fmt = "Payload reached size limit.")] Overflow, - /// A payload length is unknown. - #[display(fmt = "A payload length is unknown.")] + + /// Payload length is unknown. + #[display(fmt = "Payload length is unknown.")] UnknownLength, - /// Http2 payload error + + /// HTTP/2 payload error. #[display(fmt = "{}", _0)] Http2Payload(h2::Error), - /// Io error + + /// Generic I/O error. #[display(fmt = "{}", _0)] Io(io::Error), } -impl std::error::Error for PayloadError {} +impl std::error::Error for PayloadError { + fn source(&self) -> Option<&(dyn std::error::Error + 'static)> { + match self { + PayloadError::Incomplete(None) => None, + PayloadError::Incomplete(Some(err)) => Some(err as &dyn std::error::Error), + PayloadError::EncodingCorrupted => None, + PayloadError::Overflow => None, + PayloadError::UnknownLength => None, + PayloadError::Http2Payload(err) => Some(err as &dyn std::error::Error), + PayloadError::Io(err) => Some(err as &dyn std::error::Error), + } + } +} impl From for PayloadError { fn from(err: h2::Error) -> Self { @@ -1009,22 +1022,22 @@ mod tests { fn test_payload_error() { let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into(); - assert!(format!("{}", err).contains("ParseError")); + assert!(err.to_string().contains("ParseError")); let err = PayloadError::Incomplete(None); assert_eq!( - format!("{}", err), - "A payload reached EOF, but is not complete. With error: None" + err.to_string(), + "A payload reached EOF, but is not complete. Inner error: None" ); } macro_rules! from { ($from:expr => $error:pat) => { match ParseError::from($from) { - e @ $error => { - assert!(format!("{}", e).len() >= 5); + err @ $error => { + assert!(err.to_string().len() >= 5); } - e => unreachable!("{:?}", e), + err => unreachable!("{:?}", err), } }; } @@ -1067,7 +1080,7 @@ mod tests { let err = PayloadError::Overflow; let resp_err: &dyn ResponseError = &err; let err = resp_err.downcast_ref::().unwrap(); - assert_eq!(err.to_string(), "A payload reached size limit."); + assert_eq!(err.to_string(), "Payload reached size limit."); let not_err = resp_err.downcast_ref::(); assert!(not_err.is_none()); } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index df2f5be50..0a1f2cfd2 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -481,15 +481,14 @@ impl ResponseBuilder { self } - /// Set response content type + /// Set response content type. #[inline] pub fn content_type(&mut self, value: V) -> &mut Self where - HeaderValue: TryFrom, - >::Error: Into, + V: IntoHeaderValue, { if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderValue::try_from(value) { + match value.try_into() { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); } @@ -802,7 +801,7 @@ impl From for Response { impl From<&'static str> for Response { fn from(val: &'static str) -> Self { Response::Ok() - .content_type("text/plain; charset=utf-8") + .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } } @@ -810,7 +809,7 @@ impl From<&'static str> for Response { impl From<&'static [u8]> for Response { fn from(val: &'static [u8]) -> Self { Response::Ok() - .content_type("application/octet-stream") + .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } } @@ -818,7 +817,7 @@ impl From<&'static [u8]> for Response { impl From for Response { fn from(val: String) -> Self { Response::Ok() - .content_type("text/plain; charset=utf-8") + .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } } @@ -826,7 +825,7 @@ impl From for Response { impl<'a> From<&'a String> for Response { fn from(val: &'a String) -> Self { Response::Ok() - .content_type("text/plain; charset=utf-8") + .content_type(mime::TEXT_PLAIN_UTF_8) .body(val) } } @@ -834,7 +833,7 @@ impl<'a> From<&'a String> for Response { impl From for Response { fn from(val: Bytes) -> Self { Response::Ok() - .content_type("application/octet-stream") + .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } } @@ -842,7 +841,7 @@ impl From for Response { impl From for Response { fn from(val: BytesMut) -> Self { Response::Ok() - .content_type("application/octet-stream") + .content_type(mime::APPLICATION_OCTET_STREAM) .body(val) } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 84f5b3c73..d01e8dab9 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -89,7 +89,7 @@ impl Codec { /// Set max frame size. /// - /// By default max size is set to 64kb. + /// By default max size is set to 64kB. pub fn max_size(mut self, size: usize) -> Self { self.max_size = size; self diff --git a/awc/src/response.rs b/awc/src/response.rs index a32412b23..c3e7d71ce 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -184,7 +184,7 @@ where } } - /// Change max size of payload. By default max size is 256Kb + /// Change max size of payload. By default max size is 256kB pub fn limit(mut self, limit: usize) -> Self { if let Some(ref mut fut) = self.fut { fut.limit = limit; @@ -276,7 +276,7 @@ where } } - /// Change max size of payload. By default max size is 64Kb + /// Change max size of payload. By default max size is 64kB pub fn limit(mut self, limit: usize) -> Self { if let Some(ref mut fut) = self.fut { fut.limit = limit; diff --git a/awc/src/ws.rs b/awc/src/ws.rs index fda2aefca..f747f701f 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -147,7 +147,7 @@ impl WebsocketsRequest { /// Set max frame size /// - /// By default max size is set to 64kb + /// By default max size is set to 64kB pub fn max_frame_size(mut self, size: usize) -> Self { self.max_size = size; self diff --git a/src/error.rs b/src/error.rs index 60af8fa11..c0d6f8af9 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,12 +1,11 @@ //! Error and Result module pub use actix_http::error::*; -use derive_more::{Display, From}; +use derive_more::{Display, Error, From}; use serde_json::error::Error as JsonError; use url::ParseError as UrlParseError; -use crate::http::StatusCode; -use crate::HttpResponse; +use crate::{http::StatusCode, HttpResponse}; /// Errors which can occur when attempting to generate resource uri. #[derive(Debug, PartialEq, Display, From)] @@ -28,34 +27,37 @@ impl std::error::Error for UrlGenerationError {} impl ResponseError for UrlGenerationError {} /// A set of errors that can occur during parsing urlencoded payloads -#[derive(Debug, Display, From)] +#[derive(Debug, Display, Error, From)] pub enum UrlencodedError { - /// Can not decode chunked transfer encoding - #[display(fmt = "Can not decode chunked transfer encoding")] + /// Can not decode chunked transfer encoding. + #[display(fmt = "Can not decode chunked transfer encoding.")] Chunked, - /// Payload size is bigger than allowed. (default: 256kB) + + /// Payload size is larger than allowed. (default limit: 256kB). #[display( - fmt = "Urlencoded payload size is bigger ({} bytes) than allowed (default: {} bytes)", + fmt = "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).", size, limit )] Overflow { size: usize, limit: usize }, - /// Payload size is now known - #[display(fmt = "Payload size is now known")] + + /// Payload size is now known. + #[display(fmt = "Payload size is now known.")] UnknownLength, - /// Content type error - #[display(fmt = "Content type error")] + + /// Content type error. + #[display(fmt = "Content type error.")] ContentType, - /// Parse error - #[display(fmt = "Parse error")] + + /// Parse error. + #[display(fmt = "Parse error.")] Parse, - /// Payload error - #[display(fmt = "Error that occur during reading payload: {}", _0)] + + /// Payload error. + #[display(fmt = "Error that occur during reading payload: {}.", _0)] Payload(PayloadError), } -impl std::error::Error for UrlencodedError {} - /// Return `BadRequest` for `UrlencodedError` impl ResponseError for UrlencodedError { fn status_code(&self) -> StatusCode { @@ -115,16 +117,14 @@ impl ResponseError for PathError { } } -/// A set of errors that can occur during parsing query strings -#[derive(Debug, Display, From)] +/// A set of errors that can occur during parsing query strings. +#[derive(Debug, Display, Error, From)] pub enum QueryPayloadError { - /// Deserialize error + /// Query deserialize error. #[display(fmt = "Query deserialize error: {}", _0)] Deserialize(serde::de::value::Error), } -impl std::error::Error for QueryPayloadError {} - /// Return `BadRequest` for `QueryPayloadError` impl ResponseError for QueryPayloadError { fn status_code(&self) -> StatusCode { diff --git a/src/extract.rs b/src/extract.rs index 5916b1bc5..4081188ef 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -1,34 +1,37 @@ //! Request extractors -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use actix_http::error::Error; -use futures_util::future::{ready, Ready}; -use futures_util::ready; +use std::{ + future::Future, + pin::Pin, + task::{Context, Poll}, +}; -use crate::dev::Payload; -use crate::request::HttpRequest; +use futures_util::{ + future::{ready, Ready}, + ready, +}; + +use crate::{dev::Payload, Error, HttpRequest}; /// Trait implemented by types that can be extracted from request. /// /// Types that implement this trait can be used with `Route` handlers. pub trait FromRequest: Sized { + /// Configuration for this extractor. + type Config: Default + 'static; + /// The associated error which can be returned. type Error: Into; - /// Future that resolves to a Self + /// Future that resolves to a Self. type Future: Future>; - /// Configuration for this extractor - type Config: Default + 'static; - - /// Convert request to a Self + /// Create a Self from request parts asynchronously. fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future; - /// Convert request to a Self + /// Create a Self from request head asynchronously. /// - /// This method uses `Payload::None` as payload stream. + /// This method is short for `T::from_request(req, &mut Payload::None)`. fn extract(req: &HttpRequest) -> Self::Future { Self::from_request(req, &mut Payload::None) } diff --git a/src/lib.rs b/src/lib.rs index 88eae44bf..fa4e70aec 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,7 +6,8 @@ //! use actix_web::{get, web, App, HttpServer, Responder}; //! //! #[get("/{id}/{name}/index.html")] -//! async fn index(web::Path((id, name)): web::Path<(u32, String)>) -> impl Responder { +//! async fn index(path: web::Path<(u32, String)>) -> impl Responder { +//! let (id, name) = path.into_inner(); //! format!("Hello {}! id:{}", name, id) //! } //! @@ -90,7 +91,7 @@ mod scope; mod server; mod service; pub mod test; -mod types; +pub(crate) mod types; pub mod web; pub use actix_http::Response as HttpResponse; @@ -106,6 +107,7 @@ pub use crate::responder::Responder; pub use crate::route::Route; pub use crate::scope::Scope; pub use crate::server::HttpServer; +// TODO: is exposing the error directly really needed pub use crate::types::{Either, EitherExtractError}; pub mod dev { diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index cdbd5e485..276265a58 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -15,7 +15,7 @@ use std::{ use actix_service::{Service, Transform}; use bytes::Bytes; use futures_util::future::{ok, Ready}; -use log::debug; +use log::{debug, warn}; use regex::{Regex, RegexSet}; use time::OffsetDateTime; @@ -188,9 +188,8 @@ where for unit in &self.0.format.0 { // missing request replacement function diagnostic if let FormatText::CustomRequest(label, None) = unit { - debug!( - "No custom request replacement function was registered for label {} in\ - logger format.", + warn!( + "No custom request replacement function was registered for label \"{}\".", label ); } diff --git a/src/request.rs b/src/request.rs index 82304c1af..f8160ae47 100644 --- a/src/request.rs +++ b/src/request.rs @@ -17,9 +17,10 @@ use crate::rmap::ResourceMap; #[derive(Clone)] /// An HTTP Request pub struct HttpRequest { - // *. Rc is used exclusively and NO Weak - // is allowed anywhere in the code. Weak pointer is purposely ignored when - // doing Rc's ref counter check. + /// # Panics + /// `Rc` is used exclusively and NO `Weak` + /// is allowed anywhere in the code. Weak pointer is purposely ignored when + /// doing `Rc`'s ref counter check. Expect panics if this invariant is violated. pub(crate) inner: Rc, } diff --git a/src/responder.rs b/src/responder.rs index b61dd1016..9b33ac81a 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -4,17 +4,17 @@ use actix_http::error::InternalError; use actix_http::http::{ header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode, }; -use actix_http::{Error, Response, ResponseBuilder}; +use actix_http::ResponseBuilder; use bytes::{Bytes, BytesMut}; -use crate::request::HttpRequest; +use crate::{Error, HttpRequest, HttpResponse}; /// Trait implemented by types that can be converted to a http response. /// /// Types that implement this trait can be used as the return type of a handler. pub trait Responder { - /// Convert self to `Response`. - fn respond_to(self, req: &HttpRequest) -> Response; + /// Convert self to `HttpResponse`. + fn respond_to(self, req: &HttpRequest) -> HttpResponse; /// Override a status code for a Responder. /// @@ -63,18 +63,18 @@ pub trait Responder { } } -impl Responder for Response { +impl Responder for HttpResponse { #[inline] - fn respond_to(self, _: &HttpRequest) -> Response { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { self } } impl Responder for Option { - fn respond_to(self, req: &HttpRequest) -> Response { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { Some(t) => t.respond_to(req), - None => Response::build(StatusCode::NOT_FOUND).finish(), + None => HttpResponse::build(StatusCode::NOT_FOUND).finish(), } } } @@ -84,23 +84,23 @@ where T: Responder, E: Into, { - fn respond_to(self, req: &HttpRequest) -> Response { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { Ok(val) => val.respond_to(req), - Err(e) => Response::from_error(e.into()), + Err(e) => HttpResponse::from_error(e.into()), } } } impl Responder for ResponseBuilder { #[inline] - fn respond_to(mut self, _: &HttpRequest) -> Response { + fn respond_to(mut self, _: &HttpRequest) -> HttpResponse { self.finish() } } impl Responder for (T, StatusCode) { - fn respond_to(self, req: &HttpRequest) -> Response { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.0.respond_to(req); *res.status_mut() = self.1; res @@ -108,49 +108,49 @@ impl Responder for (T, StatusCode) { } impl Responder for &'static str { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(mime::TEXT_PLAIN_UTF_8) .body(self) } } impl Responder for &'static [u8] { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::build(StatusCode::OK) - .content_type("application/octet-stream") + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(mime::APPLICATION_OCTET_STREAM) .body(self) } } impl Responder for String { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(mime::TEXT_PLAIN_UTF_8) .body(self) } } impl<'a> Responder for &'a String { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::build(StatusCode::OK) - .content_type("text/plain; charset=utf-8") + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(mime::TEXT_PLAIN_UTF_8) .body(self) } } impl Responder for Bytes { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::build(StatusCode::OK) - .content_type("application/octet-stream") + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(mime::APPLICATION_OCTET_STREAM) .body(self) } } impl Responder for BytesMut { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::build(StatusCode::OK) - .content_type("application/octet-stream") + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::Ok() + .content_type(mime::APPLICATION_OCTET_STREAM) .body(self) } } @@ -231,7 +231,7 @@ impl CustomResponder { } impl Responder for CustomResponder { - fn respond_to(self, req: &HttpRequest) -> Response { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { let mut res = self.responder.respond_to(req); if let Some(status) = self.status { @@ -252,8 +252,8 @@ impl Responder for InternalError where T: std::fmt::Debug + std::fmt::Display + 'static, { - fn respond_to(self, _: &HttpRequest) -> Response { - Response::from_error(self.into()) + fn respond_to(self, _: &HttpRequest) -> HttpResponse { + HttpResponse::from_error(self.into()) } } diff --git a/src/types/either.rs b/src/types/either.rs index ca3ab3556..d72a14fd0 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -1,93 +1,170 @@ -use actix_http::{Error, Response}; +//! For either helper, see [`Either`]. + use bytes::Bytes; use futures_util::{future::LocalBoxFuture, FutureExt, TryFutureExt}; -use crate::{dev, request::HttpRequest, FromRequest, Responder}; +use crate::{ + dev, + web::{Form, Json}, + Error, FromRequest, HttpRequest, HttpResponse, Responder, +}; -/// Combines two different responder types into a single type +/// Combines two extractor or responder types into a single type. /// -/// ```rust -/// use actix_web::{Either, Error, HttpResponse}; +/// Can be converted to and from an [`either::Either`]. /// -/// type RegisterResult = Either>; +/// # Extractor +/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for +/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. /// -/// fn index() -> RegisterResult { -/// if is_a_variant() { -/// // <- choose left variant -/// Either::A(HttpResponse::BadRequest().body("Bad data")) +/// It is important to note that this extractor, by necessity, buffers the entire request payload +/// as part of its implementation. Though, it does respect any `PayloadConfig` maximum size limits. +/// +/// ``` +/// use actix_web::{post, web, Either}; +/// use serde::Deserialize; +/// +/// #[derive(Deserialize)] +/// struct Info { +/// name: String, +/// } +/// +/// // handler that accepts form as JSON or form-urlencoded. +/// #[post("/")] +/// async fn index(form: Either, web::Form>) -> String { +/// let name: String = match form { +/// Either::Left(json) => json.name.to_owned(), +/// Either::Right(form) => form.name.to_owned(), +/// }; +/// +/// format!("Welcome {}!", name) +/// } +/// ``` +/// +/// # Responder +/// It may be desireable to use a concrete type for a response with multiple branches. As long as +/// both types implement `Responder`, so will the `Either` type, enabling it to be used as a +/// handler's return type. +/// +/// All properties of a response are determined by the Responder branch returned. +/// +/// ``` +/// use actix_web::{get, Either, Error, HttpResponse}; +/// +/// #[get("/")] +/// async fn index() -> Either<&'static str, Result> { +/// if 1 == 2 { +/// // respond with Left variant +/// Either::Left("Bad data") /// } else { -/// Either::B( -/// // <- Right variant +/// // respond with Right variant +/// Either::Right( /// Ok(HttpResponse::Ok() -/// .content_type("text/html") -/// .body("Hello!")) +/// .content_type(mime::TEXT_HTML) +/// .body("

Hello!

")) /// ) /// } /// } -/// # fn is_a_variant() -> bool { true } -/// # fn main() {} /// ``` #[derive(Debug, PartialEq)] -pub enum Either { - /// First branch of the type - A(A), - /// Second branch of the type - B(B), +pub enum Either { + /// A value of type `L`. + Left(L), + + /// A value of type `R`. + Right(R), +} + +impl Either, Json> { + pub fn into_inner(self) -> T { + match self { + Either::Left(form) => form.into_inner(), + Either::Right(form) => form.into_inner(), + } + } +} + +impl Either, Form> { + pub fn into_inner(self) -> T { + match self { + Either::Left(form) => form.into_inner(), + Either::Right(form) => form.into_inner(), + } + } +} + +impl From> for Either { + fn from(val: either::Either) -> Self { + match val { + either::Either::Left(l) => Either::Left(l), + either::Either::Right(r) => Either::Right(r), + } + } +} + +impl From> for either::Either { + fn from(val: Either) -> Self { + match val { + Either::Left(l) => either::Either::Left(l), + Either::Right(r) => either::Either::Right(r), + } + } } #[cfg(test)] -impl Either { - pub(self) fn unwrap_left(self) -> A { +impl Either { + pub(self) fn unwrap_left(self) -> L { match self { - Either::A(data) => data, - Either::B(_) => { - panic!("Cannot unwrap left branch. Either contains a right branch.") + Either::Left(data) => data, + Either::Right(_) => { + panic!("Cannot unwrap Left branch. Either contains an `R` type.") } } } - pub(self) fn unwrap_right(self) -> B { + pub(self) fn unwrap_right(self) -> R { match self { - Either::A(_) => { - panic!("Cannot unwrap right branch. Either contains a left branch.") + Either::Left(_) => { + panic!("Cannot unwrap Right branch. Either contains an `L` type.") } - Either::B(data) => data, + Either::Right(data) => data, } } } -impl Responder for Either +/// See [here](#responder) for example of usage as a handler return type. +impl Responder for Either where - A: Responder, - B: Responder, + L: Responder, + R: Responder, { - fn respond_to(self, req: &HttpRequest) -> Response { + fn respond_to(self, req: &HttpRequest) -> HttpResponse { match self { - Either::A(a) => a.respond_to(req), - Either::B(b) => b.respond_to(req), + Either::Left(a) => a.respond_to(req), + Either::Right(b) => b.respond_to(req), } } } -/// A composite error resulting from failure to extract an `Either`. +/// A composite error resulting from failure to extract an `Either`. /// /// The implementation of `Into` will return the payload buffering error or the /// error from the primary extractor. To access the fallback error, use a match clause. #[derive(Debug)] -pub enum EitherExtractError { +pub enum EitherExtractError { /// Error from payload buffering, such as exceeding payload max size limit. Bytes(Error), /// Error from primary extractor. - Extract(A, B), + Extract(L, R), } -impl From> for Error +impl From> for Error where - A: Into, - B: Into, + L: Into, + R: Into, { - fn from(err: EitherExtractError) -> Error { + fn from(err: EitherExtractError) -> Error { match err { EitherExtractError::Bytes(err) => err, EitherExtractError::Extract(a_err, _b_err) => a_err.into(), @@ -95,17 +172,13 @@ where } } -/// Provides a mechanism for trying two extractors, a primary and a fallback. Useful for -/// "polymorphic payloads" where, for example, a form might be JSON or URL encoded. -/// -/// It is important to note that this extractor, by necessity, buffers the entire request payload -/// as part of its implementation. Though, it does respect a `PayloadConfig`'s maximum size limit. -impl FromRequest for Either +/// See [here](#extractor) for example of usage as an extractor. +impl FromRequest for Either where - A: FromRequest + 'static, - B: FromRequest + 'static, + L: FromRequest + 'static, + R: FromRequest + 'static, { - type Error = EitherExtractError; + type Error = EitherExtractError; type Future = LocalBoxFuture<'static, Result>; type Config = (); @@ -114,32 +187,32 @@ where Bytes::from_request(req, payload) .map_err(EitherExtractError::Bytes) - .and_then(|bytes| bytes_to_a_or_b(req2, bytes)) + .and_then(|bytes| bytes_to_l_or_r(req2, bytes)) .boxed_local() } } -async fn bytes_to_a_or_b( +async fn bytes_to_l_or_r( req: HttpRequest, bytes: Bytes, -) -> Result, EitherExtractError> +) -> Result, EitherExtractError> where - A: FromRequest + 'static, - B: FromRequest + 'static, + L: FromRequest + 'static, + R: FromRequest + 'static, { let fallback = bytes.clone(); let a_err; let mut pl = payload_from_bytes(bytes); - match A::from_request(&req, &mut pl).await { - Ok(a_data) => return Ok(Either::A(a_data)), + match L::from_request(&req, &mut pl).await { + Ok(a_data) => return Ok(Either::Left(a_data)), // store A's error for returning if B also fails Err(err) => a_err = err, }; let mut pl = payload_from_bytes(fallback); - match B::from_request(&req, &mut pl).await { - Ok(b_data) => return Ok(Either::B(b_data)), + match R::from_request(&req, &mut pl).await { + Ok(b_data) => return Ok(Either::Right(b_data)), Err(b_err) => Err(EitherExtractError::Extract(a_err, b_err)), } } diff --git a/src/types/form.rs b/src/types/form.rs index cdd280491..71680b19a 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -1,72 +1,68 @@ -//! Form extractor +//! For URL encoded form helper documentation, see [`Form`]. -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, ops}; +use std::{ + fmt, + future::Future, + ops, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; -use actix_http::{Error, HttpMessage, Payload, Response}; +use actix_http::Payload; use bytes::BytesMut; use encoding_rs::{Encoding, UTF_8}; -use futures_util::future::{FutureExt, LocalBoxFuture}; -use futures_util::StreamExt; -use serde::de::DeserializeOwned; -use serde::Serialize; +use futures_util::{ + future::{FutureExt, LocalBoxFuture}, + StreamExt, +}; +use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "compress")] use crate::dev::Decompress; -use crate::error::UrlencodedError; -use crate::extract::FromRequest; -use crate::http::{ - header::{ContentType, CONTENT_LENGTH}, - StatusCode, +use crate::{ + error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, + Error, HttpMessage, HttpRequest, HttpResponse, Responder, }; -use crate::request::HttpRequest; -use crate::{responder::Responder, web}; -/// Form data helper (`application/x-www-form-urlencoded`) +/// URL encoded payload extractor and responder. /// -/// Can be use to extract url-encoded data from the request body, -/// or send url-encoded data as the response. +/// `Form` has two uses: URL encoded responses, and extracting typed data from URL request payloads. /// -/// ## Extract +/// # Extractor +/// To extract typed data from a request body, the inner type `T` must implement the +/// [`serde::Deserialize`] trait. /// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. +/// Use [`FormConfig`] to configure extraction process. /// -/// [**FormConfig**](FormConfig) allows to configure extraction -/// process. -/// -/// ### Example -/// ```rust -/// use actix_web::web; -/// use serde_derive::Deserialize; +/// ``` +/// use actix_web::{post, web}; +/// use serde::Deserialize; /// /// #[derive(Deserialize)] -/// struct FormData { -/// username: String, +/// struct Info { +/// name: String, /// } /// -/// /// Extract form data using serde. -/// /// This handler get called only if content type is *x-www-form-urlencoded* -/// /// and content of the request could be deserialized to a `FormData` struct -/// fn index(form: web::Form) -> String { -/// format!("Welcome {}!", form.username) +/// // This handler is only called if: +/// // - request headers declare the content type as `application/x-www-form-urlencoded` +/// // - request payload is deserialized into a `Info` struct from the URL encoded format +/// #[post("/")] +/// async fn index(form: web::Form) -> String { +/// format!("Welcome {}!", form.name) /// } -/// # fn main() {} /// ``` /// -/// ## Respond +/// # Responder +/// The `Form` type also allows you to create URL encoded responses: +/// simply return a value of type Form where T is the type to be URL encoded. +/// The type must implement [`serde::Serialize`]. /// -/// The `Form` type also allows you to respond with well-formed url-encoded data: -/// simply return a value of type Form where T is the type to be url-encoded. -/// The type must implement `serde::Serialize`; +/// Responses use /// -/// ### Example -/// ```rust -/// use actix_web::*; -/// use serde_derive::Serialize; +/// ``` +/// use actix_web::{get, web}; +/// use serde::Serialize; /// /// #[derive(Serialize)] /// struct SomeForm { @@ -74,22 +70,23 @@ use crate::{responder::Responder, web}; /// age: u8 /// } /// -/// // Will return a 200 response with header -/// // `Content-Type: application/x-www-form-urlencoded` -/// // and body "name=actix&age=123" -/// fn index() -> web::Form { +/// // Response will have: +/// // - status: 200 OK +/// // - header: `Content-Type: application/x-www-form-urlencoded` +/// // - body: `name=actix&age=123` +/// #[get("/")] +/// async fn index() -> web::Form { /// web::Form(SomeForm { /// name: "actix".into(), /// age: 123 /// }) /// } -/// # fn main() {} /// ``` #[derive(PartialEq, Eq, PartialOrd, Ord)] pub struct Form(pub T); impl Form { - /// Deconstruct to an inner value + /// Unwrap into inner `T` value. pub fn into_inner(self) -> T { self.0 } @@ -109,6 +106,7 @@ impl ops::DerefMut for Form { } } +/// See [here](#extractor) for example of usage as an extractor. impl FromRequest for Form where T: DeserializeOwned + 'static, @@ -120,7 +118,7 @@ where #[inline] fn from_request(req: &HttpRequest, payload: &mut Payload) -> Self::Future { let req2 = req.clone(); - let (limit, err) = req + let (limit, err_handler) = req .app_data::() .or_else(|| { req.app_data::>() @@ -132,13 +130,10 @@ where UrlEncoded::new(req, payload) .limit(limit) .map(move |res| match res { - Err(e) => { - if let Some(err) = err { - Err((*err)(e, &req2)) - } else { - Err(e.into()) - } - } + Err(err) => match err_handler { + Some(err_handler) => Err((err_handler)(err, &req2)), + None => Err(err.into()), + }, Ok(item) => Ok(Form(item)), }) .boxed_local() @@ -157,44 +152,39 @@ impl fmt::Display for Form { } } +/// See [here](#responder) for example of usage as a handler return type. impl Responder for Form { - fn respond_to(self, _: &HttpRequest) -> Response { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_urlencoded::to_string(&self.0) { - Ok(body) => Response::build(StatusCode::OK) - .set(ContentType::form_url_encoded()) + Ok(body) => HttpResponse::Ok() + .content_type(mime::APPLICATION_WWW_FORM_URLENCODED) .body(body), - Err(e) => Response::from_error(e.into()), + Err(err) => HttpResponse::from_error(err.into()), } } } -/// Form extractor configuration +/// [`Form`] extractor configuration. /// -/// ```rust -/// use actix_web::{web, App, FromRequest, Result}; -/// use serde_derive::Deserialize; +/// ``` +/// use actix_web::{post, web, App, FromRequest, Result}; +/// use serde::Deserialize; /// /// #[derive(Deserialize)] -/// struct FormData { +/// struct Info { /// username: String, /// } /// -/// /// Extract form data using serde. -/// /// Custom configuration is used for this handler, max payload size is 4k -/// async fn index(form: web::Form) -> Result { +/// // Custom `FormConfig` is applied to App. +/// // Max payload size for URL encoded forms is set to 4kB. +/// #[post("/")] +/// async fn index(form: web::Form) -> Result { /// Ok(format!("Welcome {}!", form.username)) /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// // change `Form` extractor configuration -/// .app_data( -/// web::FormConfig::default().limit(4097) -/// ) -/// .route(web::get().to(index)) -/// ); -/// } +/// App::new() +/// .app_data(web::FormConfig::default().limit(4096)) +/// .service(index); /// ``` #[derive(Clone)] pub struct FormConfig { @@ -203,7 +193,7 @@ pub struct FormConfig { } impl FormConfig { - /// Change max size of payload. By default max size is 16Kb + /// Set maximum accepted payload size. By default this limit is 16kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self @@ -228,33 +218,30 @@ impl Default for FormConfig { } } -/// Future that resolves to a parsed urlencoded values. +/// Future that resolves to some `T` when parsed from a URL encoded payload. /// -/// Parse `application/x-www-form-urlencoded` encoded request's body. -/// Return `UrlEncoded` future. Form can be deserialized to any type that -/// implements `Deserialize` trait from *serde*. +/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`]. /// -/// Returns error: -/// -/// * content type is not `application/x-www-form-urlencoded` -/// * content-length is greater than 32k -/// -pub struct UrlEncoded { +/// Returns error if: +/// - content type is not `application/x-www-form-urlencoded` +/// - content length is greater than [limit](UrlEncoded::limit()) +pub struct UrlEncoded { #[cfg(feature = "compress")] stream: Option>, #[cfg(not(feature = "compress"))] stream: Option, + limit: usize, length: Option, encoding: &'static Encoding, err: Option, - fut: Option>>, + fut: Option>>, } #[allow(clippy::borrow_interior_mutable_const)] -impl UrlEncoded { - /// Create a new future to URL encode a request - pub fn new(req: &HttpRequest, payload: &mut Payload) -> UrlEncoded { +impl UrlEncoded { + /// Create a new future to decode a URL encoded request payload. + pub fn new(req: &HttpRequest, payload: &mut Payload) -> Self { // check content type if req.content_type().to_lowercase() != "application/x-www-form-urlencoded" { return Self::err(UrlencodedError::ContentType); @@ -292,29 +279,29 @@ impl UrlEncoded { } } - fn err(e: UrlencodedError) -> Self { + fn err(err: UrlencodedError) -> Self { UrlEncoded { stream: None, limit: 32_768, fut: None, - err: Some(e), + err: Some(err), length: None, encoding: UTF_8, } } - /// Change max size of payload. By default max size is 256Kb + /// Set maximum accepted payload size. The default limit is 256kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } } -impl Future for UrlEncoded +impl Future for UrlEncoded where - U: DeserializeOwned + 'static, + T: DeserializeOwned + 'static, { - type Output = Result; + type Output = Result; fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if let Some(ref mut fut) = self.fut { @@ -343,6 +330,7 @@ where while let Some(item) = stream.next().await { let chunk = item?; + if (body.len() + chunk.len()) > limit { return Err(UrlencodedError::Overflow { size: body.len() + chunk.len(), @@ -354,19 +342,21 @@ where } if encoding == UTF_8 { - serde_urlencoded::from_bytes::(&body) + serde_urlencoded::from_bytes::(&body) .map_err(|_| UrlencodedError::Parse) } else { let body = encoding .decode_without_bom_handling_and_without_replacement(&body) .map(|s| s.into_owned()) .ok_or(UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) + + serde_urlencoded::from_str::(&body) .map_err(|_| UrlencodedError::Parse) } } .boxed_local(), ); + self.poll(cx) } } @@ -377,7 +367,10 @@ mod tests { use serde::{Deserialize, Serialize}; use super::*; - use crate::http::header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}; + use crate::http::{ + header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, + StatusCode, + }; use crate::test::TestRequest; #[derive(Deserialize, Serialize, Debug, PartialEq)] @@ -514,6 +507,6 @@ mod tests { assert!(s.is_err()); let err_str = s.err().unwrap().to_string(); - assert!(err_str.contains("Urlencoded payload size is bigger")); + assert!(err_str.starts_with("URL encoded payload is larger")); } } diff --git a/src/types/json.rs b/src/types/json.rs index 452cb58c1..edfb775f3 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -1,45 +1,44 @@ -//! Json extractor/responder +//! For JSON helper documentation, see [`Json`]. -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::sync::Arc; -use std::task::{Context, Poll}; -use std::{fmt, ops}; +use std::{ + fmt, + future::Future, + marker::PhantomData, + ops, + pin::Pin, + sync::Arc, + task::{Context, Poll}, +}; use bytes::BytesMut; -use futures_util::ready; -use futures_util::stream::Stream; -use serde::de::DeserializeOwned; -use serde::Serialize; +use futures_util::{ready, stream::Stream}; +use serde::{de::DeserializeOwned, Serialize}; -use actix_http::http::{header::CONTENT_LENGTH, StatusCode}; -use actix_http::{HttpMessage, Payload, Response}; +use actix_http::Payload; #[cfg(feature = "compress")] use crate::dev::Decompress; -use crate::error::{Error, JsonPayloadError}; -use crate::extract::FromRequest; -use crate::request::HttpRequest; -use crate::{responder::Responder, web}; +use crate::{ + error::{Error, JsonPayloadError}, + extract::FromRequest, + http::header::CONTENT_LENGTH, + request::HttpRequest, + web, HttpMessage, HttpResponse, Responder, +}; -/// Json helper +/// JSON extractor and responder. /// -/// Json can be used for two different purpose. First is for json response -/// generation and second is for extracting typed information from request's -/// payload. +/// `Json` has two uses: JSON responses, and extracting typed data from JSON request payloads. /// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. +/// # Extractor +/// To extract typed data from a request body, the inner type `T` must implement the +/// [`serde::Deserialize`] trait. /// -/// [**JsonConfig**](JsonConfig) allows to configure extraction -/// process. +/// Use [`JsonConfig`] to configure extraction process. /// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; +/// ``` +/// use actix_web::{post, web, App}; +/// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -47,43 +46,37 @@ use crate::{responder::Responder, web}; /// } /// /// /// deserialize `Info` from request's body +/// #[post("/")] /// async fn index(info: web::Json) -> String { /// format!("Welcome {}!", info.username) /// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().to(index)) -/// ); -/// } /// ``` /// -/// The `Json` type allows you to respond with well-formed JSON data: simply -/// return a value of type Json where T is the type of a structure -/// to serialize into *JSON*. The type `T` must implement the `Serialize` -/// trait from *serde*. +/// # Responder +/// The `Json` type JSON formatted responses. A handler may return a value of type +/// `Json` where `T` is the type of a structure to serialize into JSON. The type `T` must +/// implement [`serde::Serialize`]. /// -/// ```rust -/// use actix_web::*; -/// use serde_derive::Serialize; +/// ``` +/// use actix_web::{post, web, HttpRequest}; +/// use serde::Serialize; /// /// #[derive(Serialize)] -/// struct MyObj { +/// struct Info { /// name: String, /// } /// -/// fn index(req: HttpRequest) -> Result> { -/// Ok(web::Json(MyObj { -/// name: req.match_info().get("name").unwrap().to_string(), -/// })) +/// #[post("/{name}")] +/// async fn index(req: HttpRequest) -> web::Json { +/// web::Json(Info { +/// name: req.match_info().get("name").unwrap().to_owned(), +/// }) /// } -/// # fn main() {} /// ``` pub struct Json(pub T); impl Json { - /// Deconstruct to an inner value + /// Unwrap into inner `T` value. pub fn into_inner(self) -> T { self.0 } @@ -121,49 +114,21 @@ where } } +/// Creates response with OK status code, correct content type header, and serialized JSON payload. +/// +/// If serialization failed impl Responder for Json { - fn respond_to(self, _: &HttpRequest) -> Response { + fn respond_to(self, _: &HttpRequest) -> HttpResponse { match serde_json::to_string(&self.0) { - Ok(body) => Response::build(StatusCode::OK) - .content_type("application/json") + Ok(body) => HttpResponse::Ok() + .content_type(mime::APPLICATION_JSON) .body(body), - Err(e) => Response::from_error(e.into()), + Err(err) => HttpResponse::from_error(err.into()), } } } -/// Json extractor. Allow to extract typed information from request's -/// payload. -/// -/// To extract typed information from request's body, the type `T` must -/// implement the `Deserialize` trait from *serde*. -/// -/// [**JsonConfig**](JsonConfig) allows to configure extraction -/// process. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// deserialize `Info` from request's body -/// async fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::post().to(index)) -/// ); -/// } -/// ``` +/// See [here](#extractor) for example of usage as an extractor. impl FromRequest for Json where T: DeserializeOwned + 'static, @@ -209,7 +174,7 @@ where let res = ready!(Pin::new(&mut this.fut).poll(cx)); let res = match res { - Err(e) => { + Err(err) => { let req = this.req.take().unwrap(); log::debug!( "Failed to deserialize Json from payload. \ @@ -217,10 +182,10 @@ where req.path() ); - if let Some(err) = this.err_handler.as_ref() { - Err((*err)(e, &req)) + if let Some(err_handler) = this.err_handler.as_ref() { + Err((*err_handler)(err, &req)) } else { - Err(e.into()) + Err(err.into()) } } Ok(data) => Ok(Json(data)), @@ -230,44 +195,39 @@ where } } -/// Json extractor configuration +/// `Json` extractor configuration. /// -/// # Example -/// -/// ```rust -/// use actix_web::{error, web, App, FromRequest, HttpResponse}; -/// use serde_derive::Deserialize; +/// # Usage +/// ``` +/// use actix_web::{error, post, web, App, FromRequest, HttpResponse}; +/// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { -/// username: String, +/// name: String, /// } /// -/// /// deserialize `Info` from request's body, max payload size is 4kb +/// // `Json` extraction is bound by custom `JsonConfig` applied to App. +/// #[post("/")] /// async fn index(info: web::Json) -> String { -/// format!("Welcome {}!", info.username) +/// format!("Welcome {}!", info.name) /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .app_data( -/// // Json extractor configuration for this resource. -/// web::JsonConfig::default() -/// .limit(4096) // Limit request payload size -/// .content_type(|mime| { // <- accept text/plain content type -/// mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN -/// }) -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }) -/// ) -/// .route(web::post().to(index)) -/// ); -/// } +/// // custom `Json` extractor configuration +/// let json_cfg = web::JsonConfig::default() +/// // limit request payload size +/// .limit(4096) +/// // only accept text/plain content type +/// .content_type(|mime| mime == mime::TEXT_PLAIN) +/// // use custom error handler +/// .error_handler(|err, req| { +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// }); +/// +/// App::new() +/// .app_data(json_cfg) +/// .service(index); /// ``` -/// #[derive(Clone)] pub struct JsonConfig { limit: usize, @@ -276,13 +236,13 @@ pub struct JsonConfig { } impl JsonConfig { - /// Change max size of payload. By default max size is 32Kb + /// Set maximum accepted payload size. By default this limit is 32kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } - /// Set custom error handler + /// Set custom error handler. pub fn error_handler(mut self, f: F) -> Self where F: Fn(JsonPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, @@ -291,7 +251,7 @@ impl JsonConfig { self } - /// Set predicate for allowed content types + /// Set predicate for allowed content types. pub fn content_type(mut self, predicate: F) -> Self where F: Fn(mime::Mime) -> bool + Send + Sync + 'static, @@ -322,15 +282,14 @@ impl Default for JsonConfig { } } -/// Request's payload json parser, it resolves to a deserialized `T` value. -/// This future could be used with `ServiceRequest` and `ServiceFromRequest`. +/// Future that resolves to some `T` when parsed from a JSON payload. /// -/// Returns error: +/// Form can be deserialized from any type `T` that implements [`serde::Deserialize`]. /// -/// * content type is not `application/json` -/// (unless specified in [`JsonConfig`]) -/// * content length is greater than 256k -pub enum JsonBody { +/// Returns error if: +/// - content type is not `application/json` +/// - content length is greater than [limit](JsonBody::limit()) +pub enum JsonBody { Error(Option), Body { limit: usize, @@ -340,17 +299,17 @@ pub enum JsonBody { #[cfg(not(feature = "compress"))] payload: Payload, buf: BytesMut, - _res: PhantomData, + _res: PhantomData, }, } -impl Unpin for JsonBody {} +impl Unpin for JsonBody {} -impl JsonBody +impl JsonBody where - U: DeserializeOwned + 'static, + T: DeserializeOwned + 'static, { - /// Create `JsonBody` for request. + /// Create a new future to decode a JSON request payload. #[allow(clippy::borrow_interior_mutable_const)] pub fn new( req: &HttpRequest, @@ -394,7 +353,7 @@ where } } - /// Change max size of payload. By default max size is 256Kb + /// Set maximum accepted payload size. The default limit is 256kB. pub fn limit(self, limit: usize) -> Self { match self { JsonBody::Body { @@ -422,11 +381,11 @@ where } } -impl Future for JsonBody +impl Future for JsonBody where - U: DeserializeOwned + 'static, + T: DeserializeOwned + 'static, { - type Output = Result; + type Output = Result; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); @@ -449,7 +408,7 @@ where } } None => { - let json = serde_json::from_slice::(&buf)?; + let json = serde_json::from_slice::(&buf)?; return Poll::Ready(Ok(json)); } } @@ -462,13 +421,17 @@ where #[cfg(test)] mod tests { use bytes::Bytes; - use serde_derive::{Deserialize, Serialize}; + use serde::{Deserialize, Serialize}; use super::*; - use crate::error::InternalError; - use crate::http::header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}; - use crate::test::{load_stream, TestRequest}; - use crate::HttpResponse; + use crate::{ + error::InternalError, + http::{ + header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, + StatusCode, + }, + test::{load_stream, TestRequest}, + }; #[derive(Serialize, Deserialize, PartialEq, Debug)] struct MyObject { @@ -526,7 +489,7 @@ mod tests { .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; - let mut resp = Response::from_error(s.err().unwrap()); + let mut resp = HttpResponse::from_error(s.err().unwrap()); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let body = load_stream(resp.take_body()).await.unwrap(); diff --git a/src/types/mod.rs b/src/types/mod.rs index cedf86dd2..a062c351e 100644 --- a/src/types/mod.rs +++ b/src/types/mod.rs @@ -1,5 +1,6 @@ -//! Helper types +//! Common extractors and responders. +// TODO: review visibility mod either; pub(crate) mod form; pub(crate) mod json; diff --git a/src/types/path.rs b/src/types/path.rs index 640ff4346..9ee5106d0 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -1,70 +1,55 @@ -//! Path extractor -use std::sync::Arc; -use std::{fmt, ops}; +//! For path segment extractor documentation, see [`Path`]. + +use std::{fmt, ops, sync::Arc}; use actix_http::error::{Error, ErrorNotFound}; use actix_router::PathDeserializer; use futures_util::future::{ready, Ready}; use serde::de; -use crate::dev::Payload; -use crate::error::PathError; -use crate::request::HttpRequest; -use crate::FromRequest; +use crate::{dev::Payload, error::PathError, FromRequest, HttpRequest}; -#[derive(PartialEq, Eq, PartialOrd, Ord)] -/// Extract typed information from the request's path. +/// Extract typed data from request path segments. /// -/// [**PathConfig**](PathConfig) allows to configure extraction process. +/// Use [`PathConfig`] to configure extraction process. /// -/// ## Example +/// # Usage +/// ``` +/// use actix_web::{get, web}; /// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String { -/// format!("Welcome {}! {}", username, count) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/{count}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- register handler with `Path` extractor -/// ); +/// // extract path info from "/{name}/{count}/index.html" into tuple +/// // {name} - deserialize a String +/// // {count} - deserialize a u32 +/// #[get("/")] +/// async fn index(path: web::Path<(String, u32)>) -> String { +/// let (name, count) = path.into_inner(); +/// format!("Welcome {}! {}", name, count) /// } /// ``` /// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. +/// Path segments also can be deserialized into any type that implements [`serde::Deserialize`]. +/// Path segment labels will be matched with struct field names. /// -/// ```rust -/// use actix_web::{web, App, Error}; -/// use serde_derive::Deserialize; +/// ``` +/// use actix_web::{get, web}; +/// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { -/// username: String, +/// name: String, /// } /// -/// /// extract `Info` from a path using serde -/// async fn index(info: web::Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- use handler with Path` extractor -/// ); +/// // extract `Info` from a path using serde +/// #[get("/")] +/// async fn index(info: web::Path) -> String { +/// format!("Welcome {}!", info.name) /// } /// ``` -pub struct Path(pub T); +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct Path(T); impl Path { - /// Deconstruct to an inner value + /// Unwrap into inner `T` value. pub fn into_inner(self) -> T { self.0 } @@ -108,52 +93,7 @@ impl fmt::Display for Path { } } -/// Extract typed information from the request's path. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// -/// /// extract path info from "/{username}/{count}/index.html" url -/// /// {username} - deserializes to a String -/// /// {count} - - deserializes to a u32 -/// async fn index(web::Path((username, count)): web::Path<(String, u32)>) -> String { -/// format!("Welcome {}! {}", username, count) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/{count}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- register handler with `Path` extractor -/// ); -/// } -/// ``` -/// -/// It is possible to extract path information to a specific type that -/// implements `Deserialize` trait from *serde*. -/// -/// ```rust -/// use actix_web::{web, App, Error}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Deserialize)] -/// struct Info { -/// username: String, -/// } -/// -/// /// extract `Info` from a path using serde -/// async fn index(info: web::Path) -> Result { -/// Ok(format!("Welcome {}!", info.username)) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/{username}/index.html") // <- define path parameters -/// .route(web::get().to(index)) // <- use handler with Path` extractor -/// ); -/// } -/// ``` +/// See [here](#usage) for example of usage as an extractor. impl FromRequest for Path where T: de::DeserializeOwned, @@ -191,10 +131,10 @@ where /// Path extractor configuration /// -/// ```rust +/// ``` /// use actix_web::web::PathConfig; /// use actix_web::{error, web, App, FromRequest, HttpResponse}; -/// use serde_derive::Deserialize; +/// use serde::Deserialize; /// /// #[derive(Deserialize, Debug)] /// enum Folder { @@ -249,7 +189,7 @@ impl Default for PathConfig { mod tests { use actix_router::ResourceDef; use derive_more::Display; - use serde_derive::Deserialize; + use serde::Deserialize; use super::*; use crate::test::TestRequest; diff --git a/src/types/payload.rs b/src/types/payload.rs index 14457176d..22528031c 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -1,57 +1,51 @@ -//! Payload/Bytes/String extractors -use std::future::Future; -use std::pin::Pin; -use std::str; -use std::task::{Context, Poll}; +//! Basic binary and string payload extractors. -use actix_http::error::{Error, ErrorBadRequest, PayloadError}; -use actix_http::HttpMessage; +use std::{ + future::Future, + pin::Pin, + str, + task::{Context, Poll}, +}; + +use actix_http::error::{ErrorBadRequest, PayloadError}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; use futures_core::stream::Stream; use futures_util::{ - future::{err, ok, Either, ErrInto, Ready, TryFutureExt as _}, + future::{ready, Either, ErrInto, Ready, TryFutureExt as _}, ready, }; use mime::Mime; -use crate::extract::FromRequest; -use crate::http::header; -use crate::request::HttpRequest; -use crate::{dev, web}; +use crate::{dev, http::header, web, Error, FromRequest, HttpMessage, HttpRequest}; -/// Payload extractor returns request 's payload stream. +/// Extract a request's raw payload stream. /// -/// ## Example +/// See [`PayloadConfig`] for important notes when using this advanced extractor. /// -/// ```rust -/// use actix_web::{web, error, App, Error, HttpResponse}; +/// # Usage +/// ``` /// use std::future::Future; -/// use futures_core::stream::Stream; -/// use futures_util::StreamExt; -/// /// extract binary data from request -/// async fn index(mut body: web::Payload) -> Result -/// { +/// use futures_util::stream::{Stream, StreamExt}; +/// use actix_web::{post, web}; +/// +/// // `body: web::Payload` parameter extracts raw payload stream from request +/// #[post("/")] +/// async fn index(mut body: web::Payload) -> actix_web::Result { +/// // for demonstration only; in a normal case use the `Bytes` extractor +/// // collect payload stream into a bytes object /// let mut bytes = web::BytesMut::new(); /// while let Some(item) = body.next().await { /// bytes.extend_from_slice(&item?); /// } /// -/// format!("Body {:?}!", bytes); -/// Ok(HttpResponse::Ok().finish()) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); +/// Ok(format!("Request Body Bytes:\n{:?}", bytes)) /// } /// ``` pub struct Payload(pub crate::dev::Payload); impl Payload { - /// Deconstruct to a inner value + /// Unwrap to inner Payload type. pub fn into_inner(self) -> crate::dev::Payload { self.0 } @@ -69,35 +63,7 @@ impl Stream for Payload { } } -/// Get request's payload stream -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, error, App, Error, HttpResponse}; -/// use std::future::Future; -/// use futures_core::stream::Stream; -/// use futures_util::StreamExt; -/// -/// /// extract binary data from request -/// async fn index(mut body: web::Payload) -> Result -/// { -/// let mut bytes = web::BytesMut::new(); -/// while let Some(item) = body.next().await { -/// bytes.extend_from_slice(&item?); -/// } -/// -/// format!("Body {:?}!", bytes); -/// Ok(HttpResponse::Ok().finish()) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); -/// } -/// ``` +/// See [here](#usage) for example of usage as an extractor. impl FromRequest for Payload { type Config = PayloadConfig; type Error = Error; @@ -105,34 +71,25 @@ impl FromRequest for Payload { #[inline] fn from_request(_: &HttpRequest, payload: &mut dev::Payload) -> Self::Future { - ok(Payload(payload.take())) + ready(Ok(Payload(payload.take()))) } } -/// Request binary data from a request's payload. +/// Extract binary data from a request's payload. /// -/// Loads request's payload and construct Bytes instance. +/// Collects request payload stream into a [Bytes] instance. /// -/// [**PayloadConfig**](PayloadConfig) allows to configure -/// extraction process. +/// Use [`PayloadConfig`] to configure extraction process. /// -/// ## Example -/// -/// ```rust -/// use bytes::Bytes; -/// use actix_web::{web, App}; +/// # Usage +/// ``` +/// use actix_web::{post, web}; /// /// /// extract binary data from request -/// async fn index(body: Bytes) -> String { +/// #[post("/")] +/// async fn index(body: web::Bytes) -> String { /// format!("Body {:?}!", body) /// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route( -/// web::get().to(index)) -/// ); -/// } /// ``` impl FromRequest for Bytes { type Config = PayloadConfig; @@ -144,8 +101,8 @@ impl FromRequest for Bytes { // allow both Config and Data let cfg = PayloadConfig::from_req(req); - if let Err(e) = cfg.check_mimetype(req) { - return Either::Right(err(e)); + if let Err(err) = cfg.check_mimetype(req) { + return Either::Right(ready(Err(err))); } let limit = cfg.limit; @@ -161,26 +118,15 @@ impl FromRequest for Bytes { /// [**PayloadConfig**](PayloadConfig) allows to configure /// extraction process. /// -/// ## Example +/// # Usage +/// ``` +/// use actix_web::{post, web, FromRequest}; /// -/// ```rust -/// use actix_web::{web, App, FromRequest}; -/// -/// /// extract text data from request +/// // extract text data from request +/// #[post("/")] /// async fn index(text: String) -> String { /// format!("Body {}!", text) /// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .app_data(String::configure(|cfg| { // <- limit size of the payload -/// cfg.limit(4096) -/// })) -/// .route(web::get().to(index)) // <- register handler with extractor params -/// ); -/// } -/// ``` impl FromRequest for String { type Config = PayloadConfig; type Error = Error; @@ -191,14 +137,14 @@ impl FromRequest for String { let cfg = PayloadConfig::from_req(req); // check content-type - if let Err(e) = cfg.check_mimetype(req) { - return Either::Right(err(e)); + if let Err(err) = cfg.check_mimetype(req) { + return Either::Right(ready(Err(err))); } // check charset let encoding = match req.encoding() { Ok(enc) => enc, - Err(e) => return Either::Right(err(e.into())), + Err(err) => return Either::Right(ready(Err(err.into()))), }; let limit = cfg.limit; let body_fut = HttpMessageBody::new(req, payload).limit(limit); @@ -238,11 +184,13 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result Self { Self { limit, @@ -258,14 +206,13 @@ impl PayloadConfig { } } - /// Change max size of payload. By default max size is 256Kb + /// Set maximum accepted payload size. The default limit is 256kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self } - /// Set required mime-type of the request. By default mime type is not - /// enforced. + /// Set required mime type of the request. By default mime type is not enforced. pub fn mimetype(mut self, mt: Mime) -> Self { self.mimetype = Some(mt); self @@ -292,7 +239,7 @@ impl PayloadConfig { } /// Extract payload config from app data. Check both `T` and `Data`, in that order, and fall - /// back to the default payload config. + /// back to the default payload config if neither is found. fn from_req(req: &HttpRequest) -> &Self { req.app_data::() .or_else(|| req.app_data::>().map(|d| d.as_ref())) @@ -314,13 +261,10 @@ impl Default for PayloadConfig { } } -/// Future that resolves to a complete http message body. +/// Future that resolves to a complete HTTP body payload. /// -/// Load http message body. -/// -/// By default only 256Kb payload reads to a memory, then -/// `PayloadError::Overflow` get returned. Use `MessageBody::limit()` -/// method to change upper limit. +/// By default only 256kB payload is accepted before `PayloadError::Overflow` is returned. +/// Use `MessageBody::limit()` method to change upper limit. pub struct HttpMessageBody { limit: usize, length: Option, @@ -366,7 +310,7 @@ impl HttpMessageBody { } } - /// Change max size of payload. By default max size is 256Kb + /// Change max size of payload. By default max size is 256kB pub fn limit(mut self, limit: usize) -> Self { if let Some(l) = self.length { if l > limit { @@ -384,8 +328,8 @@ impl Future for HttpMessageBody { fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); - if let Some(e) = this.err.take() { - return Poll::Ready(Err(e)); + if let Some(err) = this.err.take() { + return Poll::Ready(Err(err)); } loop { diff --git a/src/types/query.rs b/src/types/query.rs index 27df220fc..2cfc18c36 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -1,30 +1,27 @@ -//! Query extractor +//! For query parameter extractor documentation, see [`Query`]. -use std::sync::Arc; -use std::{fmt, ops}; +use std::{fmt, ops, sync::Arc}; -use actix_http::error::Error; use futures_util::future::{err, ok, Ready}; use serde::de; -use crate::dev::Payload; -use crate::error::QueryPayloadError; -use crate::extract::FromRequest; -use crate::request::HttpRequest; +use crate::{dev::Payload, error::QueryPayloadError, Error, FromRequest, HttpRequest}; /// Extract typed information from the request's query. /// -/// **Note**: A query string consists of unordered `key=value` pairs, therefore it cannot -/// be decoded into any type which depends upon data ordering e.g. tuples or tuple-structs. -/// Attempts to do so will *fail at runtime*. +/// To extract typed data from the URL query string, the inner type `T` must implement the +/// [`serde::Deserialize`] trait. /// -/// [**QueryConfig**](QueryConfig) allows to configure extraction process. +/// Use [`QueryConfig`] to configure extraction process. /// -/// ## Example +/// # Panics +/// A query string consists of unordered `key=value` pairs, therefore it cannot be decoded into any +/// type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic. /// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; +/// # Usage +/// ``` +/// use actix_web::{get, web}; +/// use serde::Deserialize; /// /// #[derive(Debug, Deserialize)] /// pub enum ResponseType { @@ -38,35 +35,40 @@ use crate::request::HttpRequest; /// response_type: ResponseType, /// } /// -/// // Use `Query` extractor for query information (and destructure it within the signature). -/// // This handler gets called only if the request's query string contains `id` and `response_type` fields. -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"`. -/// async fn index(web::Query(info): web::Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").route(web::get().to(index))); // <- use `Query` extractor +/// // Deserialize `AuthRequest` struct from query string. +/// // This handler gets called only if the request's query parameters contain both fields. +/// // A valid request path for this handler would be `/?id=64&response_type=Code"`. +/// #[get("/")] +/// async fn index(info: web::Query) -> String { +/// format!("Authorization request for id={} and type={:?}!", info.id, info.response_type) /// } /// ``` -#[derive(PartialEq, Eq, PartialOrd, Ord)] -pub struct Query(pub T); +#[derive(Clone, PartialEq, Eq, PartialOrd, Ord)] +pub struct Query(T); impl Query { - /// Deconstruct to a inner value + /// Unwrap into inner `T` value. pub fn into_inner(self) -> T { self.0 } - /// Get query parameters from the path + /// Deserialize `T` from a URL encoded query parameter string. + /// + /// ``` + /// # use std::collections::HashMap; + /// # use actix_web::web::Query; + /// let numbers = Query::>::from_query("one=1&two=2").unwrap(); + /// assert_eq!(numbers.get("one"), Some(&1)); + /// assert_eq!(numbers.get("two"), Some(&2)); + /// assert!(numbers.get("three").is_none()); + /// ``` pub fn from_query(query_str: &str) -> Result where T: de::DeserializeOwned, { serde_urlencoded::from_str::(query_str) - .map(|val| Ok(Query(val))) - .unwrap_or_else(move |e| Err(QueryPayloadError::Deserialize(e))) + .map(Self) + .map_err(QueryPayloadError::Deserialize) } } @@ -96,39 +98,7 @@ impl fmt::Display for Query { } } -/// Extract typed information from the request's query. -/// -/// ## Example -/// -/// ```rust -/// use actix_web::{web, App}; -/// use serde_derive::Deserialize; -/// -/// #[derive(Debug, Deserialize)] -/// pub enum ResponseType { -/// Token, -/// Code -/// } -/// -/// #[derive(Deserialize)] -/// pub struct AuthRequest { -/// id: u64, -/// response_type: ResponseType, -/// } -/// -/// // Use `Query` extractor for query information. -/// // This handler get called only if request's query contains `id` and `response_type` fields. -/// // The correct request for this handler would be `/index.html?id=64&response_type=Code"` -/// async fn index(info: web::Query) -> String { -/// format!("Authorization request for client with id={} and type={:?}!", info.id, info.response_type) -/// } -/// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html") -/// .route(web::get().to(index))); // <- use `Query` extractor -/// } -/// ``` +/// See [here](#usage) for example of usage as an extractor. impl FromRequest for Query where T: de::DeserializeOwned, @@ -141,7 +111,7 @@ where fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future { let error_handler = req .app_data::() - .map(|c| c.ehandler.clone()) + .map(|c| c.err_handler.clone()) .unwrap_or(None); serde_urlencoded::from_str::(req.query_string()) @@ -166,13 +136,12 @@ where } } -/// Query extractor configuration +/// Query extractor configuration. /// -/// ## Example -/// -/// ```rust -/// use actix_web::{error, web, App, FromRequest, HttpResponse}; -/// use serde_derive::Deserialize; +/// # Usage +/// ``` +/// use actix_web::{error, get, web, App, FromRequest, HttpResponse}; +/// use serde::Deserialize; /// /// #[derive(Deserialize)] /// struct Info { @@ -180,27 +149,25 @@ where /// } /// /// /// deserialize `Info` from request's querystring +/// #[get("/")] /// async fn index(info: web::Query) -> String { /// format!("Welcome {}!", info.username) /// } /// -/// fn main() { -/// let app = App::new().service( -/// web::resource("/index.html").app_data( -/// // change query extractor configuration -/// web::QueryConfig::default() -/// .error_handler(|err, req| { // <- create custom error response -/// error::InternalError::from_response( -/// err, HttpResponse::Conflict().finish()).into() -/// }) -/// ) -/// .route(web::post().to(index)) -/// ); -/// } +/// // custom `Query` extractor configuration +/// let query_cfg = web::QueryConfig::default() +/// // use custom error handler +/// .error_handler(|err, req| { +/// error::InternalError::from_response(err, HttpResponse::Conflict().finish()).into() +/// }); +/// +/// App::new() +/// .app_data(query_cfg) +/// .service(index); /// ``` #[derive(Clone)] pub struct QueryConfig { - ehandler: + err_handler: Option Error + Send + Sync>>, } @@ -210,14 +177,14 @@ impl QueryConfig { where F: Fn(QueryPayloadError, &HttpRequest) -> Error + Send + Sync + 'static, { - self.ehandler = Some(Arc::new(f)); + self.err_handler = Some(Arc::new(f)); self } } impl Default for QueryConfig { fn default() -> Self { - QueryConfig { ehandler: None } + QueryConfig { err_handler: None } } } @@ -225,7 +192,7 @@ impl Default for QueryConfig { mod tests { use actix_http::http::StatusCode; use derive_more::Display; - use serde_derive::Deserialize; + use serde::Deserialize; use super::*; use crate::error::InternalError; @@ -271,6 +238,17 @@ mod tests { assert_eq!(s.id, "test1"); } + #[actix_rt::test] + #[should_panic] + async fn test_tuple_panic() { + let req = TestRequest::with_uri("/?one=1&two=2").to_srv_request(); + let (req, mut pl) = req.into_parts(); + + Query::<(u32, u32)>::from_request(&req, &mut pl) + .await + .unwrap(); + } + #[actix_rt::test] async fn test_custom_error_responder() { let req = TestRequest::with_uri("/name/user1/") diff --git a/src/types/readlines.rs b/src/types/readlines.rs index f03235377..01aab64ab 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -1,17 +1,23 @@ -use std::borrow::Cow; -use std::pin::Pin; -use std::str; -use std::task::{Context, Poll}; +//! For request line reader documentation, see [`Readlines`]. + +use std::{ + borrow::Cow, + pin::Pin, + str, + task::{Context, Poll}, +}; use bytes::{Bytes, BytesMut}; use encoding_rs::{Encoding, UTF_8}; -use futures_util::stream::Stream; +use futures_core::{ready, stream::Stream}; -use crate::dev::Payload; -use crate::error::{PayloadError, ReadlinesError}; -use crate::HttpMessage; +use crate::{ + dev::Payload, + error::{PayloadError, ReadlinesError}, + HttpMessage, +}; -/// Stream to read request line by line. +/// Stream that reads request line by line. pub struct Readlines { stream: Payload, buff: BytesMut, @@ -43,7 +49,7 @@ where } } - /// Change max line size. By default max size is 256Kb + /// Set maximum accepted payload size. The default limit is 256kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self @@ -108,9 +114,10 @@ where } this.checked_buff = true; } + // poll req for more bytes - match Pin::new(&mut this.stream).poll_next(cx) { - Poll::Ready(Some(Ok(mut bytes))) => { + match ready!(Pin::new(&mut this.stream).poll_next(cx)) { + Some(Ok(mut bytes)) => { // check if there is a newline in bytes let mut found: Option = None; for (ind, b) in bytes.iter().enumerate() { @@ -144,8 +151,8 @@ where this.buff.extend_from_slice(&bytes); Poll::Pending } - Poll::Pending => Poll::Pending, - Poll::Ready(None) => { + + None => { if this.buff.is_empty() { return Poll::Ready(None); } @@ -165,7 +172,8 @@ where this.buff.clear(); Poll::Ready(Some(Ok(line))) } - Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(ReadlinesError::from(e)))), + + Some(Err(err)) => Poll::Ready(Some(Err(ReadlinesError::from(err)))), } } } From f6cc829758e7dc51467eddcc36979c274c20abae Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 9 Jan 2021 23:40:20 +0800 Subject: [PATCH 113/187] remove leaked box in REQUEST_POOL and RESPONSE_POOL (#1896) --- actix-http/src/message.rs | 33 ++++++++++++++++++--------------- 1 file changed, 18 insertions(+), 15 deletions(-) diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 1a5500c31..bccb4d53e 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -34,7 +34,9 @@ bitflags! { pub trait Head: Default + 'static { fn clear(&mut self); - fn pool() -> &'static MessagePool; + fn with_pool(f: F) -> R + where + F: FnOnce(&MessagePool) -> R; } #[derive(Debug)] @@ -69,8 +71,11 @@ impl Head for RequestHead { self.extensions.get_mut().clear(); } - fn pool() -> &'static MessagePool { - REQUEST_POOL.with(|p| *p) + fn with_pool(f: F) -> R + where + F: FnOnce(&MessagePool) -> R, + { + REQUEST_POOL.with(|p| f(p)) } } @@ -344,7 +349,7 @@ pub struct Message { impl Message { /// Get new message from the pool of objects pub fn new() -> Self { - T::pool().get_message() + T::with_pool(|p| p.get_message()) } } @@ -373,7 +378,7 @@ impl std::ops::DerefMut for Message { impl Drop for Message { fn drop(&mut self) { if Rc::strong_count(&self.head) == 1 { - T::pool().release(self.head.clone()); + T::with_pool(|p| p.release(self.head.clone())) } } } @@ -426,18 +431,17 @@ pub struct MessagePool(RefCell>>); /// Request's objects pool pub struct BoxedResponsePool(RefCell>>); -thread_local!(static REQUEST_POOL: &'static MessagePool = MessagePool::::create()); -thread_local!(static RESPONSE_POOL: &'static BoxedResponsePool = BoxedResponsePool::create()); +thread_local!(static REQUEST_POOL: MessagePool = MessagePool::::create()); +thread_local!(static RESPONSE_POOL: BoxedResponsePool = BoxedResponsePool::create()); impl MessagePool { - fn create() -> &'static MessagePool { - let pool = MessagePool(RefCell::new(Vec::with_capacity(128))); - Box::leak(Box::new(pool)) + fn create() -> MessagePool { + MessagePool(RefCell::new(Vec::with_capacity(128))) } /// Get message from the pool #[inline] - fn get_message(&'static self) -> Message { + fn get_message(&self) -> Message { if let Some(mut msg) = self.0.borrow_mut().pop() { // Message is put in pool only when it's the last copy. // which means it's guaranteed to be unique when popped out. @@ -463,14 +467,13 @@ impl MessagePool { } impl BoxedResponsePool { - fn create() -> &'static BoxedResponsePool { - let pool = BoxedResponsePool(RefCell::new(Vec::with_capacity(128))); - Box::leak(Box::new(pool)) + fn create() -> BoxedResponsePool { + BoxedResponsePool(RefCell::new(Vec::with_capacity(128))) } /// Get message from the pool #[inline] - fn get_message(&'static self, status: StatusCode) -> BoxedResponseHead { + fn get_message(&self, status: StatusCode) -> BoxedResponseHead { if let Some(mut head) = self.0.borrow_mut().pop() { head.reason = None; head.status = status; From fe392abeb470e9bcf9aec5c8146b95426394ba79 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 10 Jan 2021 00:04:19 +0800 Subject: [PATCH 114/187] remove actix-threadpool.use actix_rt::task::spawn_blocking (#1878) --- CHANGES.md | 4 ++- Cargo.toml | 5 ++-- actix-files/Cargo.toml | 4 +-- actix-files/src/chunked.rs | 48 +++++++++++++----------------- actix-files/src/lib.rs | 11 +------ actix-http-test/Cargo.toml | 4 +-- actix-http/CHANGES.md | 5 +++- actix-http/Cargo.toml | 5 ++-- actix-http/src/encoding/decoder.rs | 17 +++++++---- actix-http/src/encoding/encoder.rs | 18 +++++++---- actix-http/src/error.rs | 18 ++++++++--- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 4 +-- src/web.rs | 5 +++- 16 files changed, 83 insertions(+), 71 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 733f28ca7..70f7705c8 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -38,13 +38,15 @@ ### Removed * Public modules `middleware::{normalize, err_handlers}`. All necessary middleware structs are now exposed directly by the `middleware` module. +* Remove `actix-threadpool` as dependency. `actix_threadpool::BlockingError` error type can be imported + from `actix_web::error` module. [#1878] [#1812]: https://github.com/actix/actix-web/pull/1812 [#1813]: https://github.com/actix/actix-web/pull/1813 [#1852]: https://github.com/actix/actix-web/pull/1852 [#1865]: https://github.com/actix/actix-web/pull/1865 [#1875]: https://github.com/actix/actix-web/pull/1875 - +[#1878]: https://github.com/actix/actix-web/pull/1878 ## 3.3.2 - 2020-12-01 ### Fixed diff --git a/Cargo.toml b/Cargo.toml index fb0a876b6..bae6cb6cb 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -76,11 +76,10 @@ required-features = ["rustls"] actix-codec = "0.4.0-beta.1" actix-macros = "0.1.0" actix-router = "0.2.4" -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" actix-server = "2.0.0-beta.2" -actix-service = "2.0.0-beta.2" +actix-service = "2.0.0-beta.3" actix-utils = "3.0.0-beta.1" -actix-threadpool = "0.3.1" actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } actix-web-codegen = "0.4.0" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f93450ff8..bde2cb717 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } -actix-service = "2.0.0-beta.2" +actix-service = "2.0.0-beta.3" bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" actix-web = "4.0.0-beta.1" diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 580b06787..5b7b17dc4 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -8,17 +8,11 @@ use std::{ }; use actix_web::{ - error::{BlockingError, Error}, - web, + error::{Error, ErrorInternalServerError}, + rt::task::{spawn_blocking, JoinHandle}, }; use bytes::Bytes; use futures_core::{ready, Stream}; -use futures_util::future::{FutureExt, LocalBoxFuture}; - -use crate::handle_error; - -type ChunkedBoxFuture = - LocalBoxFuture<'static, Result<(File, Bytes), BlockingError>>; #[doc(hidden)] /// A helper created from a `std::fs::File` which reads the file @@ -27,7 +21,7 @@ pub struct ChunkedReadFile { pub(crate) size: u64, pub(crate) offset: u64, pub(crate) file: Option, - pub(crate) fut: Option, + pub(crate) fut: Option>>, pub(crate) counter: u64, } @@ -45,18 +39,20 @@ impl Stream for ChunkedReadFile { cx: &mut Context<'_>, ) -> Poll> { if let Some(ref mut fut) = self.fut { - return match ready!(Pin::new(fut).poll(cx)) { - Ok((file, bytes)) => { + let res = match ready!(Pin::new(fut).poll(cx)) { + Ok(Ok((file, bytes))) => { self.fut.take(); self.file = Some(file); self.offset += bytes.len() as u64; self.counter += bytes.len() as u64; - Poll::Ready(Some(Ok(bytes))) + Ok(bytes) } - Err(e) => Poll::Ready(Some(Err(handle_error(e)))), + Ok(Err(e)) => Err(e.into()), + Err(_) => Err(ErrorInternalServerError("Unexpected error")), }; + return Poll::Ready(Some(res)); } let size = self.size; @@ -68,25 +64,21 @@ impl Stream for ChunkedReadFile { } else { let mut file = self.file.take().expect("Use after completion"); - self.fut = Some( - web::block(move || { - let max_bytes = - cmp::min(size.saturating_sub(counter), 65_536) as usize; + self.fut = Some(spawn_blocking(move || { + let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; + let mut buf = Vec::with_capacity(max_bytes); + file.seek(io::SeekFrom::Start(offset))?; - let n_bytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; + let n_bytes = + file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - if n_bytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } + if n_bytes == 0 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } - Ok((file, Bytes::from(buf))) - }) - .boxed_local(), - ); + Ok((file, Bytes::from(buf))) + })); self.poll_next(cx) } diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 662fba0a3..b7225fbc0 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -14,12 +14,10 @@ #![deny(rust_2018_idioms)] #![warn(missing_docs, missing_debug_implementations)] -use std::io; - use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_web::{ dev::{ServiceRequest, ServiceResponse}, - error::{BlockingError, Error, ErrorInternalServerError}, + error::Error, http::header::DispositionType, }; use mime_guess::from_ext; @@ -56,13 +54,6 @@ pub fn file_extension_to_mime(ext: &str) -> mime::Mime { from_ext(ext).first_or_octet_stream() } -pub(crate) fn handle_error(err: BlockingError) -> Error { - match err { - BlockingError::Error(err) => err.into(), - BlockingError::Canceled => ErrorInternalServerError("Unexpected error"), - } -} - type MimeOverride = dyn Fn(&mime::Name<'_>) -> DispositionType; #[cfg(test)] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index a056b833e..772b60f76 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,11 +29,11 @@ default = [] openssl = ["open-ssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0-beta.2" +actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.2" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" actix-server = "2.0.0-beta.2" awc = "3.0.0-beta.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 622ed55ea..e9a94300b 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -25,11 +25,14 @@ * Remove `ConnectError::SslHandshakeError` and re-export of `HandshakeError`. due to the removal of this type from `tokio-openssl` crate. openssl handshake error would return as `ConnectError::SslError`. [#1813] +* Remove `actix-threadpool` dependency. Use `actix_rt::task::spawn_blocking`. + Due to this change `actix_threadpool::BlockingError` type is moved into + `actix_http::error` module. [#1878] [#1813]: https://github.com/actix/actix-web/pull/1813 [#1857]: https://github.com/actix/actix-web/pull/1857 [#1864]: https://github.com/actix/actix-web/pull/1864 - +[#1878]: https://github.com/actix/actix-web/pull/1878 ## 2.2.0 - 2020-11-25 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b64c71a8a..0cc8e5cf9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -40,11 +40,10 @@ secure-cookies = ["cookie/secure"] actors = ["actix"] [dependencies] -actix-service = "2.0.0-beta.2" +actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.1" -actix-threadpool = "0.3.1" +actix-rt = "2.0.0-beta.2" actix-tls = "3.0.0-beta.2" actix = { version = "0.11.0-beta.1", optional = true } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index b60435859..b26609911 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -3,14 +3,14 @@ use std::io::{self, Write}; use std::pin::Pin; use std::task::{Context, Poll}; -use actix_threadpool::{run, CpuFuture}; +use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliDecoder; use bytes::Bytes; use flate2::write::{GzDecoder, ZlibDecoder}; use futures_core::{ready, Stream}; use super::Writer; -use crate::error::PayloadError; +use crate::error::{BlockingError, PayloadError}; use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; const INPLACE: usize = 2049; @@ -19,7 +19,7 @@ pub struct Decoder { decoder: Option, stream: S, eof: bool, - fut: Option, ContentDecoder), io::Error>>, + fut: Option, ContentDecoder), io::Error>>>, } impl Decoder @@ -80,8 +80,13 @@ where loop { if let Some(ref mut fut) = self.fut { let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { - Ok(item) => item, - Err(e) => return Poll::Ready(Some(Err(e.into()))), + Ok(Ok(item)) => item, + Ok(Err(e)) => { + return Poll::Ready(Some(Err(BlockingError::Error(e).into()))) + } + Err(_) => { + return Poll::Ready(Some(Err(BlockingError::Canceled.into()))) + } }; self.decoder = Some(decoder); self.fut.take(); @@ -105,7 +110,7 @@ where return Poll::Ready(Some(Ok(chunk))); } } else { - self.fut = Some(run(move || { + self.fut = Some(spawn_blocking(move || { let chunk = decoder.feed_data(chunk)?; Ok((chunk, decoder)) })); diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index eb1821285..28c757076 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -4,7 +4,7 @@ use std::io::{self, Write}; use std::pin::Pin; use std::task::{Context, Poll}; -use actix_threadpool::{run, CpuFuture}; +use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliEncoder; use bytes::Bytes; use flate2::write::{GzEncoder, ZlibEncoder}; @@ -17,6 +17,7 @@ use crate::http::{HeaderValue, StatusCode}; use crate::{Error, ResponseHead}; use super::Writer; +use crate::error::BlockingError; const INPLACE: usize = 1024; @@ -26,7 +27,7 @@ pub struct Encoder { #[pin] body: EncoderBody, encoder: Option, - fut: Option>, + fut: Option>>, } impl Encoder { @@ -136,8 +137,15 @@ impl MessageBody for Encoder { if let Some(ref mut fut) = this.fut { let mut encoder = match ready!(Pin::new(fut).poll(cx)) { - Ok(item) => item, - Err(e) => return Poll::Ready(Some(Err(e.into()))), + Ok(Ok(item)) => item, + Ok(Err(e)) => { + return Poll::Ready(Some(Err(BlockingError::Error(e).into()))) + } + Err(_) => { + return Poll::Ready(Some(Err( + BlockingError::::Canceled.into(), + ))) + } }; let chunk = encoder.take(); *this.encoder = Some(encoder); @@ -160,7 +168,7 @@ impl MessageBody for Encoder { return Poll::Ready(Some(Ok(chunk))); } } else { - *this.fut = Some(run(move || { + *this.fut = Some(spawn_blocking(move || { encoder.write(&chunk)?; Ok(encoder) })); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 852cf8e5c..a585962be 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -7,7 +7,6 @@ use std::string::FromUtf8Error; use std::{fmt, io, result}; use actix_codec::{Decoder, Encoder}; -pub use actix_threadpool::BlockingError; use actix_utils::dispatcher::DispatcherError as FramedDispatcherError; use actix_utils::timeout::TimeoutError; use bytes::BytesMut; @@ -186,9 +185,6 @@ impl ResponseError for DeError { /// `InternalServerError` for `Canceled` impl ResponseError for Canceled {} -/// `InternalServerError` for `BlockingError` -impl ResponseError for BlockingError {} - /// Return `BAD_REQUEST` for `Utf8Error` impl ResponseError for Utf8Error { fn status_code(&self) -> StatusCode { @@ -300,6 +296,20 @@ impl From for ParseError { } } +/// A set of errors that can occur running blocking tasks in thread pool. +#[derive(Debug, Display)] +pub enum BlockingError { + #[display(fmt = "{:?}", _0)] + Error(E), + #[display(fmt = "Thread pool is gone")] + Canceled, +} + +impl std::error::Error for BlockingError {} + +/// `InternalServerError` for `BlockingError` +impl ResponseError for BlockingError {} + #[derive(Display, Debug)] /// A set of errors that can occur during payload parsing pub enum PayloadError { diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index d22cf7ef0..44a7e8d16 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,5 +28,5 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 331363543..0f90edb07 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" env_logger = "0.7" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 25e88d9e1..00875cf1b 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index b92df8247..0dbf80d33 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,9 +38,9 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-service = "2.0.0-beta.2" +actix-service = "2.0.0-beta.3" actix-http = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.1" +actix-rt = "2.0.0-beta.2" base64 = "0.13" bytes = "1" diff --git a/src/web.rs b/src/web.rs index 39dfc450a..88071f551 100644 --- a/src/web.rs +++ b/src/web.rs @@ -280,5 +280,8 @@ where I: Send + 'static, E: Send + std::fmt::Debug + 'static, { - actix_threadpool::run(f).await + match actix_rt::task::spawn_blocking(f).await { + Ok(res) => res.map_err(BlockingError::Error), + Err(_) => Err(BlockingError::Canceled), + } } From 9e401b6ef71fda227b0eb66e19b3df8b78437f00 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 10 Jan 2021 02:06:49 +0800 Subject: [PATCH 115/187] refactor Scope (#1895) --- src/app_service.rs | 3 +- src/scope.rs | 215 +++++++++++++++------------------------------ 2 files changed, 71 insertions(+), 147 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index 442de9362..3cfd84d5c 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -62,7 +62,8 @@ where type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, config: AppConfig) -> Self::Future { - // update resource default service + // set AppService's default service to 404 NotFound + // if no user defined default service exists. let default = self.default.clone().unwrap_or_else(|| { Rc::new(boxed::factory(fn_service(|req: ServiceRequest| async { Ok(req.into_response(Response::NotFound().finish())) diff --git a/src/scope.rs b/src/scope.rs index 419e572aa..2da4f5546 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -1,18 +1,18 @@ use std::cell::RefCell; use std::fmt; use std::future::Future; -use std::pin::Pin; use std::rc::Rc; -use std::task::{Context, Poll}; +use std::task::Poll; -use actix_http::{Extensions, Response}; -use actix_router::{ResourceDef, ResourceInfo, Router}; +use actix_http::Extensions; +use actix_router::{ResourceDef, Router}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_core::future::LocalBoxFuture; +use futures_util::future::join_all; use crate::config::ServiceConfig; use crate::data::Data; @@ -61,10 +61,10 @@ type HttpNewService = BoxServiceFactory<(), ServiceRequest, ServiceResponse, Err pub struct Scope { endpoint: T, rdef: String, - data: Option, + app_data: Option, services: Vec>, guards: Vec>, - default: Rc>>>, + default: Option>, external: Vec, factory_ref: Rc>>, } @@ -76,10 +76,10 @@ impl Scope { Scope { endpoint: ScopeEndpoint::new(fref.clone()), rdef: path.to_string(), - data: None, + app_data: None, guards: Vec::new(), services: Vec::new(), - default: Rc::new(RefCell::new(None)), + default: None, external: Vec::new(), factory_ref: fref, } @@ -155,10 +155,10 @@ where /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { - if self.data.is_none() { - self.data = Some(Extensions::new()); + if self.app_data.is_none() { + self.app_data = Some(Extensions::new()); } - self.data.as_mut().unwrap().insert(data); + self.app_data.as_mut().unwrap().insert(data); self } @@ -201,15 +201,15 @@ where self.external.extend(cfg.external); if !cfg.data.is_empty() { - let mut data = self.data.unwrap_or_else(Extensions::new); + let mut data = self.app_data.unwrap_or_else(Extensions::new); for value in cfg.data.iter() { value.create(&mut data); } - self.data = Some(data); + self.app_data = Some(data); } - self.data + self.app_data .get_or_insert_with(Extensions::new) .extend(cfg.extensions); self @@ -295,11 +295,9 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = Rc::new(RefCell::new(Some(Rc::new(boxed::factory( - f.into_factory().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - }), - ))))); + self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( + |e| log::error!("Can not construct default service: {:?}", e), + )))); self } @@ -337,7 +335,7 @@ where Scope { endpoint: apply(mw, self.endpoint), rdef: self.rdef, - data: self.data, + app_data: self.app_data, guards: self.guards, services: self.services, default: self.default, @@ -397,7 +395,7 @@ where Scope { endpoint: apply_fn_factory(self.endpoint, mw), rdef: self.rdef, - data: self.data, + app_data: self.app_data, guards: self.guards, services: self.services, default: self.default, @@ -419,9 +417,7 @@ where { fn register(mut self, config: &mut AppService) { // update default resource if needed - if self.default.borrow().is_none() { - *self.default.borrow_mut() = Some(config.default_service()); - } + let default = self.default.unwrap_or_else(|| config.default_service()); // register nested services let mut cfg = config.clone_config(); @@ -437,14 +433,14 @@ where } // custom app data storage - if let Some(ref mut ext) = self.data { + if let Some(ref mut ext) = self.app_data { config.set_service_data(ext); } // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { - data: self.data.take().map(Rc::new), - default: self.default.clone(), + app_data: self.app_data.take().map(Rc::new), + default, services: cfg .into_services() .1 @@ -476,129 +472,65 @@ where } pub struct ScopeFactory { - data: Option>, + app_data: Option>, services: Rc<[(ResourceDef, HttpNewService, RefCell>)]>, - default: Rc>>>, + default: Rc, } impl ServiceFactory for ScopeFactory { - type Config = (); type Response = ServiceResponse; type Error = Error; - type InitError = (); + type Config = (); type Service = ScopeService; - type Future = ScopeFactoryResponse; + type InitError = (); + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { - let default_fut = if let Some(ref default) = *self.default.borrow() { - Some(default.new_service(())) - } else { - None - }; + // construct default service factory future + let default_fut = self.default.new_service(()); - ScopeFactoryResponse { - fut: self - .services - .iter() - .map(|(path, service, guards)| { - CreateScopeServiceItem::Future( - Some(path.clone()), - guards.borrow_mut().take(), - service.new_service(()), - ) - }) - .collect(), - default: None, - data: self.data.clone(), - default_fut, - } - } -} + // construct all services factory future with it's resource def and guards. + let factory_fut = + join_all(self.services.iter().map(|(path, factory, guards)| { + let path = path.clone(); + let guards = guards.borrow_mut().take(); + let factory_fut = factory.new_service(()); + async move { + let service = factory_fut.await?; + Ok((path, guards, service)) + } + })); -/// Create scope service -#[doc(hidden)] -#[pin_project::pin_project] -pub struct ScopeFactoryResponse { - fut: Vec, - data: Option>, - default: Option, - default_fut: Option>>, -} + let app_data = self.app_data.clone(); -type HttpServiceFut = LocalBoxFuture<'static, Result>; + Box::pin(async move { + let default = default_fut.await?; -enum CreateScopeServiceItem { - Future(Option, Option, HttpServiceFut), - Service(ResourceDef, Option, HttpService), -} - -impl Future for ScopeFactoryResponse { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let mut done = true; - - if let Some(ref mut fut) = self.default_fut { - match Pin::new(fut).poll(cx)? { - Poll::Ready(default) => self.default = Some(default), - Poll::Pending => done = false, - } - } - - // poll http services - for item in &mut self.fut { - let res = match item { - CreateScopeServiceItem::Future( - ref mut path, - ref mut guards, - ref mut fut, - ) => match Pin::new(fut).poll(cx)? { - Poll::Ready(service) => { - Some((path.take().unwrap(), guards.take(), service)) - } - Poll::Pending => { - done = false; - None - } - }, - CreateScopeServiceItem::Service(_, _, _) => continue, - }; - - if let Some((path, guards, service)) = res { - *item = CreateScopeServiceItem::Service(path, guards, service); - } - } - - if done { - let router = self - .fut + // build router from the factory future result. + let router = factory_fut + .await + .into_iter() + .collect::, _>>()? .drain(..) - .fold(Router::build(), |mut router, item| { - match item { - CreateScopeServiceItem::Service(path, guards, service) => { - router.rdef(path, service).2 = guards; - } - CreateScopeServiceItem::Future(_, _, _) => unreachable!(), - } + .fold(Router::build(), |mut router, (path, guards, service)| { + router.rdef(path, service).2 = guards; router - }); - Poll::Ready(Ok(ScopeService { - data: self.data.clone(), - router: router.finish(), - default: self.default.take(), - _ready: None, - })) - } else { - Poll::Pending - } + }) + .finish(); + + Ok(ScopeService { + app_data, + router, + default, + }) + }) } } pub struct ScopeService { - data: Option>, + app_data: Option>, router: Router>>, - default: Option, - _ready: Option<(ServiceRequest, ResourceInfo)>, + default: HttpService, } impl Service for ScopeService { @@ -606,9 +538,7 @@ impl Service for ScopeService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); fn call(&mut self, mut req: ServiceRequest) -> Self::Future { let res = self.router.recognize_mut_checked(&mut req, |req, guards| { @@ -622,21 +552,14 @@ impl Service for ScopeService { true }); + if let Some(ref app_data) = self.app_data { + req.add_data_container(app_data.clone()); + } + if let Some((srv, _info)) = res { - if let Some(ref data) = self.data { - req.add_data_container(data.clone()); - } srv.call(req) - } else if let Some(ref mut default) = self.default { - if let Some(ref data) = self.data { - req.add_data_container(data.clone()); - } - default.call(req) } else { - let req = req.into_parts().0; - Box::pin(async { - Ok(ServiceResponse::new(req, Response::NotFound().finish())) - }) + self.default.call(req) } } } @@ -658,7 +581,7 @@ impl ServiceFactory for ScopeEndpoint { type Config = (); type Service = ScopeService; type InitError = (); - type Future = ScopeFactoryResponse; + type Future = LocalBoxFuture<'static, Result>; fn new_service(&self, _: ()) -> Self::Future { self.factory.borrow_mut().as_mut().unwrap().new_service(()) From 46b2f7eaaf602d9e30e086b7da18c089564caf0d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 11 Jan 2021 06:59:44 +0800 Subject: [PATCH 116/187] use a non leak pool for HttpRequestInner (#1889) Co-authored-by: Rob Ede --- src/app_service.rs | 48 +++++++++++++++++----- src/config.rs | 12 +++--- src/request.rs | 100 +++++++++++++++++++++++++++++++++------------ src/server.rs | 36 +++++++--------- src/test.rs | 29 ++++++------- 5 files changed, 144 insertions(+), 81 deletions(-) diff --git a/src/app_service.rs b/src/app_service.rs index 3cfd84d5c..c4ac0b094 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -142,10 +142,8 @@ where Ok(AppInitService { service, - rmap, - config, app_data: Rc::new(app_data), - pool: HttpRequestPool::create(), + app_state: AppInitServiceState::new(rmap, config), }) }) } @@ -157,10 +155,42 @@ where T: Service, Error = Error>, { service: T, + app_data: Rc, + app_state: Rc, +} + +// a collection of AppInitService state that shared between HttpRequests. +pub(crate) struct AppInitServiceState { rmap: Rc, config: AppConfig, - app_data: Rc, - pool: &'static HttpRequestPool, + pool: HttpRequestPool, +} + +impl AppInitServiceState { + pub(crate) fn new(rmap: Rc, config: AppConfig) -> Rc { + Rc::new(AppInitServiceState { + rmap, + config, + // TODO: AppConfig can be used to pass user defined HttpRequestPool + // capacity. + pool: HttpRequestPool::default(), + }) + } + + #[inline] + pub(crate) fn rmap(&self) -> &ResourceMap { + &*self.rmap + } + + #[inline] + pub(crate) fn config(&self) -> &AppConfig { + &self.config + } + + #[inline] + pub(crate) fn pool(&self) -> &HttpRequestPool { + &self.pool + } } impl Service for AppInitService @@ -178,7 +208,7 @@ where fn call(&mut self, req: Request) -> Self::Future { let (head, payload) = req.into_parts(); - let req = if let Some(mut req) = self.pool.get_request() { + let req = if let Some(mut req) = self.app_state.pool().pop() { let inner = Rc::get_mut(&mut req.inner).unwrap(); inner.path.get_mut().update(&head.uri); inner.path.reset(); @@ -190,10 +220,8 @@ where Path::new(Url::new(head.uri.clone())), head, payload, - self.rmap.clone(), - self.config.clone(), + self.app_state.clone(), self.app_data.clone(), - self.pool, ) }; self.service.call(ServiceRequest::new(req)) @@ -205,7 +233,7 @@ where T: Service, Error = Error>, { fn drop(&mut self) { - self.pool.clear(); + self.app_state.pool().clear(); } } diff --git a/src/config.rs b/src/config.rs index 4ec36952a..2b93ae892 100644 --- a/src/config.rs +++ b/src/config.rs @@ -125,9 +125,7 @@ impl AppService { /// Application connection config #[derive(Clone)] -pub struct AppConfig(Rc); - -struct AppConfigInner { +pub struct AppConfig { secure: bool, host: String, addr: SocketAddr, @@ -135,7 +133,7 @@ struct AppConfigInner { impl AppConfig { pub(crate) fn new(secure: bool, addr: SocketAddr, host: String) -> Self { - AppConfig(Rc::new(AppConfigInner { secure, addr, host })) + AppConfig { secure, addr, host } } /// Server host name. @@ -146,17 +144,17 @@ impl AppConfig { /// /// By default host name is set to a "localhost" value. pub fn host(&self) -> &str { - &self.0.host + &self.host } /// Returns true if connection is secure(https) pub fn secure(&self) -> bool { - self.0.secure + self.secure } /// Returns the socket address of the local half of this TCP connection pub fn local_addr(&self) -> SocketAddr { - self.0.addr + self.addr } } diff --git a/src/request.rs b/src/request.rs index f8160ae47..437d07b6e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -8,6 +8,7 @@ use actix_router::{Path, Url}; use futures_util::future::{ok, Ready}; use smallvec::SmallVec; +use crate::app_service::AppInitServiceState; use crate::config::AppConfig; use crate::error::UrlGenerationError; use crate::extract::FromRequest; @@ -29,9 +30,7 @@ pub(crate) struct HttpRequestInner { pub(crate) path: Path, pub(crate) payload: Payload, pub(crate) app_data: SmallVec<[Rc; 4]>, - rmap: Rc, - config: AppConfig, - pool: &'static HttpRequestPool, + app_state: Rc, } impl HttpRequest { @@ -40,10 +39,8 @@ impl HttpRequest { path: Path, head: Message, payload: Payload, - rmap: Rc, - config: AppConfig, + app_state: Rc, app_data: Rc, - pool: &'static HttpRequestPool, ) -> HttpRequest { let mut data = SmallVec::<[Rc; 4]>::new(); data.push(app_data); @@ -53,10 +50,8 @@ impl HttpRequest { head, path, payload, - rmap, - config, + app_state, app_data: data, - pool, }), } } @@ -142,7 +137,7 @@ impl HttpRequest { /// Returns a None when no resource is fully matched, including default services. #[inline] pub fn match_pattern(&self) -> Option { - self.inner.rmap.match_pattern(self.path()) + self.resource_map().match_pattern(self.path()) } /// The resource name that matched the path. Useful for logging and metrics. @@ -150,7 +145,7 @@ impl HttpRequest { /// Returns a None when no resource is fully matched, including default services. #[inline] pub fn match_name(&self) -> Option<&str> { - self.inner.rmap.match_name(self.path()) + self.resource_map().match_name(self.path()) } /// Request extensions @@ -192,7 +187,7 @@ impl HttpRequest { U: IntoIterator, I: AsRef, { - self.inner.rmap.url_for(&self, name, elements) + self.resource_map().url_for(&self, name, elements) } /// Generate url for named resource @@ -207,7 +202,7 @@ impl HttpRequest { #[inline] /// Get a reference to a `ResourceMap` of current application. pub fn resource_map(&self) -> &ResourceMap { - &self.inner.rmap + &self.app_state().rmap() } /// Peer socket address @@ -227,13 +222,13 @@ impl HttpRequest { /// borrowed. #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { - ConnectionInfo::get(self.head(), &*self.app_config()) + ConnectionInfo::get(self.head(), self.app_config()) } /// App config #[inline] pub fn app_config(&self) -> &AppConfig { - &self.inner.config + self.app_state().config() } /// Get an application data object stored with `App::data` or `App::app_data` @@ -253,6 +248,11 @@ impl HttpRequest { None } + + #[inline] + fn app_state(&self) -> &AppInitServiceState { + &*self.inner.app_state + } } impl HttpMessage for HttpRequest { @@ -288,14 +288,16 @@ impl Drop for HttpRequest { // This relies on no Weak exists anywhere.(There is none) if let Some(inner) = Rc::get_mut(&mut self.inner) { - let v = &mut inner.pool.0.borrow_mut(); - if v.len() < 128 { + if inner.app_state.pool().is_available() { // clear additional app_data and keep the root one for reuse. inner.app_data.truncate(1); // inner is borrowed mut here. get head's Extension mutably // to reduce borrow check inner.head.extensions.get_mut().clear(); - v.push(self.inner.clone()); + + // a re-borrow of pool is necessary here. + let req = self.inner.clone(); + self.app_state().pool().push(req); } } } @@ -364,25 +366,50 @@ impl fmt::Debug for HttpRequest { /// Request objects are added when they are dropped (see `::drop`) and re-used /// in `::call` when there are available objects in the list. /// -/// The pool's initial capacity is 128 items. -pub(crate) struct HttpRequestPool(RefCell>>); +/// The pool's default capacity is 128 items. +pub(crate) struct HttpRequestPool { + inner: RefCell>>, + cap: usize, +} + +impl Default for HttpRequestPool { + fn default() -> Self { + Self::with_capacity(128) + } +} impl HttpRequestPool { - /// Allocates a slab of memory for pool use. - pub(crate) fn create() -> &'static HttpRequestPool { - let pool = HttpRequestPool(RefCell::new(Vec::with_capacity(128))); - Box::leak(Box::new(pool)) + pub(crate) fn with_capacity(cap: usize) -> Self { + HttpRequestPool { + inner: RefCell::new(Vec::with_capacity(cap)), + cap, + } } /// Re-use a previously allocated (but now completed/discarded) HttpRequest object. #[inline] - pub(crate) fn get_request(&self) -> Option { - self.0.borrow_mut().pop().map(|inner| HttpRequest { inner }) + pub(crate) fn pop(&self) -> Option { + self.inner + .borrow_mut() + .pop() + .map(|inner| HttpRequest { inner }) + } + + /// Check if the pool still has capacity for request storage. + #[inline] + pub(crate) fn is_available(&self) -> bool { + self.inner.borrow_mut().len() < self.cap + } + + /// Push a request to pool. + #[inline] + pub(crate) fn push(&self, req: Rc) { + self.inner.borrow_mut().push(req); } /// Clears all allocated HttpRequest objects. pub(crate) fn clear(&self) { - self.0.borrow_mut().clear() + self.inner.borrow_mut().clear() } } @@ -528,6 +555,25 @@ mod tests { ); } + #[actix_rt::test] + async fn test_drop_http_request_pool() { + let mut srv = init_service(App::new().service(web::resource("/").to( + |req: HttpRequest| { + HttpResponse::Ok() + .set_header("pool_cap", req.app_state().pool().cap) + .finish() + }, + ))) + .await; + + let req = TestRequest::default().to_request(); + let resp = call_service(&mut srv, req).await; + + drop(srv); + + assert_eq!(resp.headers().get("pool_cap").unwrap(), "128"); + } + #[actix_rt::test] async fn test_data() { let mut srv = init_service(App::new().app_data(10usize).service( diff --git a/src/server.rs b/src/server.rs index 26089ccba..8bfb27b77 100644 --- a/src/server.rs +++ b/src/server.rs @@ -283,11 +283,7 @@ where lst, move || { let c = cfg.lock().unwrap(); - let cfg = AppConfig::new( - false, - addr, - c.host.clone().unwrap_or_else(|| format!("{}", addr)), - ); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); let svc = HttpService::build() .keep_alive(c.keep_alive) @@ -302,8 +298,10 @@ where svc }; - svc.finish(map_config(factory(), move |_| cfg.clone())) - .tcp() + svc.finish(map_config(factory(), move |_| { + AppConfig::new(false, addr, host.clone()) + })) + .tcp() }, )?; Ok(self) @@ -342,11 +340,7 @@ where lst, move || { let c = cfg.lock().unwrap(); - let cfg = AppConfig::new( - true, - addr, - c.host.clone().unwrap_or_else(|| format!("{}", addr)), - ); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); let svc = HttpService::build() .keep_alive(c.keep_alive) @@ -361,8 +355,10 @@ where svc }; - svc.finish(map_config(factory(), move |_| cfg.clone())) - .openssl(acceptor.clone()) + svc.finish(map_config(factory(), move |_| { + AppConfig::new(true, addr, host.clone()) + })) + .openssl(acceptor.clone()) }, )?; Ok(self) @@ -401,11 +397,7 @@ where lst, move || { let c = cfg.lock().unwrap(); - let cfg = AppConfig::new( - true, - addr, - c.host.clone().unwrap_or_else(|| format!("{}", addr)), - ); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); let svc = HttpService::build() .keep_alive(c.keep_alive) @@ -420,8 +412,10 @@ where svc }; - svc.finish(map_config(factory(), move |_| cfg.clone())) - .rustls(config.clone()) + svc.finish(map_config(factory(), move |_| { + AppConfig::new(true, addr, host.clone()) + })) + .rustls(config.clone()) }, )?; Ok(self) diff --git a/src/test.rs b/src/test.rs index a76bae6a6..271ed4505 100644 --- a/src/test.rs +++ b/src/test.rs @@ -27,10 +27,10 @@ use socket2::{Domain, Protocol, Socket, Type}; pub use actix_http::test::TestBuffer; +use crate::app_service::AppInitServiceState; use crate::config::AppConfig; use crate::data::Data; use crate::dev::{Body, MessageBody, Payload, Server}; -use crate::request::HttpRequestPool; use crate::rmap::ResourceMap; use crate::service::{ServiceRequest, ServiceResponse}; use crate::{Error, HttpRequest, HttpResponse}; @@ -542,14 +542,15 @@ impl TestRequest { head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); + let app_state = + AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + ServiceRequest::new(HttpRequest::new( self.path, head, payload, - Rc::new(self.rmap), - self.config.clone(), + app_state, Rc::new(self.app_data), - HttpRequestPool::create(), )) } @@ -564,15 +565,10 @@ impl TestRequest { head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); - HttpRequest::new( - self.path, - head, - payload, - Rc::new(self.rmap), - self.config.clone(), - Rc::new(self.app_data), - HttpRequestPool::create(), - ) + let app_state = + AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + + HttpRequest::new(self.path, head, payload, app_state, Rc::new(self.app_data)) } /// Complete request creation and generate `HttpRequest` and `Payload` instances @@ -581,14 +577,15 @@ impl TestRequest { head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); + let app_state = + AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + let req = HttpRequest::new( self.path, head, Payload::None, - Rc::new(self.rmap), - self.config.clone(), + app_state, Rc::new(self.app_data), - HttpRequestPool::create(), ); (req, payload) From 7affc6878e5d9346bf3f632c8594694b6be3d539 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 11 Jan 2021 08:13:56 +0800 Subject: [PATCH 117/187] simplify h1 dispatcher (#1899) Co-authored-by: Rob Ede --- actix-http/src/h1/dispatcher.rs | 53 +++++++++------------------------ 1 file changed, 14 insertions(+), 39 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8ef96fbef..feea7f34a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -287,42 +287,35 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - if self.write_buf.is_empty() { + let len = self.write_buf.len(); + if len == 0 { return Ok(false); } - let len = self.write_buf.len(); - let mut written = 0; let InnerDispatcherProj { io, write_buf, .. } = self.project(); let mut io = Pin::new(io.as_mut().unwrap()); + + let mut written = 0; while written < len { match io.as_mut().poll_write(cx, &write_buf[written..]) { Poll::Ready(Ok(0)) => { return Err(DispatchError::Io(io::Error::new( io::ErrorKind::WriteZero, "", - ))); - } - Poll::Ready(Ok(n)) => { - written += n; + ))) } + Poll::Ready(Ok(n)) => written += n, Poll::Pending => { - if written > 0 { - write_buf.advance(written); - } + write_buf.advance(written); return Ok(true); } Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), } } - if written == write_buf.len() { - // SAFETY: setting length to 0 is safe - // skips one length check vs truncate - unsafe { write_buf.set_len(0) } - } else { - write_buf.advance(written); - } + // SAFETY: setting length to 0 is safe + // skips one length check vs truncate + unsafe { write_buf.set_len(0) } Ok(false) } @@ -766,19 +759,12 @@ where } else { // flush buffer inner.as_mut().poll_flush(cx)?; - if !inner.write_buf.is_empty() || inner.io.is_none() { + if !inner.write_buf.is_empty() { Poll::Pending } else { - match Pin::new(inner.project().io) - .as_pin_mut() - .unwrap() + Pin::new(inner.project().io.as_mut().unwrap()) .poll_shutdown(cx) - { - Poll::Ready(res) => { - Poll::Ready(res.map_err(DispatchError::from)) - } - Poll::Pending => Poll::Pending, - } + .map_err(DispatchError::from) } } } else { @@ -920,7 +906,7 @@ where buf.reserve(HW_BUFFER_SIZE - remaining); } - match read(cx, io, buf) { + match actix_codec::poll_read_buf(Pin::new(io), cx, buf) { Poll::Pending => { return if read_some { Ok(Some(false)) } else { Ok(None) }; } @@ -948,17 +934,6 @@ where } } -fn read( - cx: &mut Context<'_>, - io: &mut T, - buf: &mut BytesMut, -) -> Poll> -where - T: AsyncRead + Unpin, -{ - actix_codec::poll_read_buf(Pin::new(io), cx, buf) -} - #[cfg(test)] mod tests { use std::str; From 57398c6df190ab0149070f0eaacd735720d12c1c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Mon, 11 Jan 2021 09:29:16 +0800 Subject: [PATCH 118/187] Refactor/service request (#1893) --- CHANGES.md | 4 ++ src/app_service.rs | 10 ++--- src/request.rs | 3 -- src/service.rs | 99 +++++++++++++++------------------------------- src/test.rs | 21 +++------- 5 files changed, 45 insertions(+), 92 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 70f7705c8..00608df76 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -8,6 +8,8 @@ ### Changed * Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. Making it more simple and performant. [#1891] +* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. + `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] ### Removed @@ -15,8 +17,10 @@ * Public field of `web::Query` has been made private. [#1894] [#1891]: https://github.com/actix/actix-web/pull/1891 +[#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 + ## 4.0.0-beta.1 - 2021-01-07 ### Added * `Compat` middleware enabling generic response body/error type of middlewares like `Logger` and diff --git a/src/app_service.rs b/src/app_service.rs index c4ac0b094..8169be517 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -1,6 +1,6 @@ use std::cell::RefCell; use std::rc::Rc; -use std::task::{Context, Poll}; +use std::task::Poll; use actix_http::{Extensions, Request, Response}; use actix_router::{Path, ResourceDef, Router, Url}; @@ -201,9 +201,7 @@ where type Error = T::Error; type Future = T::Future; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.service.poll_ready(cx) - } + actix_service::forward_ready!(service); fn call(&mut self, req: Request) -> Self::Future { let (head, payload) = req.into_parts(); @@ -213,18 +211,16 @@ where inner.path.get_mut().update(&head.uri); inner.path.reset(); inner.head = head; - inner.payload = payload; req } else { HttpRequest::new( Path::new(Url::new(head.uri.clone())), head, - payload, self.app_state.clone(), self.app_data.clone(), ) }; - self.service.call(ServiceRequest::new(req)) + self.service.call(ServiceRequest::new(req, payload)) } } diff --git a/src/request.rs b/src/request.rs index 437d07b6e..c0e26006c 100644 --- a/src/request.rs +++ b/src/request.rs @@ -28,7 +28,6 @@ pub struct HttpRequest { pub(crate) struct HttpRequestInner { pub(crate) head: Message, pub(crate) path: Path, - pub(crate) payload: Payload, pub(crate) app_data: SmallVec<[Rc; 4]>, app_state: Rc, } @@ -38,7 +37,6 @@ impl HttpRequest { pub(crate) fn new( path: Path, head: Message, - payload: Payload, app_state: Rc, app_data: Rc, ) -> HttpRequest { @@ -49,7 +47,6 @@ impl HttpRequest { inner: Rc::new(HttpRequestInner { head, path, - payload, app_state, app_data: data, }), diff --git a/src/service.rs b/src/service.rs index c6a961efc..668b7d1b4 100644 --- a/src/service.rs +++ b/src/service.rs @@ -52,75 +52,61 @@ where /// An service http request /// /// ServiceRequest allows mutable access to request's internal structures -pub struct ServiceRequest(HttpRequest); +pub struct ServiceRequest { + req: HttpRequest, + payload: Payload, +} impl ServiceRequest { /// Construct service request - pub(crate) fn new(req: HttpRequest) -> Self { - ServiceRequest(req) + pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self { + Self { req, payload } } /// Deconstruct request into parts - pub fn into_parts(mut self) -> (HttpRequest, Payload) { - let pl = Rc::get_mut(&mut (self.0).inner).unwrap().payload.take(); - (self.0, pl) + #[inline] + pub fn into_parts(self) -> (HttpRequest, Payload) { + (self.req, self.payload) } /// Construct request from parts. - /// - /// `ServiceRequest` can be re-constructed only if `req` hasn't been cloned. - pub fn from_parts( - mut req: HttpRequest, - pl: Payload, - ) -> Result { - match Rc::get_mut(&mut req.inner) { - Some(p) => { - p.payload = pl; - Ok(ServiceRequest(req)) - } - None => Err((req, pl)), - } + pub fn from_parts(req: HttpRequest, payload: Payload) -> Self { + Self { req, payload } } /// Construct request from request. /// - /// `HttpRequest` implements `Clone` trait via `Rc` type. `ServiceRequest` - /// can be re-constructed only if rc's strong pointers count eq 1 and - /// weak pointers count is 0. - pub fn from_request(req: HttpRequest) -> Result { - // There is no weak pointer used on HttpRequest so intentionally - // ignore the check. - if Rc::strong_count(&req.inner) == 1 { - debug_assert!(Rc::weak_count(&req.inner) == 0); - Ok(ServiceRequest(req)) - } else { - Err(req) + /// The returned `ServiceRequest` would have no payload. + pub fn from_request(req: HttpRequest) -> Self { + ServiceRequest { + req, + payload: Payload::None, } } /// Create service response #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { - ServiceResponse::new(self.0, res.into()) + ServiceResponse::new(self.req, res.into()) } /// Create service response for error #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { let res: Response = err.into().into(); - ServiceResponse::new(self.0, res.into_body()) + ServiceResponse::new(self.req, res.into_body()) } /// This method returns reference to the request head #[inline] pub fn head(&self) -> &RequestHead { - &self.0.head() + &self.req.head() } /// This method returns reference to the request head #[inline] pub fn head_mut(&mut self) -> &mut RequestHead { - self.0.head_mut() + self.req.head_mut() } /// Request's uri. @@ -196,42 +182,42 @@ impl ServiceRequest { /// access the matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { - self.0.match_info() + self.req.match_info() } /// Counterpart to [`HttpRequest::match_name`](super::HttpRequest::match_name()). #[inline] pub fn match_name(&self) -> Option<&str> { - self.0.match_name() + self.req.match_name() } /// Counterpart to [`HttpRequest::match_pattern`](super::HttpRequest::match_pattern()). #[inline] pub fn match_pattern(&self) -> Option { - self.0.match_pattern() + self.req.match_pattern() } #[inline] /// Get a mutable reference to the Path parameters. pub fn match_info_mut(&mut self) -> &mut Path { - self.0.match_info_mut() + self.req.match_info_mut() } #[inline] /// Get a reference to a `ResourceMap` of current application. pub fn resource_map(&self) -> &ResourceMap { - self.0.resource_map() + self.req.resource_map() } /// Service configuration #[inline] pub fn app_config(&self) -> &AppConfig { - self.0.app_config() + self.req.app_config() } /// Counterpart to [`HttpRequest::app_data`](super::HttpRequest::app_data()). pub fn app_data(&self) -> Option<&T> { - for container in (self.0).inner.app_data.iter().rev() { + for container in self.req.inner.app_data.iter().rev() { if let Some(data) = container.get::() { return Some(data); } @@ -242,13 +228,13 @@ impl ServiceRequest { /// Set request payload. pub fn set_payload(&mut self, payload: Payload) { - Rc::get_mut(&mut (self.0).inner).unwrap().payload = payload; + self.payload = payload; } #[doc(hidden)] /// Add app data container to request's resolution set. pub fn add_data_container(&mut self, extensions: Rc) { - Rc::get_mut(&mut (self.0).inner) + Rc::get_mut(&mut (self.req).inner) .unwrap() .app_data .push(extensions); @@ -273,18 +259,18 @@ impl HttpMessage for ServiceRequest { /// Request extensions #[inline] fn extensions(&self) -> Ref<'_, Extensions> { - self.0.extensions() + self.req.extensions() } /// Mutable reference to a the request's extensions #[inline] fn extensions_mut(&self) -> RefMut<'_, Extensions> { - self.0.extensions_mut() + self.req.extensions_mut() } #[inline] fn take_payload(&mut self) -> Payload { - Rc::get_mut(&mut (self.0).inner).unwrap().payload.take() + self.payload.take() } } @@ -552,27 +538,6 @@ mod tests { use actix_service::Service; use futures_util::future::ok; - #[test] - fn test_service_request() { - let req = TestRequest::default().to_srv_request(); - let (r, pl) = req.into_parts(); - assert!(ServiceRequest::from_parts(r, pl).is_ok()); - - let req = TestRequest::default().to_srv_request(); - let (r, pl) = req.into_parts(); - let _r2 = r.clone(); - assert!(ServiceRequest::from_parts(r, pl).is_err()); - - let req = TestRequest::default().to_srv_request(); - let (r, _pl) = req.into_parts(); - assert!(ServiceRequest::from_request(r).is_ok()); - - let req = TestRequest::default().to_srv_request(); - let (r, _pl) = req.into_parts(); - let _r2 = r.clone(); - assert!(ServiceRequest::from_request(r).is_err()); - } - #[actix_rt::test] async fn test_service() { let mut srv = init_service( diff --git a/src/test.rs b/src/test.rs index 271ed4505..f8b789d1b 100644 --- a/src/test.rs +++ b/src/test.rs @@ -545,13 +545,10 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - ServiceRequest::new(HttpRequest::new( - self.path, - head, + ServiceRequest::new( + HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)), payload, - app_state, - Rc::new(self.app_data), - )) + ) } /// Complete request creation and generate `ServiceResponse` instance @@ -561,14 +558,14 @@ impl TestRequest { /// Complete request creation and generate `HttpRequest` instance pub fn to_http_request(mut self) -> HttpRequest { - let (mut head, payload) = self.req.finish().into_parts(); + let (mut head, _) = self.req.finish().into_parts(); head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - HttpRequest::new(self.path, head, payload, app_state, Rc::new(self.app_data)) + HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)) } /// Complete request creation and generate `HttpRequest` and `Payload` instances @@ -580,13 +577,7 @@ impl TestRequest { let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); - let req = HttpRequest::new( - self.path, - head, - Payload::None, - app_state, - Rc::new(self.app_data), - ); + let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)); (req, payload) } From 46a8f28b7452c05082aecc55e52745fa420af75b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 12 Jan 2021 01:27:33 +0800 Subject: [PATCH 119/187] fix actix-files doc about thread pool (#1898) --- actix-files/src/files.rs | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 98dd26880..0cf0a91ba 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -82,8 +82,9 @@ impl Files { /// be inaccessible. Register more specific handlers and services first. /// /// `Files` uses a threadpool for blocking filesystem operations. By default, the pool uses a - /// number of threads equal to 5x the number of available logical CPUs. Pool size can be changed - /// by setting ACTIX_THREADPOOL environment variable. + /// max number of threads equal to `512 * HttpServer::worker`. Real time thread count are + /// adjusted with work load. More threads would spawn when need and threads goes idle for a + /// period of time would be de-spawned. pub fn new>(mount_path: &str, serve_from: T) -> Files { let orig_dir = serve_from.into(); let dir = match orig_dir.canonicalize() { From a919d2de5607552c532d7cfdd038aa765a7fc4cd Mon Sep 17 00:00:00 2001 From: Robin Schoonover Date: Mon, 11 Jan 2021 11:18:23 -0700 Subject: [PATCH 120/187] actix-files: Fix If-(Un)Modified to not consider sub-seconds (#1887) --- actix-files/CHANGES.md | 2 ++ actix-files/src/lib.rs | 36 ++++++++++++++++++++++++++++++++++++ actix-files/src/named.rs | 4 ++-- 3 files changed, 40 insertions(+), 2 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index ff8ccd640..9f606dfcd 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,7 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +* Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +[#1887]: https://github.com/actix/actix-web/pull/1887 ## 0.6.0-beta.1 - 2021-01-07 * `HttpRange::parse` now has its own error type. diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index b7225fbc0..f4314f6bc 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -107,6 +107,18 @@ mod tests { assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); } + #[actix_rt::test] + async fn test_if_modified_since_without_if_none_match_same() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = file.last_modified().unwrap(); + + let req = TestRequest::default() + .header(header::IF_MODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); + } + #[actix_rt::test] async fn test_if_modified_since_with_if_none_match() { let file = NamedFile::open("Cargo.toml").unwrap(); @@ -121,6 +133,30 @@ mod tests { assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); } + #[actix_rt::test] + async fn test_if_unmodified_since() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = file.last_modified().unwrap(); + + let req = TestRequest::default() + .header(header::IF_UNMODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); + } + + #[actix_rt::test] + async fn test_if_unmodified_since_failed() { + let file = NamedFile::open("Cargo.toml").unwrap(); + let since = header::HttpDate::from(SystemTime::UNIX_EPOCH); + + let req = TestRequest::default() + .header(header::IF_UNMODIFIED_SINCE, since) + .to_http_request(); + let resp = file.respond_to(&req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED); + } + #[actix_rt::test] async fn test_named_file_text() { assert!(NamedFile::open("test--").is_err()); diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index b3c247b1f..dc461e29a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -331,7 +331,7 @@ impl NamedFile { let t2: SystemTime = since.clone().into(); match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { - (Ok(t1), Ok(t2)) => t1 > t2, + (Ok(t1), Ok(t2)) => t1.as_secs() > t2.as_secs(), _ => false, } } else { @@ -350,7 +350,7 @@ impl NamedFile { let t2: SystemTime = since.clone().into(); match (t1.duration_since(UNIX_EPOCH), t2.duration_since(UNIX_EPOCH)) { - (Ok(t1), Ok(t2)) => t1 <= t2, + (Ok(t1), Ok(t2)) => t1.as_secs() <= t2.as_secs(), _ => false, } } else { From d34a8689e55dac141b43ab30430404aba5d51ad0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 12 Jan 2021 22:38:53 +0800 Subject: [PATCH 121/187] Refactor h1 encoder (#1900) --- actix-http/src/h1/encoder.rs | 48 +++++++++++++++++++++--------------- 1 file changed, 28 insertions(+), 20 deletions(-) diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 4427174ec..bb89905fb 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -8,7 +8,7 @@ use bytes::{BufMut, BytesMut}; use crate::body::BodySize; use crate::config::ServiceConfig; -use crate::header::map; +use crate::header::{map::Value, HeaderName}; use crate::helpers; use crate::http::header::{CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING}; use crate::http::{HeaderMap, StatusCode, Version}; @@ -121,21 +121,11 @@ pub(crate) trait MessageType: Sized { _ => {} } - // merging headers from head and extra headers. HeaderMap::new() does not allocate. - let empty_headers = HeaderMap::new(); - let extra_headers = self.extra_headers().unwrap_or(&empty_headers); - let headers = self - .headers() - .inner - .iter() - .filter(|(name, _)| !extra_headers.contains_key(*name)) - .chain(extra_headers.inner.iter()); - // write headers let mut has_date = false; - let mut buf = dst.chunk_mut().as_mut_ptr() as *mut u8; + let mut buf = dst.chunk_mut().as_mut_ptr(); let mut remaining = dst.capacity() - dst.len(); // tracks bytes written since last buffer resize @@ -143,10 +133,10 @@ pub(crate) trait MessageType: Sized { // container's knowledge, this is used to sync the containers cursor after data is written let mut pos = 0; - for (key, value) in headers { + self.write_headers(|key, value| { match *key { - CONNECTION => continue, - TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => continue, + CONNECTION => return, + TRANSFER_ENCODING | CONTENT_LENGTH if skip_len => return, DATE => has_date = true, _ => {} } @@ -155,7 +145,7 @@ pub(crate) trait MessageType: Sized { let k_len = k.len(); match value { - map::Value::One(ref val) => { + Value::One(ref val) => { let v = val.as_ref(); let v_len = v.len(); @@ -177,7 +167,7 @@ pub(crate) trait MessageType: Sized { // re-assign buf raw pointer since it's possible that the buffer was // reallocated and/or resized - buf = dst.chunk_mut().as_mut_ptr() as *mut u8; + buf = dst.chunk_mut().as_mut_ptr(); } // SAFETY: on each write, it is enough to ensure that the advancement of the @@ -206,7 +196,7 @@ pub(crate) trait MessageType: Sized { remaining -= len; } - map::Value::Multi(ref vec) => { + Value::Multi(ref vec) => { for val in vec { let v = val.as_ref(); let v_len = v.len(); @@ -224,7 +214,7 @@ pub(crate) trait MessageType: Sized { // re-assign buf raw pointer since it's possible that the buffer was // reallocated and/or resized - buf = dst.chunk_mut().as_mut_ptr() as *mut u8; + buf = dst.chunk_mut().as_mut_ptr(); } // SAFETY: on each write, it is enough to ensure that the advancement of @@ -253,7 +243,7 @@ pub(crate) trait MessageType: Sized { } } } - } + }); // final cursor synchronization with the bytes container // @@ -273,6 +263,24 @@ pub(crate) trait MessageType: Sized { Ok(()) } + + fn write_headers(&mut self, mut f: F) + where + F: FnMut(&HeaderName, &Value), + { + match self.extra_headers() { + Some(headers) => { + // merging headers from head and extra headers. + self.headers() + .inner + .iter() + .filter(|(name, _)| !headers.contains_key(*name)) + .chain(headers.inner.iter()) + .for_each(|(k, v)| f(k, v)) + } + None => self.headers().inner.iter().for_each(|(k, v)| f(k, v)), + } + } } impl MessageType for Response<()> { From 4edeb5ce47280d4daf1352faefc832420ecf409b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 14 Jan 2021 09:43:44 +0800 Subject: [PATCH 122/187] optimize ErrorHandler middleware (#1902) --- src/middleware/err_handlers.rs | 77 ++++++++++++++++++++++++---------- 1 file changed, 55 insertions(+), 22 deletions(-) diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index dfd9a7dc5..44962aa98 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -1,10 +1,15 @@ //! For middleware documentation, see [`ErrorHandlers`]. -use std::rc::Rc; +use std::{ + future::Future, + pin::Pin, + rc::Rc, + task::{Context, Poll}, +}; use actix_service::{Service, Transform}; use ahash::AHashMap; -use futures_util::future::{ok, FutureExt, LocalBoxFuture, Ready}; +use futures_core::{future::LocalBoxFuture, ready}; use crate::{ dev::{ServiceRequest, ServiceResponse}, @@ -51,9 +56,11 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result { - handlers: Rc>>>, + handlers: Handlers, } +type Handlers = Rc>>>; + impl Default for ErrorHandlers { fn default() -> Self { ErrorHandlers { @@ -82,7 +89,7 @@ impl ErrorHandlers { impl Transform for ErrorHandlers where - S: Service, Error = Error>, + S: Service, Error = Error> + 'static, S::Future: 'static, B: 'static, { @@ -90,20 +97,18 @@ where type Error = Error; type Transform = ErrorHandlersMiddleware; type InitError = (); - type Future = Ready>; + type Future = LocalBoxFuture<'static, Result>; fn new_transform(&self, service: S) -> Self::Future { - ok(ErrorHandlersMiddleware { - service, - handlers: self.handlers.clone(), - }) + let handlers = self.handlers.clone(); + Box::pin(async move { Ok(ErrorHandlersMiddleware { service, handlers }) }) } } #[doc(hidden)] pub struct ErrorHandlersMiddleware { service: S, - handlers: Rc>>>, + handlers: Handlers, } impl Service for ErrorHandlersMiddleware @@ -114,35 +119,63 @@ where { type Response = ServiceResponse; type Error = Error; - type Future = LocalBoxFuture<'static, Result>; + type Future = ErrorHandlersFuture; actix_service::forward_ready!(service); fn call(&mut self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); let fut = self.service.call(req); + ErrorHandlersFuture::ServiceFuture { fut, handlers } + } +} - async move { - let res = fut.await?; +#[pin_project::pin_project(project = ErrorHandlersProj)] +pub enum ErrorHandlersFuture +where + Fut: Future, +{ + ServiceFuture { + #[pin] + fut: Fut, + handlers: Handlers, + }, + HandlerFuture { + fut: LocalBoxFuture<'static, Fut::Output>, + }, +} - if let Some(handler) = handlers.get(&res.status()) { - match handler(res) { - Ok(ErrorHandlerResponse::Response(res)) => Ok(res), - Ok(ErrorHandlerResponse::Future(fut)) => fut.await, - Err(e) => Err(e), +impl Future for ErrorHandlersFuture +where + Fut: Future, Error>>, +{ + type Output = Fut::Output; + + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + match self.as_mut().project() { + ErrorHandlersProj::ServiceFuture { fut, handlers } => { + let res = ready!(fut.poll(cx))?; + match handlers.get(&res.status()) { + Some(handler) => match handler(res)? { + ErrorHandlerResponse::Response(res) => Poll::Ready(Ok(res)), + ErrorHandlerResponse::Future(fut) => { + self.as_mut() + .set(ErrorHandlersFuture::HandlerFuture { fut }); + self.poll(cx) + } + }, + None => Poll::Ready(Ok(res)), } - } else { - Ok(res) } + ErrorHandlersProj::HandlerFuture { fut } => fut.as_mut().poll(cx), } - .boxed_local() } } #[cfg(test)] mod tests { use actix_service::IntoService; - use futures_util::future::ok; + use futures_util::future::{ok, FutureExt}; use super::*; use crate::http::{header::CONTENT_TYPE, HeaderValue, StatusCode}; From b1dd8d28bc704b9bcee7997fc0454f9123daf31e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 15 Jan 2021 02:11:10 +0000 Subject: [PATCH 123/187] response header rework (#1869) --- CHANGES.md | 2 + actix-files/src/lib.rs | 28 +- actix-files/src/named.rs | 31 +- actix-files/src/service.rs | 4 +- actix-http/CHANGES.md | 21 +- actix-http/Cargo.toml | 3 +- actix-http/examples/echo.rs | 5 +- actix-http/examples/echo2.rs | 2 +- actix-http/examples/hello-world.rs | 5 +- actix-http/src/client/h1proto.rs | 2 +- actix-http/src/client/pool.rs | 6 +- actix-http/src/extensions.rs | 12 +- actix-http/src/header/common/accept.rs | 29 +- .../src/header/common/accept_charset.rs | 23 +- .../src/header/common/accept_language.rs | 20 +- actix-http/src/header/common/allow.rs | 22 +- actix-http/src/header/common/cache_control.rs | 31 +- .../src/header/common/content_disposition.rs | 2 +- .../src/header/common/content_encoding.rs | 64 +++ .../src/header/common/content_language.rs | 25 +- actix-http/src/header/common/content_range.rs | 2 +- actix-http/src/header/common/content_type.rs | 18 +- actix-http/src/header/common/date.rs | 8 +- actix-http/src/header/common/etag.rs | 12 +- actix-http/src/header/common/expires.rs | 8 +- actix-http/src/header/common/if_match.rs | 8 +- .../src/header/common/if_modified_since.rs | 8 +- actix-http/src/header/common/if_none_match.rs | 18 +- actix-http/src/header/common/if_range.rs | 30 +- .../src/header/common/if_unmodified_since.rs | 8 +- actix-http/src/header/common/last_modified.rs | 8 +- actix-http/src/header/common/mod.rs | 18 +- actix-http/src/header/into_pair.rs | 117 +++++ actix-http/src/header/into_value.rs | 131 ++++++ actix-http/src/header/map.rs | 46 +- actix-http/src/header/mod.rs | 427 +----------------- actix-http/src/header/shared/entity.rs | 2 +- actix-http/src/header/shared/extended.rs | 193 ++++++++ actix-http/src/header/shared/httpdate.rs | 2 +- actix-http/src/header/shared/mod.rs | 18 +- actix-http/src/header/utils.rs | 63 +++ actix-http/src/httpmessage.rs | 71 +-- actix-http/src/lib.rs | 4 +- actix-http/src/request.rs | 8 +- actix-http/src/response.rs | 233 ++++++---- actix-http/src/test.rs | 99 ++-- actix-http/src/ws/mod.rs | 60 +-- actix-http/tests/test_client.rs | 2 +- actix-http/tests/test_openssl.rs | 10 +- actix-http/tests/test_rustls.rs | 8 +- actix-http/tests/test_server.rs | 8 +- actix-web-actors/src/ws.rs | 118 ++--- awc/CHANGES.md | 11 + awc/src/builder.rs | 2 +- awc/src/frozen.rs | 2 +- awc/src/lib.rs | 12 +- awc/src/request.rs | 160 +++---- awc/src/sender.rs | 4 +- awc/src/test.rs | 4 +- awc/src/ws.rs | 6 +- awc/tests/test_client.rs | 19 +- examples/client.rs | 2 +- src/extract.rs | 58 +-- src/guard.rs | 11 +- src/info.rs | 12 +- src/lib.rs | 2 +- src/middleware/default_headers.rs | 7 +- src/middleware/logger.rs | 67 +-- src/request.rs | 16 +- src/responder.rs | 76 ++-- src/service.rs | 4 +- src/test.rs | 69 ++- src/types/form.rs | 60 +-- src/types/json.rs | 135 +++--- src/types/payload.rs | 31 +- tests/test_server.rs | 42 +- 76 files changed, 1568 insertions(+), 1347 deletions(-) create mode 100644 actix-http/src/header/common/content_encoding.rs create mode 100644 actix-http/src/header/into_pair.rs create mode 100644 actix-http/src/header/into_value.rs create mode 100644 actix-http/src/header/shared/extended.rs create mode 100644 actix-http/src/header/utils.rs diff --git a/CHANGES.md b/CHANGES.md index 00608df76..12caa2df9 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -15,10 +15,12 @@ ### Removed * Public field of `web::Path` has been made private. [#1894] * Public field of `web::Query` has been made private. [#1894] +* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] [#1891]: https://github.com/actix/actix-web/pull/1891 [#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 +[#1869]: https://github.com/actix/actix-web/pull/1869 ## 4.0.0-beta.1 - 2021-01-07 diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index f4314f6bc..7b3e9adb5 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -101,7 +101,7 @@ mod tests { header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) + .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); @@ -113,7 +113,7 @@ mod tests { let since = file.last_modified().unwrap(); let req = TestRequest::default() - .header(header::IF_MODIFIED_SINCE, since) + .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_MODIFIED); @@ -126,8 +126,8 @@ mod tests { header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() - .header(header::IF_NONE_MATCH, "miss_etag") - .header(header::IF_MODIFIED_SINCE, since) + .insert_header((header::IF_NONE_MATCH, "miss_etag")) + .insert_header((header::IF_MODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_ne!(resp.status(), StatusCode::NOT_MODIFIED); @@ -139,7 +139,7 @@ mod tests { let since = file.last_modified().unwrap(); let req = TestRequest::default() - .header(header::IF_UNMODIFIED_SINCE, since) + .insert_header((header::IF_UNMODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -151,7 +151,7 @@ mod tests { let since = header::HttpDate::from(SystemTime::UNIX_EPOCH); let req = TestRequest::default() - .header(header::IF_UNMODIFIED_SINCE, since) + .insert_header((header::IF_UNMODIFIED_SINCE, since)) .to_http_request(); let resp = file.respond_to(&req).await.unwrap(); assert_eq!(resp.status(), StatusCode::PRECONDITION_FAILED); @@ -398,7 +398,7 @@ mod tests { // Valid range header let request = TestRequest::get() .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=10-20") + .insert_header((header::RANGE, "bytes=10-20")) .to_request(); let response = test::call_service(&mut srv, request).await; assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); @@ -406,7 +406,7 @@ mod tests { // Invalid range header let request = TestRequest::get() .uri("/t%65st/Cargo.toml") - .header(header::RANGE, "bytes=1-0") + .insert_header((header::RANGE, "bytes=1-0")) .to_request(); let response = test::call_service(&mut srv, request).await; @@ -420,7 +420,7 @@ mod tests { // Valid range header let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=10-20") + .insert_header((header::RANGE, "bytes=10-20")) .send() .await .unwrap(); @@ -430,7 +430,7 @@ mod tests { // Invalid range header let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=10-5") + .insert_header((header::RANGE, "bytes=10-5")) .send() .await .unwrap(); @@ -445,7 +445,7 @@ mod tests { // Valid range header let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=10-20") + .insert_header((header::RANGE, "bytes=10-20")) .send() .await .unwrap(); @@ -455,7 +455,7 @@ mod tests { // Valid range header, starting from 0 let response = srv .get("/tests/test.binary") - .header(header::RANGE, "bytes=0-20") + .insert_header((header::RANGE, "bytes=0-20")) .send() .await .unwrap(); @@ -560,7 +560,7 @@ mod tests { let request = TestRequest::get() .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") + .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); let res = test::call_service(&mut srv, request).await; assert_eq!(res.status(), StatusCode::OK); @@ -580,7 +580,7 @@ mod tests { let request = TestRequest::get() .uri("/") - .header(header::ACCEPT_ENCODING, "gzip") + .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); let res = test::call_service(&mut srv, request).await; assert_eq!(res.status(), StatusCode::OK); diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index dc461e29a..8cd2a23f9 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -282,16 +282,16 @@ impl NamedFile { if self.flags.contains(Flags::PREFER_UTF8) { let ct = equiv_utf8_text(self.content_type.clone()); - res.header(header::CONTENT_TYPE, ct.to_string()); + res.insert_header((header::CONTENT_TYPE, ct.to_string())); } else { - res.header(header::CONTENT_TYPE, self.content_type.to_string()); + res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); } if self.flags.contains(Flags::CONTENT_DISPOSITION) { - res.header( + res.insert_header(( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), - ); + )); } if let Some(current_encoding) = self.encoding { @@ -361,16 +361,16 @@ impl NamedFile { if self.flags.contains(Flags::PREFER_UTF8) { let ct = equiv_utf8_text(self.content_type.clone()); - resp.header(header::CONTENT_TYPE, ct.to_string()); + resp.insert_header((header::CONTENT_TYPE, ct.to_string())); } else { - resp.header(header::CONTENT_TYPE, self.content_type.to_string()); + resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); } if self.flags.contains(Flags::CONTENT_DISPOSITION) { - resp.header( + resp.insert_header(( header::CONTENT_DISPOSITION, self.content_disposition.to_string(), - ); + )); } // default compressing @@ -379,14 +379,14 @@ impl NamedFile { } if let Some(lm) = last_modified { - resp.header(header::LAST_MODIFIED, lm.to_string()); + resp.insert_header((header::LAST_MODIFIED, lm.to_string())); } if let Some(etag) = etag { - resp.header(header::ETAG, etag.to_string()); + resp.insert_header((header::ETAG, etag.to_string())); } - resp.header(header::ACCEPT_RANGES, "bytes"); + resp.insert_header((header::ACCEPT_RANGES, "bytes")); let mut length = self.md.len(); let mut offset = 0; @@ -399,7 +399,7 @@ impl NamedFile { offset = ranges[0].start; resp.encoding(ContentEncoding::Identity); - resp.header( + resp.insert_header(( header::CONTENT_RANGE, format!( "bytes {}-{}/{}", @@ -407,9 +407,12 @@ impl NamedFile { offset + length - 1, self.md.len() ), - ); + )); } else { - resp.header(header::CONTENT_RANGE, format!("bytes */{}", length)); + resp.insert_header(( + header::CONTENT_RANGE, + format!("bytes */{}", length), + )); return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 05431db38..25e285efc 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -78,7 +78,7 @@ impl Service for FilesService { if !is_method_valid { return Either::Left(ok(req.into_response( actix_web::HttpResponse::MethodNotAllowed() - .header(header::CONTENT_TYPE, "text/plain") + .insert_header(header::ContentType(mime::TEXT_PLAIN_UTF_8)) .body("Request did not meet this resource's requirements."), ))); } @@ -102,7 +102,7 @@ impl Service for FilesService { return Either::Left(ok(req.into_response( HttpResponse::Found() - .header(header::LOCATION, redirect_to) + .insert_header((header::LOCATION, redirect_to)) .body("") .into_body(), ))); diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index e9a94300b..ed3764587 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,26 @@ # Changes ## Unreleased - 2021-xx-xx -* `Response::content_type` now takes an `impl IntoHeaderValue` to support `mime` types. [#1894] +### Added +* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] +* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] +* `ResponseBuilder::append_header` method which allows using typed headers. [#1869] +* `TestRequest::insert_header` method which allows using typed headers. [#1869] +### Changed +* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed + `mime` types. [#1894] +* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std + `TryInto` trait. [#1894] + +### Removed +* `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] +* `ResponseBuilder::set_header`; use `ResponseBuilder::insert_header`. [#1869] +* `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] +* `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] +* `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] + +[#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -34,6 +52,7 @@ [#1864]: https://github.com/actix/actix-web/pull/1864 [#1878]: https://github.com/actix/actix-web/pull/1878 + ## 2.2.0 - 2020-11-25 ### Added * HttpResponse builders for 1xx status codes. [#1768] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0cc8e5cf9..af6209248 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -58,7 +58,7 @@ encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } -fxhash = "0.2.1" +ahash = "0.6" h2 = "0.3.0" http = "0.2.2" httparse = "1.3" @@ -75,6 +75,7 @@ regex = "1.3" serde = "1.0" serde_json = "1.0" sha-1 = "0.9" +smallvec = "1.6" slab = "0.4" serde_urlencoded = "0.7" time = { version = "0.2.7", default-features = false, features = ["std"] } diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index beb1cce2c..90d768cbe 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -26,7 +26,10 @@ async fn main() -> io::Result<()> { info!("request body: {:?}", body); Ok::<_, Error>( Response::Ok() - .header("x-head", HeaderValue::from_static("dummy value!")) + .insert_header(( + "x-head", + HeaderValue::from_static("dummy value!"), + )) .body(body), ) }) diff --git a/actix-http/examples/echo2.rs b/actix-http/examples/echo2.rs index 5b7e504d3..bc932ce8f 100644 --- a/actix-http/examples/echo2.rs +++ b/actix-http/examples/echo2.rs @@ -15,7 +15,7 @@ async fn handle_request(mut req: Request) -> Result { info!("request body: {:?}", body); Ok(Response::Ok() - .header("x-head", HeaderValue::from_static("dummy value!")) + .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .body(body)) } diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index d6477b152..a84e9aac6 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -19,7 +19,10 @@ async fn main() -> io::Result<()> { .finish(|_req| { info!("{:?}", _req); let mut res = Response::Ok(); - res.header("x-head", HeaderValue::from_static("dummy value!")); + res.insert_header(( + "x-head", + HeaderValue::from_static("dummy value!"), + )); future::ok::<_, ()>(res.body("Hello world!")) }) .tcp() diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 758ad8424..24f4207e8 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -45,7 +45,7 @@ where Some(port) => write!(wrt, "{}:{}", host, port), }; - match wrt.get_mut().split().freeze().try_into() { + match wrt.get_mut().split().freeze().try_into_value() { Ok(value) => match head { RequestHeadType::Owned(ref mut head) => { head.headers.insert(HOST, value) diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 7da2b6234..af862cdd7 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -10,10 +10,10 @@ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use actix_utils::task::LocalWaker; +use ahash::AHashMap; use bytes::Bytes; use futures_channel::oneshot; use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; -use fxhash::FxHashMap; use h2::client::{Connection, SendRequest}; use http::uri::Authority; use indexmap::IndexSet; @@ -59,7 +59,7 @@ where acquired: 0, waiters: Slab::new(), waiters_queue: IndexSet::new(), - available: FxHashMap::default(), + available: AHashMap::default(), waker: LocalWaker::new(), })); @@ -257,7 +257,7 @@ struct AvailableConnection { pub(crate) struct Inner { config: ConnectorConfig, acquired: usize, - available: FxHashMap>>, + available: AHashMap>>, waiters: Slab< Option<( Connect, diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index b20dfe11d..e978c1749 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -1,14 +1,16 @@ -use std::any::{Any, TypeId}; -use std::{fmt, mem}; +use std::{ + any::{Any, TypeId}, + fmt, mem, +}; -use fxhash::FxHashMap; +use ahash::AHashMap; /// A type map of request extensions. #[derive(Default)] pub struct Extensions { /// Use FxHasher with a std HashMap with for faster /// lookups on the small `TypeId` (u64 equivalent) keys. - map: FxHashMap>, + map: AHashMap>, } impl Extensions { @@ -16,7 +18,7 @@ impl Extensions { #[inline] pub fn new() -> Extensions { Extensions { - map: FxHashMap::default(), + map: AHashMap::default(), } } diff --git a/actix-http/src/header/common/accept.rs b/actix-http/src/header/common/accept.rs index da26b0261..775da3394 100644 --- a/actix-http/src/header/common/accept.rs +++ b/actix-http/src/header/common/accept.rs @@ -32,50 +32,36 @@ header! { /// * `text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c` /// /// # Examples - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{Accept, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// - /// builder.set( + /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{Accept, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// - /// builder.set( + /// builder.insert_header( /// Accept(vec![ /// qitem(mime::APPLICATION_JSON), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// extern crate mime; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{Accept, QualityItem, q, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// - /// builder.set( + /// builder.insert_header( /// Accept(vec![ /// qitem(mime::TEXT_HTML), /// qitem("application/xhtml+xml".parse().unwrap()), @@ -90,7 +76,6 @@ header! { /// ), /// ]) /// ); - /// # } /// ``` (Accept, header::ACCEPT) => (QualityItem)+ @@ -132,7 +117,7 @@ header! { #[test] fn test_fuzzing1() { use crate::test::TestRequest; - let req = TestRequest::with_header(crate::header::ACCEPT, "chunk#;e").finish(); + let req = TestRequest::default().insert_header((crate::header::ACCEPT, "chunk#;e")).finish(); let header = Accept::parse(&req); assert!(header.is_ok()); } diff --git a/actix-http/src/header/common/accept_charset.rs b/actix-http/src/header/common/accept_charset.rs index 291ca53b6..db530a8bc 100644 --- a/actix-http/src/header/common/accept_charset.rs +++ b/actix-http/src/header/common/accept_charset.rs @@ -21,44 +21,37 @@ header! { /// * `iso-8859-5, unicode-1-1;q=0.8` /// /// # Examples - /// ```rust - /// # extern crate actix_http; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Us_Ascii)]) /// ); - /// # } /// ``` - /// ```rust - /// # extern crate actix_http; + /// + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{AcceptCharset, Charset, q, QualityItem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptCharset(vec![ /// QualityItem::new(Charset::Us_Ascii, q(900)), /// QualityItem::new(Charset::Iso_8859_10, q(200)), /// ]) /// ); - /// # } /// ``` - /// ```rust - /// # extern crate actix_http; + /// + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{AcceptCharset, Charset, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptCharset(vec![qitem(Charset::Ext("utf-8".to_owned()))]) /// ); - /// # } /// ``` (AcceptCharset, ACCEPT_CHARSET) => (QualityItem)+ diff --git a/actix-http/src/header/common/accept_language.rs b/actix-http/src/header/common/accept_language.rs index 55879b57f..a7ad00863 100644 --- a/actix-http/src/header/common/accept_language.rs +++ b/actix-http/src/header/common/accept_language.rs @@ -22,41 +22,35 @@ header! { /// /// # Examples /// - /// ```rust - /// # extern crate actix_http; - /// # extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; /// use actix_http::http::header::{AcceptLanguage, LanguageTag, qitem}; /// - /// # fn main() { /// let mut builder = Response::Ok(); /// let mut langtag: LanguageTag = Default::default(); /// langtag.language = Some("en".to_owned()); /// langtag.region = Some("US".to_owned()); - /// builder.set( + /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; /// use actix_http::http::header::{AcceptLanguage, QualityItem, q, qitem}; - /// # - /// # fn main() { + /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// AcceptLanguage(vec![ /// qitem(langtag!(da)), /// QualityItem::new(langtag!(en;;;GB), q(800)), /// QualityItem::new(langtag!(en), q(700)), /// ]) /// ); - /// # } /// ``` (AcceptLanguage, ACCEPT_LANGUAGE) => (QualityItem)+ diff --git a/actix-http/src/header/common/allow.rs b/actix-http/src/header/common/allow.rs index 88c21763c..06b1efedc 100644 --- a/actix-http/src/header/common/allow.rs +++ b/actix-http/src/header/common/allow.rs @@ -22,38 +22,28 @@ header! { /// /// # Examples /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_http; + /// ``` /// use actix_http::Response; - /// use actix_http::http::header::Allow; - /// use http::Method; + /// use actix_http::http::{header::Allow, Method}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// Allow(vec![Method::GET]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate http; - /// # extern crate actix_http; + /// ``` /// use actix_http::Response; - /// use actix_http::http::header::Allow; - /// use http::Method; + /// use actix_http::http::{header::Allow, Method}; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// Allow(vec![ /// Method::GET, /// Method::POST, /// Method::PATCH, /// ]) /// ); - /// # } /// ``` (Allow, header::ALLOW) => (Method)* diff --git a/actix-http/src/header/common/cache_control.rs b/actix-http/src/header/common/cache_control.rs index ec94ce4a9..94ce9a750 100644 --- a/actix-http/src/header/common/cache_control.rs +++ b/actix-http/src/header/common/cache_control.rs @@ -28,12 +28,12 @@ use crate::header::{ /// * `max-age=30` /// /// # Examples -/// ```rust +/// ``` /// use actix_http::Response; /// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = Response::Ok(); -/// builder.set(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); +/// builder.insert_header(CacheControl(vec![CacheDirective::MaxAge(86400u32)])); /// ``` /// /// ```rust @@ -41,7 +41,7 @@ use crate::header::{ /// use actix_http::http::header::{CacheControl, CacheDirective}; /// /// let mut builder = Response::Ok(); -/// builder.set(CacheControl(vec![ +/// builder.insert_header(CacheControl(vec![ /// CacheDirective::NoCache, /// CacheDirective::Private, /// CacheDirective::MaxAge(360u32), @@ -82,7 +82,7 @@ impl fmt::Display for CacheControl { impl IntoHeaderValue for CacheControl { type Error = header::InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); header::HeaderValue::from_maybe_shared(writer.take()) @@ -196,7 +196,8 @@ mod tests { #[test] fn test_parse_multiple_headers() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "no-cache, private") + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "no-cache, private")) .finish(); let cache = Header::parse(&req); assert_eq!( @@ -210,9 +211,9 @@ mod tests { #[test] fn test_parse_argument() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=100, private") - .finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "max-age=100, private")) + .finish(); let cache = Header::parse(&req); assert_eq!( cache.ok(), @@ -225,8 +226,9 @@ mod tests { #[test] fn test_parse_quote_form() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "max-age=\"200\"").finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "max-age=\"200\"")) + .finish(); let cache = Header::parse(&req); assert_eq!( cache.ok(), @@ -236,8 +238,9 @@ mod tests { #[test] fn test_parse_extension() { - let req = - TestRequest::with_header(header::CACHE_CONTROL, "foo, bar=baz").finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "foo, bar=baz")) + .finish(); let cache = Header::parse(&req); assert_eq!( cache.ok(), @@ -250,7 +253,9 @@ mod tests { #[test] fn test_parse_bad_syntax() { - let req = TestRequest::with_header(header::CACHE_CONTROL, "foo=").finish(); + let req = TestRequest::default() + .insert_header((header::CACHE_CONTROL, "foo=")) + .finish(); let cache: Result = Header::parse(&req); assert_eq!(cache.ok(), None) } diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index 4c512acbe..ae4a97902 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -454,7 +454,7 @@ impl ContentDisposition { impl IntoHeaderValue for ContentDisposition { type Error = header::InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); header::HeaderValue::from_maybe_shared(writer.take()) diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/common/content_encoding.rs new file mode 100644 index 000000000..49f0ff78f --- /dev/null +++ b/actix-http/src/header/common/content_encoding.rs @@ -0,0 +1,64 @@ +/// Represents a supported content encoding. +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum ContentEncoding { + /// Automatically select encoding based on encoding negotiation. + Auto, + + /// A format using the Brotli algorithm. + Br, + + /// A format using the zlib structure with deflate algorithm. + Deflate, + + /// Gzip algorithm. + Gzip, + + /// Indicates the identity function (i.e. no compression, nor modification). + Identity, +} + +impl ContentEncoding { + /// Is the content compressed? + #[inline] + pub fn is_compression(self) -> bool { + matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) + } + + /// Convert content encoding to string + #[inline] + pub fn as_str(self) -> &'static str { + match self { + ContentEncoding::Br => "br", + ContentEncoding::Gzip => "gzip", + ContentEncoding::Deflate => "deflate", + ContentEncoding::Identity | ContentEncoding::Auto => "identity", + } + } + + /// Default Q-factor (quality) value. + #[inline] + pub fn quality(self) -> f64 { + match self { + ContentEncoding::Br => 1.1, + ContentEncoding::Gzip => 1.0, + ContentEncoding::Deflate => 0.9, + ContentEncoding::Identity | ContentEncoding::Auto => 0.1, + } + } +} + +impl From<&str> for ContentEncoding { + fn from(val: &str) -> ContentEncoding { + let val = val.trim(); + + if val.eq_ignore_ascii_case("br") { + ContentEncoding::Br + } else if val.eq_ignore_ascii_case("gzip") { + ContentEncoding::Gzip + } else if val.eq_ignore_ascii_case("deflate") { + ContentEncoding::Deflate + } else { + ContentEncoding::Identity + } + } +} diff --git a/actix-http/src/header/common/content_language.rs b/actix-http/src/header/common/content_language.rs index 838981a39..e9be67a1b 100644 --- a/actix-http/src/header/common/content_language.rs +++ b/actix-http/src/header/common/content_language.rs @@ -23,38 +23,31 @@ header! { /// /// # Examples /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { + /// use actix_http::http::header::{ContentLanguage, qitem}; + /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(en)), /// ]) /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate actix_http; - /// # #[macro_use] extern crate language_tags; + /// ``` + /// use language_tags::langtag; /// use actix_http::Response; - /// # use actix_http::http::header::{ContentLanguage, qitem}; - /// # - /// # fn main() { + /// use actix_http::http::header::{ContentLanguage, qitem}; /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// ContentLanguage(vec![ /// qitem(langtag!(da)), /// qitem(langtag!(en;;;GB)), /// ]) /// ); - /// # } /// ``` (ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+ diff --git a/actix-http/src/header/common/content_range.rs b/actix-http/src/header/common/content_range.rs index 9a604c641..8b7552377 100644 --- a/actix-http/src/header/common/content_range.rs +++ b/actix-http/src/header/common/content_range.rs @@ -200,7 +200,7 @@ impl Display for ContentRangeSpec { impl IntoHeaderValue for ContentRangeSpec { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); HeaderValue::from_maybe_shared(writer.take()) diff --git a/actix-http/src/header/common/content_type.rs b/actix-http/src/header/common/content_type.rs index a0baa5637..ac5c7e5b8 100644 --- a/actix-http/src/header/common/content_type.rs +++ b/actix-http/src/header/common/content_type.rs @@ -30,31 +30,24 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::ContentType; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// ContentType::json() /// ); - /// # } /// ``` /// - /// ```rust - /// # extern crate mime; - /// # extern crate actix_http; - /// use mime::TEXT_HTML; + /// ``` /// use actix_http::Response; /// use actix_http::http::header::ContentType; /// - /// # fn main() { /// let mut builder = Response::Ok(); - /// builder.set( - /// ContentType(TEXT_HTML) + /// builder.insert_header( + /// ContentType(mime::TEXT_HTML) /// ); - /// # } /// ``` (ContentType, CONTENT_TYPE) => [Mime] @@ -99,6 +92,7 @@ impl ContentType { pub fn form_url_encoded() -> ContentType { ContentType(mime::APPLICATION_WWW_FORM_URLENCODED) } + /// A constructor to easily create a `Content-Type: image/jpeg` header. #[inline] pub fn jpeg() -> ContentType { diff --git a/actix-http/src/header/common/date.rs b/actix-http/src/header/common/date.rs index 784100e8d..e5ace95e6 100644 --- a/actix-http/src/header/common/date.rs +++ b/actix-http/src/header/common/date.rs @@ -19,13 +19,15 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::SystemTime; /// use actix_http::Response; /// use actix_http::http::header::Date; - /// use std::time::SystemTime; /// /// let mut builder = Response::Ok(); - /// builder.set(Date(SystemTime::now().into())); + /// builder.insert_header( + /// Date(SystemTime::now().into()) + /// ); /// ``` (Date, DATE) => [HttpDate] diff --git a/actix-http/src/header/common/etag.rs b/actix-http/src/header/common/etag.rs index 325b91cbf..4c1e8d262 100644 --- a/actix-http/src/header/common/etag.rs +++ b/actix-http/src/header/common/etag.rs @@ -27,20 +27,24 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set(ETag(EntityTag::new(false, "xyzzy".to_owned()))); + /// builder.insert_header( + /// ETag(EntityTag::new(false, "xyzzy".to_owned())) + /// ); /// ``` /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{ETag, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set(ETag(EntityTag::new(true, "xyzzy".to_owned()))); + /// builder.insert_header( + /// ETag(EntityTag::new(true, "xyzzy".to_owned())) + /// ); /// ``` (ETag, ETAG) => [EntityTag] diff --git a/actix-http/src/header/common/expires.rs b/actix-http/src/header/common/expires.rs index 3b9a7873d..79563955d 100644 --- a/actix-http/src/header/common/expires.rs +++ b/actix-http/src/header/common/expires.rs @@ -21,14 +21,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::Expires; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let expiration = SystemTime::now() + Duration::from_secs(60 * 60 * 24); - /// builder.set(Expires(expiration.into())); + /// builder.insert_header( + /// Expires(expiration.into()) + /// ); /// ``` (Expires, EXPIRES) => [HttpDate] diff --git a/actix-http/src/header/common/if_match.rs b/actix-http/src/header/common/if_match.rs index 7e0e9a7e0..db255e91a 100644 --- a/actix-http/src/header/common/if_match.rs +++ b/actix-http/src/header/common/if_match.rs @@ -29,20 +29,20 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::IfMatch; /// /// let mut builder = Response::Ok(); - /// builder.set(IfMatch::Any); + /// builder.insert_header(IfMatch::Any); /// ``` /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{IfMatch, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// IfMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), /// EntityTag::new(false, "foobar".to_owned()), diff --git a/actix-http/src/header/common/if_modified_since.rs b/actix-http/src/header/common/if_modified_since.rs index 39aca595d..99c7e441d 100644 --- a/actix-http/src/header/common/if_modified_since.rs +++ b/actix-http/src/header/common/if_modified_since.rs @@ -21,14 +21,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::IfModifiedSince; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfModifiedSince(modified.into())); + /// builder.insert_header( + /// IfModifiedSince(modified.into()) + /// ); /// ``` (IfModifiedSince, IF_MODIFIED_SINCE) => [HttpDate] diff --git a/actix-http/src/header/common/if_none_match.rs b/actix-http/src/header/common/if_none_match.rs index 7f6ccb137..464caf1ae 100644 --- a/actix-http/src/header/common/if_none_match.rs +++ b/actix-http/src/header/common/if_none_match.rs @@ -31,20 +31,20 @@ header! { /// /// # Examples /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::IfNoneMatch; /// /// let mut builder = Response::Ok(); - /// builder.set(IfNoneMatch::Any); + /// builder.insert_header(IfNoneMatch::Any); /// ``` /// - /// ```rust + /// ``` /// use actix_http::Response; /// use actix_http::http::header::{IfNoneMatch, EntityTag}; /// /// let mut builder = Response::Ok(); - /// builder.set( + /// builder.insert_header( /// IfNoneMatch::Items(vec![ /// EntityTag::new(false, "xyzzy".to_owned()), /// EntityTag::new(false, "foobar".to_owned()), @@ -73,13 +73,15 @@ mod tests { fn test_if_none_match() { let mut if_none_match: Result; - let req = TestRequest::with_header(IF_NONE_MATCH, "*").finish(); + let req = TestRequest::default() + .insert_header((IF_NONE_MATCH, "*")) + .finish(); if_none_match = Header::parse(&req); assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any)); - let req = - TestRequest::with_header(IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..]) - .finish(); + let req = TestRequest::default() + .insert_header((IF_NONE_MATCH, &b"\"foobar\", W/\"weak-etag\""[..])) + .finish(); if_none_match = Header::parse(&req); let mut entities: Vec = Vec::new(); diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs index b14ad0391..1513b7a44 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/actix-http/src/header/common/if_range.rs @@ -35,31 +35,34 @@ use crate::httpmessage::HttpMessage; /// /// # Examples /// -/// ```rust +/// ``` /// use actix_http::Response; /// use actix_http::http::header::{EntityTag, IfRange}; /// /// let mut builder = Response::Ok(); -/// builder.set(IfRange::EntityTag(EntityTag::new( -/// false, -/// "xyzzy".to_owned(), -/// ))); +/// builder.insert_header( +/// IfRange::EntityTag( +/// EntityTag::new(false, "abc".to_owned()) +/// ) +/// ); /// ``` /// -/// ```rust -/// use actix_http::Response; -/// use actix_http::http::header::IfRange; +/// ``` /// use std::time::{Duration, SystemTime}; +/// use actix_http::{http::header::IfRange, Response}; /// /// let mut builder = Response::Ok(); /// let fetched = SystemTime::now() - Duration::from_secs(60 * 60 * 24); -/// builder.set(IfRange::Date(fetched.into())); +/// builder.insert_header( +/// IfRange::Date(fetched.into()) +/// ); /// ``` #[derive(Clone, Debug, PartialEq)] pub enum IfRange { - /// The entity-tag the client has of the resource + /// The entity-tag the client has of the resource. EntityTag(EntityTag), - /// The date when the client retrieved the resource + + /// The date when the client retrieved the resource. Date(HttpDate), } @@ -98,7 +101,7 @@ impl Display for IfRange { impl IntoHeaderValue for IfRange { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut writer = Writer::new(); let _ = write!(&mut writer, "{}", self); HeaderValue::from_maybe_shared(writer.take()) @@ -110,7 +113,8 @@ mod test_if_range { use super::IfRange as HeaderField; use crate::header::*; use std::str; + test_header!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]); - test_header!(test2, vec![b"\"xyzzy\""]); + test_header!(test2, vec![b"\"abc\""]); test_header!(test3, vec![b"this-is-invalid"], None::); } diff --git a/actix-http/src/header/common/if_unmodified_since.rs b/actix-http/src/header/common/if_unmodified_since.rs index d6c099e64..1c2b4af78 100644 --- a/actix-http/src/header/common/if_unmodified_since.rs +++ b/actix-http/src/header/common/if_unmodified_since.rs @@ -22,14 +22,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::IfUnmodifiedSince; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(IfUnmodifiedSince(modified.into())); + /// builder.insert_header( + /// IfUnmodifiedSince(modified.into()) + /// ); /// ``` (IfUnmodifiedSince, IF_UNMODIFIED_SINCE) => [HttpDate] diff --git a/actix-http/src/header/common/last_modified.rs b/actix-http/src/header/common/last_modified.rs index cc888ccb0..65608d846 100644 --- a/actix-http/src/header/common/last_modified.rs +++ b/actix-http/src/header/common/last_modified.rs @@ -21,14 +21,16 @@ header! { /// /// # Example /// - /// ```rust + /// ``` + /// use std::time::{SystemTime, Duration}; /// use actix_http::Response; /// use actix_http::http::header::LastModified; - /// use std::time::{SystemTime, Duration}; /// /// let mut builder = Response::Ok(); /// let modified = SystemTime::now() - Duration::from_secs(60 * 60 * 24); - /// builder.set(LastModified(modified.into())); + /// builder.insert_header( + /// LastModified(modified.into()) + /// ); /// ``` (LastModified, LAST_MODIFIED) => [HttpDate] diff --git a/actix-http/src/header/common/mod.rs b/actix-http/src/header/common/mod.rs index c3d18613c..90e0a855e 100644 --- a/actix-http/src/header/common/mod.rs +++ b/actix-http/src/header/common/mod.rs @@ -18,6 +18,7 @@ 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; @@ -83,7 +84,7 @@ macro_rules! test_header { let a: Vec> = raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req = req.header(HeaderField::name(), item).take(); + req = req.insert_header((HeaderField::name(), item)).take(); } let req = req.finish(); let value = HeaderField::parse(&req); @@ -110,7 +111,7 @@ macro_rules! test_header { let a: Vec> = $raw.iter().map(|x| x.to_vec()).collect(); let mut req = test::TestRequest::default(); for item in a { - req.header(HeaderField::name(), item); + req.insert_header((HeaderField::name(), item)); } let req = req.finish(); let val = HeaderField::parse(&req); @@ -168,7 +169,7 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use std::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); @@ -204,7 +205,7 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use std::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); @@ -240,8 +241,8 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { - self.0.try_into() + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + self.0.try_into_value() } } }; @@ -289,7 +290,7 @@ macro_rules! header { impl $crate::http::header::IntoHeaderValue for $id { type Error = $crate::http::header::InvalidHeaderValue; - fn try_into(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { + fn try_into_value(self) -> Result<$crate::http::header::HeaderValue, Self::Error> { use std::fmt::Write; let mut writer = $crate::http::header::Writer::new(); let _ = write!(&mut writer, "{}", self); @@ -333,13 +334,14 @@ macro_rules! header { } mod accept_charset; -//mod accept_encoding; +// mod accept_encoding; mod accept; mod accept_language; mod allow; mod cache_control; mod content_disposition; mod content_language; +mod content_encoding; mod content_range; mod content_type; mod date; diff --git a/actix-http/src/header/into_pair.rs b/actix-http/src/header/into_pair.rs new file mode 100644 index 000000000..d0d6e7324 --- /dev/null +++ b/actix-http/src/header/into_pair.rs @@ -0,0 +1,117 @@ +use std::convert::TryFrom; + +use http::{ + header::{HeaderName, InvalidHeaderName, InvalidHeaderValue}, + Error as HttpError, HeaderValue, +}; + +use super::{Header, IntoHeaderValue}; + +/// Transforms structures into header K/V pairs for inserting into `HeaderMap`s. +pub trait IntoHeaderPair: Sized { + type Error: Into; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>; +} + +#[derive(Debug)] +pub enum InvalidHeaderPart { + Name(InvalidHeaderName), + Value(InvalidHeaderValue), +} + +impl From for HttpError { + fn from(part_err: InvalidHeaderPart) -> Self { + match part_err { + InvalidHeaderPart::Name(err) => err.into(), + InvalidHeaderPart::Value(err) => err.into(), + } + } +} + +impl IntoHeaderPair for (HeaderName, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name, value)) + } +} + +impl IntoHeaderPair for (&HeaderName, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name.clone(), value)) + } +} + +impl IntoHeaderPair for (&[u8], V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name, value)) + } +} + +impl IntoHeaderPair for (&str, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?; + let value = value + .try_into_value() + .map_err(|err| InvalidHeaderPart::Value(err.into()))?; + Ok((name, value)) + } +} + +impl IntoHeaderPair for (String, V) +where + V: IntoHeaderValue, + V::Error: Into, +{ + type Error = InvalidHeaderPart; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + let (name, value) = self; + (name.as_str(), value).try_into_header_pair() + } +} + +impl IntoHeaderPair for T { + type Error = ::Error; + + fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> { + Ok((T::name(), self.try_into_value()?)) + } +} diff --git a/actix-http/src/header/into_value.rs b/actix-http/src/header/into_value.rs new file mode 100644 index 000000000..4b1e6cbbd --- /dev/null +++ b/actix-http/src/header/into_value.rs @@ -0,0 +1,131 @@ +use std::convert::TryFrom; + +use bytes::Bytes; +use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue}; +use mime::Mime; + +/// A trait for any object that can be Converted to a `HeaderValue` +pub trait IntoHeaderValue: Sized { + /// The type returned in the event of a conversion error. + type Error: Into; + + /// Try to convert value to a HeaderValue. + fn try_into_value(self) -> Result; +} + +impl IntoHeaderValue for HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + Ok(self) + } +} + +impl IntoHeaderValue for &HeaderValue { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + Ok(self.clone()) + } +} + +impl IntoHeaderValue for &str { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + self.parse() + } +} + +impl IntoHeaderValue for &[u8] { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::from_bytes(self) + } +} + +impl IntoHeaderValue for Bytes { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::from_maybe_shared(self) + } +} + +impl IntoHeaderValue for Vec { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self) + } +} + +impl IntoHeaderValue for String { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self) + } +} + +impl IntoHeaderValue for usize { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for i64 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for u64 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for i32 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for u32 { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(self.to_string()) + } +} + +impl IntoHeaderValue for Mime { + type Error = InvalidHeaderValue; + + #[inline] + fn try_into_value(self) -> Result { + HeaderValue::try_from(format!("{}", self)) + } +} diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 6ab3509f7..8f20f3e6f 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -1,33 +1,36 @@ -use std::collections::hash_map::{self, Entry}; -use std::convert::TryFrom; +use std::{ + collections::hash_map::{self, Entry}, + convert::TryFrom, +}; +use ahash::AHashMap; use either::Either; -use fxhash::FxHashMap; use http::header::{HeaderName, HeaderValue}; +use smallvec::{smallvec, SmallVec}; -/// A set of HTTP headers +/// A multi-map of HTTP headers. /// -/// `HeaderMap` is an multi-map of [`HeaderName`] to values. -#[derive(Debug, Clone)] +/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more values. +#[derive(Debug, Clone, Default)] pub struct HeaderMap { - pub(crate) inner: FxHashMap, + pub(crate) inner: AHashMap, } #[derive(Debug, Clone)] pub(crate) enum Value { One(HeaderValue), - Multi(Vec), + Multi(SmallVec<[HeaderValue; 4]>), } impl Value { - fn get(&self) -> &HeaderValue { + fn first(&self) -> &HeaderValue { match self { Value::One(ref val) => val, Value::Multi(ref val) => &val[0], } } - fn get_mut(&mut self) -> &mut HeaderValue { + fn first_mut(&mut self) -> &mut HeaderValue { match self { Value::One(ref mut val) => val, Value::Multi(ref mut val) => &mut val[0], @@ -37,7 +40,7 @@ impl Value { fn append(&mut self, val: HeaderValue) { match self { Value::One(_) => { - let data = std::mem::replace(self, Value::Multi(vec![val])); + let data = std::mem::replace(self, Value::Multi(smallvec![val])); match data { Value::One(val) => self.append(val), Value::Multi(_) => unreachable!(), @@ -55,7 +58,7 @@ impl HeaderMap { /// allocate. pub fn new() -> Self { HeaderMap { - inner: FxHashMap::default(), + inner: AHashMap::default(), } } @@ -69,7 +72,7 @@ impl HeaderMap { /// More capacity than requested may be allocated. pub fn with_capacity(capacity: usize) -> HeaderMap { HeaderMap { - inner: FxHashMap::with_capacity_and_hasher(capacity, Default::default()), + inner: AHashMap::with_capacity_and_hasher(capacity, Default::default()), } } @@ -118,7 +121,7 @@ impl HeaderMap { /// is returned. Use `get_all` to get all values associated with a given /// key. Returns `None` if there are no values associated with the key. pub fn get(&self, name: N) -> Option<&HeaderValue> { - self.get2(name).map(|v| v.get()) + self.get2(name).map(|v| v.first()) } fn get2(&self, name: N) -> Option<&Value> { @@ -134,11 +137,11 @@ impl HeaderMap { } } - /// Returns a view of all values associated with a key. + /// Returns an iterator of all values associated with a key. /// - /// The returned view does not incur any allocations and allows iterating - /// the values associated with the key. See [`GetAll`] for more details. - /// Returns `None` if there are no values associated with the key. + /// The returned view does not incur any allocations and allows iterating the values associated + /// with the key. Returns `None` if there are no values associated with the key. Iteration order + /// is not guaranteed to be the same as insertion order. pub fn get_all(&self, name: N) -> GetAll<'_> { GetAll { idx: 0, @@ -153,10 +156,10 @@ impl HeaderMap { /// key. Returns `None` if there are no values associated with the key. pub fn get_mut(&mut self, name: N) -> Option<&mut HeaderValue> { match name.as_name() { - Either::Left(name) => self.inner.get_mut(name).map(|v| v.get_mut()), + Either::Left(name) => self.inner.get_mut(name).map(|v| v.first_mut()), Either::Right(s) => { if let Ok(name) = HeaderName::try_from(s) { - self.inner.get_mut(&name).map(|v| v.get_mut()) + self.inner.get_mut(&name).map(|v| v.first_mut()) } else { None } @@ -282,6 +285,7 @@ impl<'a> AsName for &'a String { } } +/// Iterator for all values in a `HeaderMap` with the same name. pub struct GetAll<'a> { idx: usize, item: Option<&'a Value>, @@ -337,7 +341,7 @@ impl<'a> IntoIterator for &'a HeaderMap { pub struct Iter<'a> { idx: usize, - current: Option<(&'a HeaderName, &'a Vec)>, + current: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>, iter: hash_map::Iter<'a, HeaderName, Value>, } diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 0f87516eb..e4e15cd91 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,12 +1,9 @@ -//! Various http headers -// This is mostly copy of [hyper](https://github.com/hyperium/hyper/tree/master/src/header) +//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other +//! header utility methods. -use std::convert::TryFrom; -use std::{fmt, str::FromStr}; +use std::fmt; use bytes::{Bytes, BytesMut}; -use http::Error as HttpError; -use mime::Mime; use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; @@ -14,22 +11,27 @@ pub use http::header::*; use crate::error::ParseError; use crate::httpmessage::HttpMessage; +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::*; +pub use self::into_pair::IntoHeaderPair; +pub use self::into_value::IntoHeaderValue; #[doc(hidden)] pub use self::map::GetAll; pub use self::map::HeaderMap; +pub use self::utils::*; -/// A trait for any object that will represent a header field and value. -pub trait Header -where - Self: IntoHeaderValue, -{ +/// A trait for any object that already represents a valid header field and value. +pub trait Header: IntoHeaderValue { /// Returns the name of the header field fn name() -> HeaderName; @@ -37,159 +39,6 @@ where fn parse(msg: &T) -> Result; } -/// A trait for any object that can be Converted to a `HeaderValue` -pub trait IntoHeaderValue: Sized { - /// The type returned in the event of a conversion error. - type Error: Into; - - /// Try to convert value to a Header value. - fn try_into(self) -> Result; -} - -impl IntoHeaderValue for HeaderValue { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - Ok(self) - } -} - -impl<'a> IntoHeaderValue for &'a str { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - self.parse() - } -} - -impl<'a> IntoHeaderValue for &'a [u8] { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_bytes(self) - } -} - -impl IntoHeaderValue for Bytes { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::from_maybe_shared(self) - } -} - -impl IntoHeaderValue for Vec { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(self) - } -} - -impl IntoHeaderValue for String { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(self) - } -} - -impl IntoHeaderValue for usize { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - let s = format!("{}", self); - HeaderValue::try_from(s) - } -} - -impl IntoHeaderValue for u64 { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - let s = format!("{}", self); - HeaderValue::try_from(s) - } -} - -impl IntoHeaderValue for Mime { - type Error = InvalidHeaderValue; - - #[inline] - fn try_into(self) -> Result { - HeaderValue::try_from(format!("{}", self)) - } -} - -/// Represents supported types of content encodings -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum ContentEncoding { - /// Automatically select encoding based on encoding negotiation - Auto, - /// A format using the Brotli algorithm - Br, - /// A format using the zlib structure with deflate algorithm - Deflate, - /// Gzip algorithm - Gzip, - /// Indicates the identity function (i.e. no compression, nor modification) - Identity, -} - -impl ContentEncoding { - #[inline] - /// Is the content compressed? - pub fn is_compression(self) -> bool { - matches!(self, ContentEncoding::Identity | ContentEncoding::Auto) - } - - #[inline] - /// Convert content encoding to string - pub fn as_str(self) -> &'static str { - match self { - ContentEncoding::Br => "br", - ContentEncoding::Gzip => "gzip", - ContentEncoding::Deflate => "deflate", - ContentEncoding::Identity | ContentEncoding::Auto => "identity", - } - } - - #[inline] - /// default quality value - pub fn quality(self) -> f64 { - match self { - ContentEncoding::Br => 1.1, - ContentEncoding::Gzip => 1.0, - ContentEncoding::Deflate => 0.9, - ContentEncoding::Identity | ContentEncoding::Auto => 0.1, - } - } -} - -impl<'a> From<&'a str> for ContentEncoding { - fn from(s: &'a str) -> ContentEncoding { - let s = s.trim(); - - if s.eq_ignore_ascii_case("br") { - ContentEncoding::Br - } else if s.eq_ignore_ascii_case("gzip") { - ContentEncoding::Gzip - } else if s.eq_ignore_ascii_case("deflate") { - ContentEncoding::Deflate - } else { - ContentEncoding::Identity - } - } -} - #[doc(hidden)] pub(crate) struct Writer { buf: BytesMut, @@ -201,6 +50,7 @@ impl Writer { buf: BytesMut::new(), } } + fn take(&mut self) -> Bytes { self.buf.split().freeze() } @@ -219,164 +69,7 @@ impl fmt::Write for Writer { } } -#[inline] -#[doc(hidden)] -/// Reads a comma-delimited raw header into a Vec. -pub fn from_comma_delimited<'a, I: Iterator + 'a, T: FromStr>( - all: I, -) -> Result, ParseError> { - let mut result = Vec::new(); - for h in all { - let s = h.to_str().map_err(|_| ParseError::Header)?; - result.extend( - s.split(',') - .filter_map(|x| match x.trim() { - "" => None, - y => Some(y), - }) - .filter_map(|x| x.trim().parse().ok()), - ) - } - Ok(result) -} - -#[inline] -#[doc(hidden)] -/// Reads a single string when parsing a header. -pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { - if let Some(line) = val { - let line = line.to_str().map_err(|_| ParseError::Header)?; - if !line.is_empty() { - return T::from_str(line).or(Err(ParseError::Header)); - } - } - Err(ParseError::Header) -} - -#[inline] -#[doc(hidden)] -/// Format an array into a comma-delimited string. -pub fn fmt_comma_delimited(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result -where - T: fmt::Display, -{ - let mut iter = parts.iter(); - if let Some(part) = iter.next() { - fmt::Display::fmt(part, f)?; - } - for part in iter { - f.write_str(", ")?; - fmt::Display::fmt(part, f)?; - } - Ok(()) -} - -// From hyper v0.11.27 src/header/parsing.rs - -/// The value part of an extended parameter consisting of three parts: -/// the REQUIRED character set name (`charset`), the OPTIONAL language information (`language_tag`), -/// and a character sequence representing the actual value (`value`), separated by single quote -/// characters. It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] -pub struct ExtendedValue { - /// The character set that is used to encode the `value` to a string. - pub charset: Charset, - /// The human language details of the `value`, if available. - pub language_tag: Option, - /// The parameter value, as expressed in octets. - pub value: Vec, -} - -/// Parses extended header parameter values (`ext-value`), as defined in -/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). -/// -/// Extended values are denoted by parameter names that end with `*`. -/// -/// ## ABNF -/// -/// ```text -/// ext-value = charset "'" [ language ] "'" value-chars -/// ; like RFC 2231's -/// ; (see [RFC2231], Section 7) -/// -/// charset = "UTF-8" / "ISO-8859-1" / mime-charset -/// -/// mime-charset = 1*mime-charsetc -/// mime-charsetc = ALPHA / DIGIT -/// / "!" / "#" / "$" / "%" / "&" -/// / "+" / "-" / "^" / "_" / "`" -/// / "{" / "}" / "~" -/// ; as in Section 2.3 of [RFC2978] -/// ; except that the single quote is not included -/// ; SHOULD be registered in the IANA charset registry -/// -/// language = -/// -/// value-chars = *( pct-encoded / attr-char ) -/// -/// pct-encoded = "%" HEXDIG HEXDIG -/// ; see [RFC3986], Section 2.1 -/// -/// attr-char = ALPHA / DIGIT -/// / "!" / "#" / "$" / "&" / "+" / "-" / "." -/// / "^" / "_" / "`" / "|" / "~" -/// ; token except ( "*" / "'" / "%" ) -/// ``` -pub fn parse_extended_value( - val: &str, -) -> Result { - // Break into three pieces separated by the single-quote character - let mut parts = val.splitn(3, '\''); - - // Interpret the first piece as a Charset - let charset: Charset = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, - }; - - // Interpret the second piece as a language tag - let language_tag: Option = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some("") => None, - Some(s) => match s.parse() { - Ok(lt) => Some(lt), - Err(_) => return Err(crate::error::ParseError::Header), - }, - }; - - // Interpret the third piece as a sequence of value characters - let value: Vec = match parts.next() { - None => return Err(crate::error::ParseError::Header), - Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), - }; - - Ok(ExtendedValue { - value, - charset, - language_tag, - }) -} - -impl fmt::Display for ExtendedValue { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - let encoded_value = - percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); - if let Some(ref lang) = self.language_tag { - write!(f, "{}'{}'{}", self.charset, lang, encoded_value) - } else { - write!(f, "{}''{}", self.charset, encoded_value) - } - } -} - -/// Percent encode a sequence of bytes with a character set defined in -/// -pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { - let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); - fmt::Display::fmt(&encoded, f) -} - -/// Convert http::HeaderMap to a HeaderMap +/// Convert `http::HeaderMap` to our `HeaderMap`. impl From for HeaderMap { fn from(map: http::HeaderMap) -> HeaderMap { let mut new_map = HeaderMap::with_capacity(map.capacity()); @@ -388,7 +81,7 @@ impl From for HeaderMap { } // This encode set is used for HTTP header values and is defined at -// https://tools.ietf.org/html/rfc5987#section-3.2 +// https://tools.ietf.org/html/rfc5987#section-3.2. pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b' ') .add(b'"') @@ -410,91 +103,3 @@ pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b']') .add(b'{') .add(b'}'); - -#[cfg(test)] -mod tests { - use super::shared::Charset; - use super::{parse_extended_value, ExtendedValue}; - use language_tags::LanguageTag; - - #[test] - fn test_parse_extended_value_with_encoding_and_language_tag() { - let expected_language_tag = "en".parse::().unwrap(); - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode character U+00A3 (POUND SIGN) - let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Iso_8859_1, extended_value.charset); - assert!(extended_value.language_tag.is_some()); - assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); - assert_eq!( - vec![163, b' ', b'r', b'a', b't', b'e', b's'], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_with_encoding() { - // RFC 5987, Section 3.2.2 - // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) - // and U+20AC (EURO SIGN) - let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); - assert!(result.is_ok()); - let extended_value = result.unwrap(); - assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); - assert!(extended_value.language_tag.is_none()); - assert_eq!( - vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - extended_value.value - ); - } - - #[test] - fn test_parse_extended_value_missing_language_tag_and_encoding() { - // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 - let result = parse_extended_value("foo%20bar.html"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted() { - let result = parse_extended_value("UTF-8'missing third part"); - assert!(result.is_err()); - } - - #[test] - fn test_parse_extended_value_partially_formatted_blank() { - let result = parse_extended_value("blank second part'"); - assert!(result.is_err()); - } - - #[test] - fn test_fmt_extended_value_with_encoding_and_language_tag() { - let extended_value = ExtendedValue { - charset: Charset::Iso_8859_1, - language_tag: Some("en".parse().expect("Could not parse language tag")), - value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], - }; - assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); - } - - #[test] - fn test_fmt_extended_value_with_encoding() { - let extended_value = ExtendedValue { - charset: Charset::Ext("UTF-8".to_string()), - language_tag: None, - value: vec![ - 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', - b't', b'e', b's', - ], - }; - assert_eq!( - "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", - format!("{}", extended_value) - ); - } -} diff --git a/actix-http/src/header/shared/entity.rs b/actix-http/src/header/shared/entity.rs index 344cfb864..eb383cd6f 100644 --- a/actix-http/src/header/shared/entity.rs +++ b/actix-http/src/header/shared/entity.rs @@ -161,7 +161,7 @@ impl FromStr for EntityTag { impl IntoHeaderValue for EntityTag { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut wrt = Writer::new(); write!(wrt, "{}", self).unwrap(); HeaderValue::from_maybe_shared(wrt.take()) diff --git a/actix-http/src/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs new file mode 100644 index 000000000..6bdcb7922 --- /dev/null +++ b/actix-http/src/header/shared/extended.rs @@ -0,0 +1,193 @@ +use std::{fmt, str::FromStr}; + +use language_tags::LanguageTag; + +use crate::header::{Charset, HTTP_VALUE}; + +// From hyper v0.11.27 src/header/parsing.rs + +/// The value part of an extended parameter consisting of three parts: +/// - The REQUIRED character set name (`charset`). +/// - The OPTIONAL language information (`language_tag`). +/// - A character sequence representing the actual value (`value`), separated by single quotes. +/// +/// It is defined in [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +#[derive(Clone, Debug, PartialEq)] +pub struct ExtendedValue { + /// The character set that is used to encode the `value` to a string. + pub charset: Charset, + + /// The human language details of the `value`, if available. + pub language_tag: Option, + + /// The parameter value, as expressed in octets. + pub value: Vec, +} + +/// Parses extended header parameter values (`ext-value`), as defined in +/// [RFC 5987](https://tools.ietf.org/html/rfc5987#section-3.2). +/// +/// Extended values are denoted by parameter names that end with `*`. +/// +/// ## ABNF +/// +/// ```text +/// ext-value = charset "'" [ language ] "'" value-chars +/// ; like RFC 2231's +/// ; (see [RFC2231], Section 7) +/// +/// charset = "UTF-8" / "ISO-8859-1" / mime-charset +/// +/// mime-charset = 1*mime-charsetc +/// mime-charsetc = ALPHA / DIGIT +/// / "!" / "#" / "$" / "%" / "&" +/// / "+" / "-" / "^" / "_" / "`" +/// / "{" / "}" / "~" +/// ; as in Section 2.3 of [RFC2978] +/// ; except that the single quote is not included +/// ; SHOULD be registered in the IANA charset registry +/// +/// language = +/// +/// value-chars = *( pct-encoded / attr-char ) +/// +/// pct-encoded = "%" HEXDIG HEXDIG +/// ; see [RFC3986], Section 2.1 +/// +/// attr-char = ALPHA / DIGIT +/// / "!" / "#" / "$" / "&" / "+" / "-" / "." +/// / "^" / "_" / "`" / "|" / "~" +/// ; token except ( "*" / "'" / "%" ) +/// ``` +pub fn parse_extended_value( + val: &str, +) -> Result { + // Break into three pieces separated by the single-quote character + let mut parts = val.splitn(3, '\''); + + // Interpret the first piece as a Charset + let charset: Charset = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(n) => FromStr::from_str(n).map_err(|_| crate::error::ParseError::Header)?, + }; + + // Interpret the second piece as a language tag + let language_tag: Option = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some("") => None, + Some(s) => match s.parse() { + Ok(lt) => Some(lt), + Err(_) => return Err(crate::error::ParseError::Header), + }, + }; + + // Interpret the third piece as a sequence of value characters + let value: Vec = match parts.next() { + None => return Err(crate::error::ParseError::Header), + Some(v) => percent_encoding::percent_decode(v.as_bytes()).collect(), + }; + + Ok(ExtendedValue { + value, + charset, + language_tag, + }) +} + +impl fmt::Display for ExtendedValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + let encoded_value = + percent_encoding::percent_encode(&self.value[..], HTTP_VALUE); + if let Some(ref lang) = self.language_tag { + write!(f, "{}'{}'{}", self.charset, lang, encoded_value) + } else { + write!(f, "{}''{}", self.charset, encoded_value) + } + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_parse_extended_value_with_encoding_and_language_tag() { + let expected_language_tag = "en".parse::().unwrap(); + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode character U+00A3 (POUND SIGN) + let result = parse_extended_value("iso-8859-1'en'%A3%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Iso_8859_1, extended_value.charset); + assert!(extended_value.language_tag.is_some()); + assert_eq!(expected_language_tag, extended_value.language_tag.unwrap()); + assert_eq!( + vec![163, b' ', b'r', b'a', b't', b'e', b's'], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_with_encoding() { + // RFC 5987, Section 3.2.2 + // Extended notation, using the Unicode characters U+00A3 (POUND SIGN) + // and U+20AC (EURO SIGN) + let result = parse_extended_value("UTF-8''%c2%a3%20and%20%e2%82%ac%20rates"); + assert!(result.is_ok()); + let extended_value = result.unwrap(); + assert_eq!(Charset::Ext("UTF-8".to_string()), extended_value.charset); + assert!(extended_value.language_tag.is_none()); + assert_eq!( + vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + extended_value.value + ); + } + + #[test] + fn test_parse_extended_value_missing_language_tag_and_encoding() { + // From: https://greenbytes.de/tech/tc2231/#attwithfn2231quot2 + let result = parse_extended_value("foo%20bar.html"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted() { + let result = parse_extended_value("UTF-8'missing third part"); + assert!(result.is_err()); + } + + #[test] + fn test_parse_extended_value_partially_formatted_blank() { + let result = parse_extended_value("blank second part'"); + assert!(result.is_err()); + } + + #[test] + fn test_fmt_extended_value_with_encoding_and_language_tag() { + let extended_value = ExtendedValue { + charset: Charset::Iso_8859_1, + language_tag: Some("en".parse().expect("Could not parse language tag")), + value: vec![163, b' ', b'r', b'a', b't', b'e', b's'], + }; + assert_eq!("ISO-8859-1'en'%A3%20rates", format!("{}", extended_value)); + } + + #[test] + fn test_fmt_extended_value_with_encoding() { + let extended_value = ExtendedValue { + charset: Charset::Ext("UTF-8".to_string()), + language_tag: None, + value: vec![ + 194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', + b't', b'e', b's', + ], + }; + assert_eq!( + "UTF-8''%C2%A3%20and%20%E2%82%AC%20rates", + format!("{}", extended_value) + ); + } +} diff --git a/actix-http/src/header/shared/httpdate.rs b/actix-http/src/header/shared/httpdate.rs index d6b9d8001..72a225589 100644 --- a/actix-http/src/header/shared/httpdate.rs +++ b/actix-http/src/header/shared/httpdate.rs @@ -48,7 +48,7 @@ impl From for HttpDate { impl IntoHeaderValue for HttpDate { type Error = InvalidHeaderValue; - fn try_into(self) -> Result { + fn try_into_value(self) -> Result { let mut wrt = BytesMut::with_capacity(29).writer(); write!( wrt, diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs index f2bc91634..72161e46b 100644 --- a/actix-http/src/header/shared/mod.rs +++ b/actix-http/src/header/shared/mod.rs @@ -1,14 +1,16 @@ -//! Copied for `hyper::header::shared`; - -pub use self::charset::Charset; -pub use self::encoding::Encoding; -pub use self::entity::EntityTag; -pub use self::httpdate::HttpDate; -pub use self::quality_item::{q, qitem, Quality, QualityItem}; -pub use language_tags::LanguageTag; +//! Originally taken from `hyper::header::shared`. mod charset; mod encoding; mod entity; +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::extended::{parse_extended_value, ExtendedValue}; +pub use self::httpdate::HttpDate; +pub use self::quality_item::{q, qitem, Quality, QualityItem}; +pub use language_tags::LanguageTag; diff --git a/actix-http/src/header/utils.rs b/actix-http/src/header/utils.rs new file mode 100644 index 000000000..e232d462f --- /dev/null +++ b/actix-http/src/header/utils.rs @@ -0,0 +1,63 @@ +use std::{fmt, str::FromStr}; + +use http::HeaderValue; + +use crate::{error::ParseError, header::HTTP_VALUE}; + +/// Reads a comma-delimited raw header into a Vec. +#[inline] +pub fn from_comma_delimited<'a, I, T>(all: I) -> Result, ParseError> +where + I: Iterator + 'a, + T: FromStr, +{ + let mut result = Vec::new(); + for h in all { + let s = h.to_str().map_err(|_| ParseError::Header)?; + result.extend( + s.split(',') + .filter_map(|x| match x.trim() { + "" => None, + y => Some(y), + }) + .filter_map(|x| x.trim().parse().ok()), + ) + } + Ok(result) +} + +/// Reads a single string when parsing a header. +#[inline] +pub fn from_one_raw_str(val: Option<&HeaderValue>) -> Result { + if let Some(line) = val { + let line = line.to_str().map_err(|_| ParseError::Header)?; + if !line.is_empty() { + return T::from_str(line).or(Err(ParseError::Header)); + } + } + Err(ParseError::Header) +} + +/// Format an array into a comma-delimited string. +#[inline] +pub fn fmt_comma_delimited(f: &mut fmt::Formatter<'_>, parts: &[T]) -> fmt::Result +where + T: fmt::Display, +{ + let mut iter = parts.iter(); + if let Some(part) = iter.next() { + fmt::Display::fmt(part, f)?; + } + for part in iter { + f.write_str(", ")?; + fmt::Display::fmt(part, f)?; + } + Ok(()) +} + +/// Percent encode a sequence of bytes with a character set defined in +/// +pub fn http_percent_encode(f: &mut fmt::Formatter<'_>, bytes: &[u8]) -> fmt::Result { + let encoded = percent_encoding::percent_encode(bytes, HTTP_VALUE); + fmt::Display::fmt(&encoded, f) +} diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/httpmessage.rs index 471fbbcdc..2610b8784 100644 --- a/actix-http/src/httpmessage.rs +++ b/actix-http/src/httpmessage.rs @@ -13,7 +13,7 @@ use crate::payload::Payload; struct Cookies(Vec>); -/// Trait that implements general purpose operations on http messages +/// Trait that implements general purpose operations on HTTP messages. pub trait HttpMessage: Sized { /// Type of message payload stream type Stream; @@ -30,8 +30,8 @@ pub trait HttpMessage: Sized { /// Mutable reference to a the request's extensions container fn extensions_mut(&self) -> RefMut<'_, Extensions>; + /// Get a header. #[doc(hidden)] - /// Get a header fn get_header(&self) -> Option where Self: Sized, @@ -43,8 +43,8 @@ pub trait HttpMessage: Sized { } } - /// Read the request content type. If request does not contain - /// *Content-Type* header, empty str get returned. + /// Read the request content type. If request did not contain a *Content-Type* header, an empty + /// string is returned. fn content_type(&self) -> &str { if let Some(content_type) = self.headers().get(header::CONTENT_TYPE) { if let Ok(content_type) = content_type.to_str() { @@ -90,7 +90,7 @@ pub trait HttpMessage: Sized { Ok(None) } - /// Check if request has chunked transfer encoding + /// Check if request has chunked transfer encoding. fn chunked(&self) -> Result { if let Some(encodings) = self.headers().get(header::TRANSFER_ENCODING) { if let Ok(s) = encodings.to_str() { @@ -173,11 +173,13 @@ mod tests { #[test] fn test_content_type() { - let req = TestRequest::with_header("content-type", "text/plain").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "text/plain")) + .finish(); assert_eq!(req.content_type(), "text/plain"); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf=8") - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=utf=8")) + .finish(); assert_eq!(req.content_type(), "application/json"); let req = TestRequest::default().finish(); assert_eq!(req.content_type(), ""); @@ -185,13 +187,15 @@ mod tests { #[test] fn test_mime_type() { - let req = TestRequest::with_header("content-type", "application/json").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json")) + .finish(); assert_eq!(req.mime_type().unwrap(), Some(mime::APPLICATION_JSON)); let req = TestRequest::default().finish(); assert_eq!(req.mime_type().unwrap(), None); - let req = - TestRequest::with_header("content-type", "application/json; charset=utf-8") - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=utf-8")) + .finish(); let mt = req.mime_type().unwrap().unwrap(); assert_eq!(mt.get_param(mime::CHARSET), Some(mime::UTF_8)); assert_eq!(mt.type_(), mime::APPLICATION); @@ -200,11 +204,9 @@ mod tests { #[test] fn test_mime_type_error() { - let req = TestRequest::with_header( - "content-type", - "applicationadfadsfasdflknadsfklnadsfjson", - ) - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "applicationadfadsfasdflknadsfklnadsfjson")) + .finish(); assert_eq!(Err(ContentTypeError::ParseError), req.mime_type()); } @@ -213,27 +215,27 @@ mod tests { let req = TestRequest::default().finish(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - let req = TestRequest::with_header("content-type", "application/json").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json")) + .finish(); assert_eq!(UTF_8.name(), req.encoding().unwrap().name()); - let req = TestRequest::with_header( - "content-type", - "application/json; charset=ISO-8859-2", - ) - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=ISO-8859-2")) + .finish(); assert_eq!(ISO_8859_2, req.encoding().unwrap()); } #[test] fn test_encoding_error() { - let req = TestRequest::with_header("content-type", "applicatjson").finish(); + let req = TestRequest::default() + .insert_header(("content-type", "applicatjson")) + .finish(); assert_eq!(Some(ContentTypeError::ParseError), req.encoding().err()); - let req = TestRequest::with_header( - "content-type", - "application/json; charset=kkkttktk", - ) - .finish(); + let req = TestRequest::default() + .insert_header(("content-type", "application/json; charset=kkkttktk")) + .finish(); assert_eq!( Some(ContentTypeError::UnknownEncoding), req.encoding().err() @@ -245,15 +247,16 @@ mod tests { let req = TestRequest::default().finish(); assert!(!req.chunked().unwrap()); - let req = - TestRequest::with_header(header::TRANSFER_ENCODING, "chunked").finish(); + let req = TestRequest::default() + .insert_header((header::TRANSFER_ENCODING, "chunked")) + .finish(); assert!(req.chunked().unwrap()); let req = TestRequest::default() - .header( + .insert_header(( header::TRANSFER_ENCODING, Bytes::from_static(b"some va\xadscc\xacas0xsdasdlue"), - ) + )) .finish(); assert!(req.chunked().is_err()); } diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index e17b7de0a..cc99130eb 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -53,7 +53,7 @@ pub use self::response::{Response, ResponseBuilder}; pub use self::service::HttpService; pub mod http { - //! Various HTTP related types + //! Various HTTP related types. // re-exports pub use http::header::{HeaderName, HeaderValue}; @@ -64,7 +64,7 @@ pub mod http { pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::header::HeaderMap; - /// Various http headers + /// A collection of HTTP headers and helpers. pub mod header { pub use crate::header::*; } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 0bc84a44e..1031d7dce 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -1,5 +1,9 @@ -use std::cell::{Ref, RefMut}; -use std::{fmt, net}; +//! HTTP requests. + +use std::{ + cell::{Ref, RefMut}, + fmt, net, +}; use http::{header, Method, Uri, Version}; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 0a1f2cfd2..880f93c6a 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -1,10 +1,14 @@ -//! Http response -use std::cell::{Ref, RefMut}; -use std::convert::TryFrom; -use std::future::Future; -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, str}; +//! HTTP responses. + +use std::{ + cell::{Ref, RefMut}, + convert::TryInto, + fmt, + future::Future, + pin::Pin, + str, + task::{Context, Poll}, +}; use bytes::{Bytes, BytesMut}; use futures_core::Stream; @@ -14,7 +18,7 @@ use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; -use crate::header::{Header, IntoHeaderValue}; +use crate::header::{IntoHeaderPair, IntoHeaderValue}; use crate::http::header::{self, HeaderName, HeaderValue}; use crate::http::{Error as HttpError, HeaderMap, StatusCode}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; @@ -341,93 +345,96 @@ impl ResponseBuilder { self } - /// Set a header. + /// Insert a header, replacing any that were set with an equivalent field name. /// /// ```rust - /// use actix_http::{http, Request, Response, Result}; + /// # use actix_http::Response; + /// use actix_http::http::header::ContentType; /// - /// fn index(req: Request) -> Result { - /// Ok(Response::Ok() - /// .set(http::header::IfModifiedSince( - /// "Sun, 07 Nov 1994 08:48:37 GMT".parse()?, - /// )) - /// .finish()) - /// } + /// Response::Ok() + /// .insert_header(ContentType(mime::APPLICATION_JSON)) + /// .insert_header(("X-TEST", "value")) + /// .finish(); /// ``` - #[doc(hidden)] - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Some(parts) = parts(&mut self.head, &self.err) { - match hdr.try_into() { - Ok(value) => { - parts.headers.append(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - } - self - } - - /// Append a header to existing headers. - /// - /// ```rust - /// use actix_http::{http, Request, Response}; - /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } - /// ``` - pub fn header(&mut self, key: K, value: V) -> &mut Self + pub fn insert_header(&mut self, header: H) -> &mut Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.append(key, value); - } - Err(e) => self.err = Some(e.into()), - }, + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.insert(key, value), Err(e) => self.err = Some(e.into()), }; } + self } - /// Set a header. + /// Append a header, keeping any that were set with an equivalent field name. /// /// ```rust - /// use actix_http::{http, Request, Response}; + /// # use actix_http::Response; + /// use actix_http::http::header::ContentType; /// - /// fn index(req: Request) -> Response { - /// Response::Ok() - /// .set_header("X-TEST", "value") - /// .set_header(http::header::CONTENT_TYPE, "application/json") - /// .finish() - /// } + /// Response::Ok() + /// .append_header(ContentType(mime::APPLICATION_JSON)) + /// .append_header(("X-TEST", "value1")) + /// .append_header(("X-TEST", "value2")) + /// .finish(); /// ``` + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + if let Some(parts) = parts(&mut self.head, &self.err) { + match header.try_into_header_pair() { + Ok((key, value)) => parts.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + } + + self + } + + /// Replaced with [`Self::insert_header()`]. + #[deprecated = "Replaced with `insert_header((key, value))`."] pub fn set_header(&mut self, key: K, value: V) -> &mut Self where - HeaderName: TryFrom, - >::Error: Into, + K: TryInto, + K::Error: Into, V: IntoHeaderValue, { - if let Some(parts) = parts(&mut self.head, &self.err) { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - parts.headers.insert(key, value); - } - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - }; + if self.err.is_some() { + return self; } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.insert_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + + self + } + + /// Replaced with [`Self::append_header()`]. + #[deprecated = "Replaced with `append_header((key, value))`."] + pub fn header(&mut self, key: K, value: V) -> &mut Self + where + K: TryInto, + K::Error: Into, + V: IntoHeaderValue, + { + if self.err.is_some() { + return self; + } + + match (key.try_into(), value.try_into_value()) { + (Ok(name), Ok(value)) => return self.append_header((name, value)), + (Err(err), _) => self.err = Some(err.into()), + (_, Err(err)) => self.err = Some(err.into()), + } + self } @@ -458,7 +465,12 @@ impl ResponseBuilder { if let Some(parts) = parts(&mut self.head, &self.err) { parts.set_connection_type(ConnectionType::Upgrade); } - self.set_header(header::UPGRADE, value) + + if let Ok(value) = value.try_into_value() { + self.insert_header((header::UPGRADE, value)); + } + + self } /// Force close connection, even if it is marked as keep-alive @@ -473,7 +485,7 @@ impl ResponseBuilder { /// Disable chunked transfer encoding for HTTP/1.1 streaming responses. #[inline] pub fn no_chunking(&mut self, len: u64) -> &mut Self { - self.header(header::CONTENT_LENGTH, len); + self.insert_header((header::CONTENT_LENGTH, len)); if let Some(parts) = parts(&mut self.head, &self.err) { parts.no_chunking(true); @@ -488,7 +500,7 @@ impl ResponseBuilder { V: IntoHeaderValue, { if let Some(parts) = parts(&mut self.head, &self.err) { - match value.try_into() { + match value.try_into_value() { Ok(value) => { parts.headers.insert(header::CONTENT_TYPE, value); } @@ -658,8 +670,9 @@ impl ResponseBuilder { } else { true }; + if !contains { - self.header(header::CONTENT_TYPE, "application/json"); + self.insert_header(header::ContentType(mime::APPLICATION_JSON)); } self.body(Body::from(body)) @@ -848,6 +861,8 @@ impl From for Response { #[cfg(test)] mod tests { + use serde_json::json; + use super::*; use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; @@ -855,8 +870,8 @@ mod tests { #[test] fn test_debug() { let resp = Response::Ok() - .header(COOKIE, HeaderValue::from_static("cookie1=value1; ")) - .header(COOKIE, HeaderValue::from_static("cookie2=value2; ")) + .append_header((COOKIE, HeaderValue::from_static("cookie1=value1; "))) + .append_header((COOKIE, HeaderValue::from_static("cookie2=value2; "))) .finish(); let dbg = format!("{:?}", resp); assert!(dbg.contains("Response")); @@ -867,8 +882,8 @@ mod tests { use crate::httpmessage::HttpMessage; let req = crate::test::TestRequest::default() - .header(COOKIE, "cookie1=value1") - .header(COOKIE, "cookie2=value2") + .append_header((COOKIE, "cookie1=value1")) + .append_header((COOKIE, "cookie2=value2")) .finish(); let cookies = req.cookies().unwrap(); @@ -922,7 +937,7 @@ mod tests { #[test] fn test_basic_builder() { - let resp = Response::Ok().header("X-TEST", "value").finish(); + let resp = Response::Ok().insert_header(("X-TEST", "value")).finish(); assert_eq!(resp.status(), StatusCode::OK); } @@ -963,7 +978,7 @@ mod tests { #[test] fn test_json_ct() { let resp = Response::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") + .insert_header((CONTENT_TYPE, "text/json")) .json(vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); @@ -981,7 +996,7 @@ mod tests { #[test] fn test_json2_ct() { let resp = Response::build(StatusCode::OK) - .header(CONTENT_TYPE, "text/json") + .insert_header((CONTENT_TYPE, "text/json")) .json2(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); @@ -1081,4 +1096,54 @@ mod tests { let cookie = resp.cookies().next().unwrap(); assert_eq!((cookie.name(), cookie.value()), ("cookie1", "val100")); } + + #[test] + fn response_builder_header_insert_kv() { + let mut res = Response::Ok(); + res.insert_header(("Content-Type", "application/octet-stream")); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_insert_typed() { + let mut res = Response::Ok(); + res.insert_header(header::ContentType(mime::APPLICATION_OCTET_STREAM)); + let res = res.finish(); + + assert_eq!( + res.headers().get("Content-Type"), + Some(&HeaderValue::from_static("application/octet-stream")) + ); + } + + #[test] + fn response_builder_header_append_kv() { + let mut res = Response::Ok(); + res.append_header(("Content-Type", "application/octet-stream")); + res.append_header(("Content-Type", "application/json")); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } + + #[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)); + let res = res.finish(); + + let headers: Vec<_> = res.headers().get_all("Content-Type").cloned().collect(); + assert_eq!(headers.len(), 2); + assert!(headers.contains(&HeaderValue::from_static("application/octet-stream"))); + assert!(headers.contains(&HeaderValue::from_static("application/json"))); + } } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 3f08bb7ee..8f0a7d21a 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -2,7 +2,6 @@ use std::{ cell::{Ref, RefCell}, - convert::TryFrom, io::{self, Read, Write}, pin::Pin, rc::Rc, @@ -12,14 +11,17 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; -use http::header::{self, HeaderName, HeaderValue}; -use http::{Error as HttpError, Method, Uri, Version}; +use http::{ + header::{self, HeaderValue}, + Method, Uri, Version, +}; -use crate::cookie::{Cookie, CookieJar}; -use crate::header::HeaderMap; -use crate::header::{Header, IntoHeaderValue}; -use crate::payload::Payload; -use crate::Request; +use crate::{ + cookie::{Cookie, CookieJar}, + header::{HeaderMap, IntoHeaderPair}, + payload::Payload, + Request, +}; /// Test `Request` builder /// @@ -36,7 +38,7 @@ use crate::Request; /// } /// } /// -/// let resp = TestRequest::with_header("content-type", "text/plain") +/// let resp = TestRequest::default().insert_header("content-type", "text/plain") /// .run(&index) /// .unwrap(); /// assert_eq!(resp.status(), StatusCode::OK); @@ -69,76 +71,73 @@ impl Default for TestRequest { } impl TestRequest { - /// Create TestRequest and set request uri + /// Create a default TestRequest and then set its URI. pub fn with_uri(path: &str) -> TestRequest { TestRequest::default().uri(path).take() } - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr).take() - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - TestRequest::default().header(key, value).take() - } - - /// Set HTTP version of this request + /// Set HTTP version of this request. pub fn version(&mut self, ver: Version) -> &mut Self { parts(&mut self.0).version = ver; self } - /// Set HTTP method of this request + /// Set HTTP method of this request. pub fn method(&mut self, meth: Method) -> &mut Self { parts(&mut self.0).method = meth; self } - /// Set HTTP Uri of this request + /// Set URI of this request. + /// + /// # Panics + /// If provided URI is invalid. pub fn uri(&mut self, path: &str) -> &mut Self { parts(&mut self.0).uri = Uri::from_str(path).unwrap(); self } - /// Set a header - pub fn set(&mut self, hdr: H) -> &mut Self { - if let Ok(value) = hdr.try_into() { - parts(&mut self.0).headers.append(H::name(), value); - return self; - } - panic!("Can not set header"); - } - - /// Set a header - pub fn header(&mut self, key: K, value: V) -> &mut Self + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(&mut self, header: H) -> &mut 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() { - parts(&mut self.0).headers.append(key, value); - return self; + match header.try_into_header_pair() { + Ok((key, value)) => { + parts(&mut self.0).headers.insert(key, value); + } + Err(err) => { + panic!("Error inserting test header: {}.", err.into()); } } - panic!("Can not create header"); + + self } - /// Set cookie for this request + /// Append a header, keeping any that were set with an equivalent field name. + pub fn append_header(&mut self, header: H) -> &mut Self + where + H: IntoHeaderPair, + { + match header.try_into_header_pair() { + Ok((key, value)) => { + parts(&mut self.0).headers.append(key, value); + } + Err(err) => { + panic!("Error inserting test header: {}.", err.into()); + } + } + + self + } + + /// Set cookie for this request. pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self } - /// Set request payload + /// Set request payload. pub fn set_payload>(&mut self, data: B) -> &mut Self { let mut payload = crate::h1::Payload::empty(); payload.unread_data(data.into()); @@ -150,7 +149,7 @@ impl TestRequest { TestRequest(self.0.take()) } - /// Complete request creation and generate `Request` instance + /// Complete request creation and generate `Request` instance. pub fn finish(&mut self) -> Request { let inner = self.0.take().expect("cannot reuse test request builder"); diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index a2b093ce4..f8142693f 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -101,7 +101,7 @@ impl ResponseError for HandshakeError { fn error_response(&self) -> Response { match self { HandshakeError::GetMethodRequired => Response::MethodNotAllowed() - .header(header::ALLOW, "GET") + .insert_header((header::ALLOW, "GET")) .finish(), HandshakeError::NoWebsocketUpgrade => Response::BadRequest() @@ -198,8 +198,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { Response::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .header(header::TRANSFER_ENCODING, "chunked") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) + .insert_header((header::TRANSFER_ENCODING, "chunked")) + .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) .take() } @@ -224,7 +224,7 @@ mod tests { ); let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) + .insert_header((header::UPGRADE, header::HeaderValue::from_static("test"))) .finish(); assert_eq!( HandshakeError::NoWebsocketUpgrade, @@ -232,10 +232,10 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) + )) .finish(); assert_eq!( HandshakeError::NoConnectionUpgrade, @@ -243,14 +243,14 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) + )) .finish(); assert_eq!( HandshakeError::NoVersionHeader, @@ -258,18 +258,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ) + )) .finish(); assert_eq!( HandshakeError::UnsupportedVersion, @@ -277,18 +277,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) + )) .finish(); assert_eq!( HandshakeError::BadWebsocketKey, @@ -296,22 +296,22 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) + )) .finish(); assert_eq!( StatusCode::SWITCHING_PROTOCOLS, diff --git a/actix-http/tests/test_client.rs b/actix-http/tests/test_client.rs index f78636b9a..91b2412f4 100644 --- a/actix-http/tests/test_client.rs +++ b/actix-http/tests/test_client.rs @@ -38,7 +38,7 @@ async fn test_h1_v2() { let response = srv.get("/").send().await.unwrap(); assert!(response.status().is_success()); - let request = srv.get("/").header("x-test", "111").send(); + let request = srv.get("/").insert_header(("x-test", "111")).send(); let mut response = request.await.unwrap(); assert!(response.status().is_success()); diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index bb4732281..fe7825d78 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -173,8 +173,8 @@ async fn test_h2_headers() { HttpService::build().h2(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { - builder.header( - format!("X-TEST-{}", idx).as_str(), + builder.insert_header( + (format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ @@ -188,7 +188,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); + )); } ok::<_, ()>(builder.body(data.clone())) }) @@ -341,7 +341,7 @@ async fn test_h2_body_chunked_explicit() { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") + .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) }) @@ -369,7 +369,7 @@ async fn test_h2_response_http_error_handling() { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(header::CONTENT_TYPE, broken_header) + .insert_header((header::CONTENT_TYPE, broken_header)) .body(STR), ) })) diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index beae359d9..2f6b31f49 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -181,7 +181,7 @@ async fn test_h2_headers() { HttpService::build().h2(move |_| { let mut config = Response::Ok(); for idx in 0..90 { - config.header( + config.insert_header(( format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ @@ -196,7 +196,7 @@ async fn test_h2_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); + )); } future::ok::<_, ()>(config.body(data.clone())) }) @@ -352,7 +352,7 @@ async fn test_h2_body_chunked_explicit() { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") + .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) }) @@ -380,7 +380,7 @@ async fn test_h2_response_http_error_handling() { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) + .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) })) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index fa1aeb695..e1cfe9db4 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -392,7 +392,7 @@ async fn test_h1_headers() { HttpService::build().h1(move |_| { let mut builder = Response::Ok(); for idx in 0..90 { - builder.header( + builder.insert_header(( format!("X-TEST-{}", idx).as_str(), "TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ @@ -407,7 +407,7 @@ async fn test_h1_headers() { TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \ TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ", - ); + )); } future::ok::<_, ()>(builder.body(data.clone())) }).tcp() @@ -561,7 +561,7 @@ async fn test_h1_body_chunked_explicit() { let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref()))); ok::<_, ()>( Response::Ok() - .header(header::TRANSFER_ENCODING, "chunked") + .insert_header((header::TRANSFER_ENCODING, "chunked")) .streaming(body), ) }) @@ -625,7 +625,7 @@ async fn test_h1_response_http_error_handling() { let broken_header = Bytes::from_static(b"\0\0\0"); ok::<_, ()>( Response::Ok() - .header(http::header::CONTENT_TYPE, broken_header) + .insert_header((http::header::CONTENT_TYPE, broken_header)) .body(STR), ) })) diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 60942c6c6..10113665b 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -166,11 +166,11 @@ pub fn handshake_with_protocols( let mut response = HttpResponse::build(StatusCode::SWITCHING_PROTOCOLS) .upgrade("websocket") - .header(header::SEC_WEBSOCKET_ACCEPT, key.as_str()) + .insert_header((header::SEC_WEBSOCKET_ACCEPT, key)) .take(); if let Some(protocol) = protocol { - response.header(&header::SEC_WEBSOCKET_PROTOCOL, protocol); + response.insert_header((header::SEC_WEBSOCKET_PROTOCOL, protocol)); } Ok(response) @@ -573,7 +573,7 @@ mod tests { ); let req = TestRequest::default() - .header(header::UPGRADE, header::HeaderValue::from_static("test")) + .insert_header((header::UPGRADE, header::HeaderValue::from_static("test"))) .to_http_request(); assert_eq!( HandshakeError::NoWebsocketUpgrade, @@ -581,10 +581,10 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::NoConnectionUpgrade, @@ -592,14 +592,14 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::NoVersionHeader, @@ -607,18 +607,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("5"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::UnsupportedVersion, @@ -626,18 +626,18 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) + )) .to_http_request(); assert_eq!( HandshakeError::BadWebsocketKey, @@ -645,22 +645,22 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) + )) .to_http_request(); let resp = handshake(&req).unwrap().finish(); @@ -669,26 +669,26 @@ mod tests { assert_eq!(None, resp.headers().get(&header::TRANSFER_ENCODING)); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_PROTOCOL, header::HeaderValue::from_static("graphql"), - ) + )) .to_http_request(); let protocols = ["graphql"]; @@ -710,26 +710,26 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_PROTOCOL, header::HeaderValue::from_static("p1, p2, p3"), - ) + )) .to_http_request(); let protocols = vec!["p3", "p2"]; @@ -751,26 +751,26 @@ mod tests { ); let req = TestRequest::default() - .header( + .insert_header(( header::UPGRADE, header::HeaderValue::from_static("websocket"), - ) - .header( + )) + .insert_header(( header::CONNECTION, header::HeaderValue::from_static("upgrade"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_VERSION, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_KEY, header::HeaderValue::from_static("13"), - ) - .header( + )) + .insert_header(( header::SEC_WEBSOCKET_PROTOCOL, header::HeaderValue::from_static("p1,p2,p3"), - ) + )) .to_http_request(); let protocols = vec!["p3", "p2"]; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 89b6121f3..c5c1185be 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,17 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `ClientRequest::insert_header` method which allows using typed headers. [#1869] +* `ClientRequest::append_header` method which allows using typed headers. [#1869] + +### Removed +* `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] +* `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] +* `ClientRequest::set_header_if_none`; use `ClientRequest::insert_header_if_none`. [#1869] +* `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] + +[#1869]: https://github.com/actix/actix-web/pull/1869 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 6be0112d8..351a493af 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -133,7 +133,7 @@ impl ClientBuilder { V::Error: fmt::Debug, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => { self.headers.append(key, value); } diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index f7098863c..878f404c6 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -144,7 +144,7 @@ impl FrozenSendBuilder { V: IntoHeaderValue, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => self.extra_headers.insert(key, value), Err(e) => self.err = Some(e.into()), }, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index aad6ec38b..f750a3df2 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -7,7 +7,7 @@ //! # async fn main() -> Result<(), awc::error::SendRequestError> { //! let mut client = awc::Client::default(); //! let response = client.get("http://www.rust-lang.org") // <- Create request builder -//! .header("User-Agent", "Actix-web") +//! .insert_header(("User-Agent", "Actix-web")) //! .send() // <- Send http request //! .await?; //! @@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper}; /// let mut client = Client::default(); /// /// let res = client.get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") +/// .insert_header(("User-Agent", "Actix-web")) /// .send() // <- Send http request /// .await; // <- send request and wait for response /// @@ -182,8 +182,8 @@ impl Client { { let mut req = ClientRequest::new(method, url, self.0.clone()); - for (key, value) in self.0.headers.iter() { - req = req.set_header_if_none(key.clone(), value.clone()); + for header in self.0.headers.iter() { + req = req.insert_header_if_none(header); } req } @@ -198,8 +198,8 @@ impl Client { >::Error: Into, { let mut req = self.request(head.method.clone(), url); - for (key, value) in head.headers.iter() { - req = req.set_header_if_none(key.clone(), value.clone()); + for header in head.headers.iter() { + req = req.insert_header_if_none(header); } req } diff --git a/awc/src/request.rs b/awc/src/request.rs index 51c3f5190..b9a333b18 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -9,10 +9,10 @@ use serde::Serialize; use actix_http::body::Body; use actix_http::cookie::{Cookie, CookieJar}; -use actix_http::http::header::{self, Header, IntoHeaderValue}; +use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderName, HeaderValue, Method, - Uri, Version, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, + Version, }; use actix_http::{Error, RequestHead}; @@ -37,13 +37,11 @@ cfg_if::cfg_if! { /// builder-like pattern. /// /// ```rust -/// use actix_rt::System; -/// /// #[actix_rt::main] /// async fn main() { /// let response = awc::Client::new() /// .get("http://www.rust-lang.org") // <- Create request builder -/// .header("User-Agent", "Actix-web") +/// .insert_header(("User-Agent", "Actix-web")) /// .send() // <- Send http request /// .await; /// @@ -143,110 +141,71 @@ impl ClientRequest { &self.head.peer_addr } - #[inline] /// Returns request's headers. + #[inline] pub fn headers(&self) -> &HeaderMap { &self.head.headers } - #[inline] /// Returns request's mutable headers. + #[inline] pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head.headers } - /// Set a header. - /// - /// ```rust - /// fn main() { - /// # actix_rt::System::new("test").block_on(futures_util::future::lazy(|_| { - /// let req = awc::Client::new() - /// .get("http://www.rust-lang.org") - /// .set(awc::http::header::Date::now()) - /// .set(awc::http::header::ContentType(mime::TEXT_HTML)); - /// # Ok::<_, ()>(()) - /// # })); - /// } - /// ``` - pub fn set(mut self, hdr: H) -> Self { - match hdr.try_into() { - Ok(value) => { - self.head.headers.insert(H::name(), value); - } - Err(e) => self.err = Some(e.into()), - } - self - } - - /// Append a header. - /// - /// Header gets appended to existing header. - /// To override header use `set_header()` method. - /// - /// ```rust - /// use awc::{http, Client}; - /// - /// fn main() { - /// # actix_rt::System::new("test").block_on(async { - /// let req = Client::new() - /// .get("http://www.rust-lang.org") - /// .header("X-TEST", "value") - /// .header(http::header::CONTENT_TYPE, "application/json"); - /// # Ok::<_, ()>(()) - /// # }); - /// } - /// ``` - pub fn header(mut self, key: K, value: V) -> Self + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.head.headers.append(key, value), - Err(e) => self.err = Some(e.into()), - }, + match header.try_into_header_pair() { + Ok((key, value)) => self.head.headers.insert(key, value), Err(e) => self.err = Some(e.into()), - } - self - } + }; - /// Insert a header, replaces existing header. - pub fn set_header(mut self, key: K, value: V) -> Self - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => self.head.headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - }, - Err(e) => self.err = Some(e.into()), - } self } /// Insert a header only if it is not yet set. - pub fn set_header_if_none(mut self, key: K, value: V) -> Self + pub fn insert_header_if_none(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - match HeaderName::try_from(key) { - Ok(key) => { + match header.try_into_header_pair() { + Ok((key, value)) => { if !self.head.headers.contains_key(&key) { - match value.try_into() { - Ok(value) => self.head.headers.insert(key, value), - Err(e) => self.err = Some(e.into()), - } + self.head.headers.insert(key, value); } } Err(e) => self.err = Some(e.into()), - } + }; + + self + } + + /// Append a header, keeping any that were set with an equivalent field name. + /// + /// ```rust + /// # #[actix_rt::main] + /// # async fn main() { + /// # use awc::Client; + /// use awc::http::header::ContentType; + /// + /// Client::new() + /// .get("http://www.rust-lang.org") + /// .insert_header(("X-TEST", "value")) + /// .insert_header(ContentType(mime::APPLICATION_JSON)); + /// # } + /// ``` + pub fn append_header(mut self, header: H) -> Self + where + H: IntoHeaderPair, + { + match header.try_into_header_pair() { + Ok((key, value)) => self.head.headers.append(key, value), + Err(e) => self.err = Some(e.into()), + }; + self } @@ -282,7 +241,7 @@ impl ClientRequest { /// Set content length #[inline] pub fn content_length(self, len: u64) -> Self { - self.header(header::CONTENT_LENGTH, len) + self.append_header((header::CONTENT_LENGTH, len)) } /// Set HTTP basic authorization header @@ -294,10 +253,10 @@ impl ClientRequest { Some(password) => format!("{}:{}", username, password), None => format!("{}:", username), }; - self.header( + self.append_header(( header::AUTHORIZATION, format!("Basic {}", base64::encode(&auth)), - ) + )) } /// Set HTTP bearer authentication header @@ -305,7 +264,7 @@ impl ClientRequest { where T: fmt::Display, { - self.header(header::AUTHORIZATION, format!("Bearer {}", token)) + self.append_header((header::AUTHORIZATION, format!("Bearer {}", token))) } /// Set a cookie @@ -557,12 +516,15 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = slf.set_header_if_none(header::ACCEPT_ENCODING, HTTPS_ENCODING) + slf = + slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - slf = - slf.set_header_if_none(header::ACCEPT_ENCODING, "gzip, deflate") + slf = slf.insert_header_if_none(( + header::ACCEPT_ENCODING, + "gzip, deflate", + )) } }; } @@ -595,7 +557,7 @@ mod tests { #[actix_rt::test] async fn test_debug() { - let request = Client::new().get("/").header("x-test", "111"); + let request = Client::new().get("/").append_header(("x-test", "111")); let repr = format!("{:?}", request); assert!(repr.contains("ClientRequest")); assert!(repr.contains("x-test")); @@ -606,18 +568,18 @@ mod tests { let req = Client::new() .put("/") .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) + .insert_header(header::Date(SystemTime::now().into())) .content_type("plain/text") - .header(header::SERVER, "awc"); + .append_header((header::SERVER, "awc")); let req = if let Some(val) = Some("server") { - req.header(header::USER_AGENT, val) + req.append_header((header::USER_AGENT, val)) } else { req }; let req = if let Some(_val) = Option::<&str>::None { - req.header(header::ALLOW, "1") + req.append_header((header::ALLOW, "1")) } else { req }; @@ -660,7 +622,7 @@ mod tests { .header(header::CONTENT_TYPE, "111") .finish() .get("/") - .set_header(header::CONTENT_TYPE, "222"); + .insert_header((header::CONTENT_TYPE, "222")); assert_eq!( req.head diff --git a/awc/src/sender.rs b/awc/src/sender.rs index ebf87e23b..9fb821a0e 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -296,7 +296,7 @@ impl RequestSender { match self { RequestSender::Owned(head) => { if !head.headers.contains_key(&key) { - match value.try_into() { + match value.try_into_value() { Ok(value) => head.headers.insert(key, value), Err(e) => return Err(e.into()), } @@ -306,7 +306,7 @@ impl RequestSender { if !head.headers.contains_key(&key) && !extra_headers.iter().any(|h| h.contains_key(&key)) { - match value.try_into() { + match value.try_into_value() { Ok(v) => { let h = extra_headers.get_or_insert(HeaderMap::new()); h.insert(key, v) diff --git a/awc/src/test.rs b/awc/src/test.rs index 68e5c9dc5..84646b9f7 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -45,7 +45,7 @@ impl TestResponse { /// Set a header pub fn set(mut self, hdr: H) -> Self { - if let Ok(value) = hdr.try_into() { + if let Ok(value) = hdr.try_into_value() { self.head.headers.append(H::name(), value); return self; } @@ -60,7 +60,7 @@ impl TestResponse { V: IntoHeaderValue, { if let Ok(key) = HeaderName::try_from(key) { - if let Ok(value) = value.try_into() { + if let Ok(value) = value.try_into_value() { self.head.headers.append(key, value); return self; } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index f747f701f..17086cf2a 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -170,7 +170,7 @@ impl WebsocketsRequest { V: IntoHeaderValue, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => { self.head.headers.append(key, value); } @@ -189,7 +189,7 @@ impl WebsocketsRequest { V: IntoHeaderValue, { match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { + Ok(key) => match value.try_into_value() { Ok(value) => { self.head.headers.insert(key, value); } @@ -210,7 +210,7 @@ impl WebsocketsRequest { match HeaderName::try_from(key) { Ok(key) => { if !self.head.headers.contains_key(&key) { - match value.try_into() { + match value.try_into_value() { Ok(value) => { self.head.headers.insert(key, value); } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 6cae77a49..c31921560 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -52,7 +52,7 @@ async fn test_simple() { .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); - let request = srv.get("/").header("x-test", "111").send(); + let request = srv.get("/").insert_header(("x-test", "111")).send(); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -82,7 +82,7 @@ async fn test_json() { let request = srv .get("/") - .header("x-test", "111") + .insert_header(("x-test", "111")) .send_json(&"TEST".to_string()); let response = request.await.unwrap(); assert!(response.status().is_success()); @@ -99,7 +99,10 @@ async fn test_form() { let mut data = HashMap::new(); let _ = data.insert("key".to_string(), "TEST".to_string()); - let request = srv.get("/").header("x-test", "111").send_form(&data); + let request = srv + .get("/") + .append_header(("x-test", "111")) + .send_form(&data); let response = request.await.unwrap(); assert!(response.status().is_success()); } @@ -438,7 +441,7 @@ async fn test_client_gzip_encoding() { let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "gzip") + .insert_header(("content-encoding", "gzip")) .body(data) }))) }); @@ -461,7 +464,7 @@ async fn test_client_gzip_encoding_large() { let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "gzip") + .insert_header(("content-encoding", "gzip")) .body(data) }))) }); @@ -489,7 +492,7 @@ async fn test_client_gzip_encoding_large_random() { e.write_all(&data).unwrap(); let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "gzip") + .insert_header(("content-encoding", "gzip")) .body(data) }))) }); @@ -511,7 +514,7 @@ async fn test_client_brotli_encoding() { e.write_all(&data).unwrap(); let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "br") + .insert_header(("content-encoding", "br")) .body(data) }))) }); @@ -539,7 +542,7 @@ async fn test_client_brotli_encoding_large_random() { e.write_all(&data).unwrap(); let data = e.finish().unwrap(); HttpResponse::Ok() - .header("content-encoding", "br") + .insert_header(("content-encoding", "br")) .body(data) }))) }); diff --git a/examples/client.rs b/examples/client.rs index 15cf24fa8..b9574590d 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -10,7 +10,7 @@ async fn main() -> Result<(), Error> { // Create request builder, configure request and send let mut response = client .get("https://www.rust-lang.org/") - .header("User-Agent", "Actix-web") + .append_header(("User-Agent", "Actix-web")) .send() .await?; diff --git a/src/extract.rs b/src/extract.rs index 4081188ef..7a677bca4 100644 --- a/src/extract.rs +++ b/src/extract.rs @@ -347,25 +347,21 @@ mod tests { #[actix_rt::test] async fn test_option() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .data(FormConfig::default().limit(4096)) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .data(FormConfig::default().limit(4096)) + .to_http_parts(); let r = Option::>::from_request(&req, &mut pl) .await .unwrap(); assert_eq!(r, None); - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, "9")) + .set_payload(Bytes::from_static(b"hello=world")) + .to_http_parts(); let r = Option::>::from_request(&req, &mut pl) .await @@ -377,13 +373,11 @@ mod tests { })) ); - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, "9")) + .set_payload(Bytes::from_static(b"bye=world")) + .to_http_parts(); let r = Option::>::from_request(&req, &mut pl) .await @@ -393,13 +387,11 @@ mod tests { #[actix_rt::test] async fn test_result() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, "11")) + .set_payload(Bytes::from_static(b"hello=world")) + .to_http_parts(); let r = Result::, Error>::from_request(&req, &mut pl) .await @@ -412,13 +404,11 @@ mod tests { }) ); - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .header(header::CONTENT_LENGTH, "9") - .set_payload(Bytes::from_static(b"bye=world")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((header::CONTENT_LENGTH, 9)) + .set_payload(Bytes::from_static(b"bye=world")) + .to_http_parts(); let r = Result::, Error>::from_request(&req, &mut pl) .await diff --git a/src/guard.rs b/src/guard.rs index 25284236a..ba0cbea85 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -330,7 +330,8 @@ mod tests { #[test] fn test_header() { - let req = TestRequest::with_header(header::TRANSFER_ENCODING, "chunked") + let req = TestRequest::default() + .insert_header((header::TRANSFER_ENCODING, "chunked")) .to_http_request(); let pred = Header("transfer-encoding", "chunked"); @@ -346,10 +347,10 @@ mod tests { #[test] fn test_host() { let req = TestRequest::default() - .header( + .insert_header(( header::HOST, header::HeaderValue::from_static("www.rust-lang.org"), - ) + )) .to_http_request(); let pred = Host("www.rust-lang.org"); @@ -374,10 +375,10 @@ mod tests { #[test] fn test_host_scheme() { let req = TestRequest::default() - .header( + .insert_header(( header::HOST, header::HeaderValue::from_static("https://www.rust-lang.org"), - ) + )) .to_http_request(); let pred = Host("www.rust-lang.org").scheme("https"); diff --git a/src/info.rs b/src/info.rs index 75ebf67eb..c9ddf6ec4 100644 --- a/src/info.rs +++ b/src/info.rs @@ -200,10 +200,10 @@ mod tests { assert_eq!(info.host(), "localhost:8080"); let req = TestRequest::default() - .header( + .insert_header(( header::FORWARDED, "for=192.0.2.60; proto=https; by=203.0.113.43; host=rust-lang.org", - ) + )) .to_http_request(); let info = req.connection_info(); @@ -212,7 +212,7 @@ mod tests { assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); let req = TestRequest::default() - .header(header::HOST, "rust-lang.org") + .insert_header((header::HOST, "rust-lang.org")) .to_http_request(); let info = req.connection_info(); @@ -221,20 +221,20 @@ mod tests { assert_eq!(info.realip_remote_addr(), None); let req = TestRequest::default() - .header(X_FORWARDED_FOR, "192.0.2.60") + .insert_header((X_FORWARDED_FOR, "192.0.2.60")) .to_http_request(); let info = req.connection_info(); assert_eq!(info.realip_remote_addr(), Some("192.0.2.60")); let req = TestRequest::default() - .header(X_FORWARDED_HOST, "192.0.2.60") + .insert_header((X_FORWARDED_HOST, "192.0.2.60")) .to_http_request(); let info = req.connection_info(); assert_eq!(info.host(), "192.0.2.60"); assert_eq!(info.realip_remote_addr(), None); let req = TestRequest::default() - .header(X_FORWARDED_PROTO, "https") + .insert_header((X_FORWARDED_PROTO, "https")) .to_http_request(); let info = req.connection_info(); assert_eq!(info.scheme(), "https"); diff --git a/src/lib.rs b/src/lib.rs index fa4e70aec..52471f4b3 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -211,7 +211,7 @@ pub mod client { //! //! // Create request builder and send request //! let response = client.get("http://www.rust-lang.org") - //! .header("User-Agent", "actix-web/3.0") + //! .insert_header(("User-Agent", "actix-web/3.0")) //! .send() // <- Send request //! .await; // <- Wait for response //! diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 6f027124f..5d55fd830 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -212,8 +212,11 @@ mod tests { let req = TestRequest::default().to_srv_request(); let srv = |req: ServiceRequest| { - ok(req - .into_response(HttpResponse::Ok().header(CONTENT_TYPE, "0002").finish())) + ok(req.into_response( + HttpResponse::Ok() + .insert_header((CONTENT_TYPE, "0002")) + .finish(), + )) }; let mut mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 276265a58..66bd7e464 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -603,7 +603,7 @@ mod tests { let srv = |req: ServiceRequest| { ok(req.into_response( HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") + .insert_header(("X-Test", "ttt")) .finish(), )) }; @@ -611,11 +611,12 @@ mod tests { let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .to_srv_request(); let _res = srv.call(req).await; } @@ -624,7 +625,7 @@ mod tests { let srv = |req: ServiceRequest| { ok(req.into_response( HttpResponse::build(StatusCode::OK) - .header("X-Test", "ttt") + .insert_header(("X-Test", "ttt")) .finish(), )) }; @@ -633,23 +634,25 @@ mod tests { let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .to_srv_request(); let _res = srv.call(req).await.unwrap(); } #[actix_rt::test] async fn test_url_path() { let mut format = Format::new("%T %U"); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .uri("/test/route/yeah") - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .uri("/test/route/yeah") + .to_srv_request(); let now = OffsetDateTime::now_utc(); for unit in &mut format.0 { @@ -676,12 +679,13 @@ mod tests { async fn test_default_format() { let mut format = Format::default(); - let req = TestRequest::with_header( - header::USER_AGENT, - header::HeaderValue::from_static("ACTIX-WEB"), - ) - .peer_addr("127.0.0.1:8081".parse().unwrap()) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::USER_AGENT, + header::HeaderValue::from_static("ACTIX-WEB"), + )) + .peer_addr("127.0.0.1:8081".parse().unwrap()) + .to_srv_request(); let now = OffsetDateTime::now_utc(); for unit in &mut format.0 { @@ -736,13 +740,14 @@ mod tests { async fn test_remote_addr_format() { let mut format = Format::new("%{r}a"); - let req = TestRequest::with_header( - header::FORWARDED, - header::HeaderValue::from_static( - "for=192.0.2.60;proto=http;by=203.0.113.43", - ), - ) - .to_srv_request(); + let req = TestRequest::default() + .insert_header(( + header::FORWARDED, + header::HeaderValue::from_static( + "for=192.0.2.60;proto=http;by=203.0.113.43", + ), + )) + .to_srv_request(); let now = OffsetDateTime::now_utc(); for unit in &mut format.0 { diff --git a/src/request.rs b/src/request.rs index c0e26006c..fe60cb169 100644 --- a/src/request.rs +++ b/src/request.rs @@ -423,8 +423,9 @@ mod tests { #[test] fn test_debug() { - let req = - TestRequest::with_header("content-type", "text/plain").to_http_request(); + let req = TestRequest::default() + .insert_header(("content-type", "text/plain")) + .to_http_request(); let dbg = format!("{:?}", req); assert!(dbg.contains("HttpRequest")); } @@ -438,8 +439,8 @@ mod tests { #[test] fn test_request_cookies() { let req = TestRequest::default() - .header(header::COOKIE, "cookie1=value1") - .header(header::COOKIE, "cookie2=value2") + .append_header((header::COOKIE, "cookie1=value1")) + .append_header((header::COOKIE, "cookie2=value2")) .to_http_request(); { let cookies = req.cookies().unwrap(); @@ -476,7 +477,8 @@ mod tests { assert!(rmap.has_resource("/user/test.html")); assert!(!rmap.has_resource("/test/unknown")); - let req = TestRequest::with_header(header::HOST, "www.rust-lang.org") + let req = TestRequest::default() + .insert_header((header::HOST, "www.rust-lang.org")) .rmap(rmap) .to_http_request(); @@ -506,7 +508,7 @@ mod tests { assert!(rmap.has_resource("/index.html")); let req = TestRequest::with_uri("/test") - .header(header::HOST, "www.rust-lang.org") + .insert_header((header::HOST, "www.rust-lang.org")) .rmap(rmap) .to_http_request(); let url = req.url_for_static("index"); @@ -557,7 +559,7 @@ mod tests { let mut srv = init_service(App::new().service(web::resource("/").to( |req: HttpRequest| { HttpResponse::Ok() - .set_header("pool_cap", req.app_state().pool().cap) + .insert_header(("pool_cap", req.app_state().pool().cap)) .finish() }, ))) diff --git a/src/responder.rs b/src/responder.rs index 9b33ac81a..d5771d520 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -1,17 +1,17 @@ -use std::convert::TryFrom; +use std::fmt; -use actix_http::error::InternalError; -use actix_http::http::{ - header::IntoHeaderValue, Error as HttpError, HeaderMap, HeaderName, StatusCode, +use actix_http::{ + error::InternalError, + http::{header::IntoHeaderPair, Error as HttpError, HeaderMap, StatusCode}, + ResponseBuilder, }; -use actix_http::ResponseBuilder; use bytes::{Bytes, BytesMut}; use crate::{Error, HttpRequest, HttpResponse}; -/// Trait implemented by types that can be converted to a http response. +/// Trait implemented by types that can be converted to an HTTP response. /// -/// Types that implement this trait can be used as the return type of a handler. +/// Any types that implement this trait can be used in the return type of a handler. pub trait Responder { /// Convert self to `HttpResponse`. fn respond_to(self, req: &HttpRequest) -> HttpResponse; @@ -19,12 +19,11 @@ pub trait Responder { /// Override a status code for a Responder. /// /// ```rust - /// use actix_web::{HttpRequest, Responder, http::StatusCode}; + /// use actix_web::{http::StatusCode, HttpRequest, Responder}; /// /// fn index(req: HttpRequest) -> impl Responder { /// "Welcome!".with_status(StatusCode::OK) /// } - /// # fn main() {} /// ``` fn with_status(self, status: StatusCode) -> CustomResponder where @@ -33,7 +32,9 @@ pub trait Responder { CustomResponder::new(self).with_status(status) } - /// Add header to the Responder's response. + /// Insert header to the final response. + /// + /// Overrides other headers with the same name. /// /// ```rust /// use actix_web::{web, HttpRequest, Responder}; @@ -45,21 +46,16 @@ pub trait Responder { /// } /// /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json( - /// MyObj{name: "Name".to_string()} - /// ) - /// .with_header("x-version", "1.2.3") + /// web::Json(MyObj { name: "Name".to_owned() }) + /// .with_header(("x-version", "1.2.3")) /// } - /// # fn main() {} /// ``` - fn with_header(self, key: K, value: V) -> CustomResponder + fn with_header(self, header: H) -> CustomResponder where Self: Sized, - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - CustomResponder::new(self).with_header(key, value) + CustomResponder::new(self).with_header(header) } } @@ -155,7 +151,7 @@ impl Responder for BytesMut { } } -/// Allows to override status code and headers for a responder. +/// Allows overriding status code and headers for a responder. pub struct CustomResponder { responder: T, status: Option, @@ -181,14 +177,15 @@ impl CustomResponder { /// fn index(req: HttpRequest) -> impl Responder { /// "Welcome!".with_status(StatusCode::OK) /// } - /// # fn main() {} /// ``` pub fn with_status(mut self, status: StatusCode) -> Self { self.status = Some(status); self } - /// Add header to the Responder's response. + /// Insert header to the final response. + /// + /// Overrides other headers with the same name. /// /// ```rust /// use actix_web::{web, HttpRequest, Responder}; @@ -200,32 +197,24 @@ impl CustomResponder { /// } /// /// fn index(req: HttpRequest) -> impl Responder { - /// web::Json( - /// MyObj{name: "Name".to_string()} - /// ) - /// .with_header("x-version", "1.2.3") + /// web::Json(MyObj { name: "Name".to_string() }) + /// .with_header(("x-version", "1.2.3")) + /// .with_header(("x-version", "1.2.3")) /// } - /// # fn main() {} /// ``` - pub fn with_header(mut self, key: K, value: V) -> Self + pub fn with_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { if self.headers.is_none() { self.headers = Some(HeaderMap::new()); } - match HeaderName::try_from(key) { - Ok(key) => match value.try_into() { - Ok(value) => { - self.headers.as_mut().unwrap().append(key, value); - } - Err(e) => self.error = Some(e.into()), - }, + match header.try_into_header_pair() { + Ok((key, value)) => self.headers.as_mut().unwrap().append(key, value), Err(e) => self.error = Some(e.into()), }; + self } } @@ -240,6 +229,7 @@ impl Responder for CustomResponder { if let Some(ref headers) = self.headers { for (k, v) in headers { + // TODO: before v4, decide if this should be append instead res.headers_mut().insert(k.clone(), v.clone()); } } @@ -250,7 +240,7 @@ impl Responder for CustomResponder { impl Responder for InternalError where - T: std::fmt::Debug + std::fmt::Display + 'static, + T: fmt::Debug + fmt::Display + 'static, { fn respond_to(self, _: &HttpRequest) -> HttpResponse { HttpResponse::from_error(self.into()) @@ -412,7 +402,7 @@ pub(crate) mod tests { let res = "test" .to_string() - .with_header("content-type", "json") + .with_header(("content-type", "json")) .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); @@ -432,13 +422,13 @@ pub(crate) mod tests { let req = TestRequest::default().to_http_request(); let res = ("test".to_string(), StatusCode::OK) - .with_header("content-type", "json") + .with_header((CONTENT_TYPE, mime::APPLICATION_JSON)) .respond_to(&req); assert_eq!(res.status(), StatusCode::OK); assert_eq!(res.body().bin_ref(), b"test"); assert_eq!( res.headers().get(CONTENT_TYPE).unwrap(), - HeaderValue::from_static("json") + HeaderValue::from_static("application/json") ); } } diff --git a/src/service.rs b/src/service.rs index 668b7d1b4..d4fa4acca 100644 --- a/src/service.rs +++ b/src/service.rs @@ -588,14 +588,14 @@ mod tests { fn test_fmt_debug() { let req = TestRequest::get() .uri("/index.html?test=1") - .header("x-test", "111") + .insert_header(("x-test", "111")) .to_srv_request(); let s = format!("{:?}", req); assert!(s.contains("ServiceRequest")); assert!(s.contains("test=1")); assert!(s.contains("x-test")); - let res = HttpResponse::Ok().header("x-test", "111").finish(); + let res = HttpResponse::Ok().insert_header(("x-test", "111")).finish(); let res = TestRequest::post() .uri("/index.html?test=1") .to_srv_response(res); diff --git a/src/test.rs b/src/test.rs index f8b789d1b..55b436084 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1,13 +1,13 @@ //! Various helpers for Actix applications to use during testing. -use std::convert::TryFrom; + use std::net::SocketAddr; use std::rc::Rc; use std::sync::mpsc; use std::{fmt, net, thread, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed}; -use actix_http::http::header::{ContentType, Header, HeaderName, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, Method, StatusCode, Uri, Version}; +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::{cookie::Cookie, ws, Extensions, HttpService, Request}; use actix_router::{Path, ResourceDef, Url}; @@ -349,7 +349,7 @@ where /// /// #[test] /// fn test_index() { -/// let req = test::TestRequest::with_header("content-type", "text/plain") +/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") /// .to_http_request(); /// /// let resp = index(req).await.unwrap(); @@ -389,21 +389,6 @@ impl TestRequest { TestRequest::default().uri(path) } - /// Create TestRequest and set header - pub fn with_hdr(hdr: H) -> TestRequest { - TestRequest::default().set(hdr) - } - - /// Create TestRequest and set header - pub fn with_header(key: K, value: V) -> TestRequest - where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, - { - TestRequest::default().header(key, value) - } - /// Create TestRequest and set method to `Method::GET` pub fn get() -> TestRequest { TestRequest::default().method(Method::GET) @@ -447,24 +432,25 @@ impl TestRequest { self } - /// Set a header - pub fn set(mut self, hdr: H) -> Self { - self.req.set(hdr); - self - } - - /// Set a header - pub fn header(mut self, key: K, value: V) -> Self + /// Insert a header, replacing any that were set with an equivalent field name. + pub fn insert_header(mut self, header: H) -> Self where - HeaderName: TryFrom, - >::Error: Into, - V: IntoHeaderValue, + H: IntoHeaderPair, { - self.req.header(key, value); + self.req.insert_header(header); self } - /// Set cookie for this request + /// Append a header, keeping any that were set with an equivalent field name. + pub fn append_header(mut self, header: H) -> Self + where + H: IntoHeaderPair, + { + self.req.append_header(header); + self + } + + /// Set cookie for this request. pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.req.cookie(cookie); self @@ -494,7 +480,7 @@ impl TestRequest { let bytes = serde_urlencoded::to_string(data) .expect("Failed to serialize test data as a urlencoded form"); self.req.set_payload(bytes); - self.req.set(ContentType::form_url_encoded()); + self.req.insert_header(ContentType::form_url_encoded()); self } @@ -504,7 +490,7 @@ impl TestRequest { let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); self.req.set_payload(bytes); - self.req.set(ContentType::json()); + self.req.insert_header(ContentType::json()); self } @@ -1029,9 +1015,10 @@ mod tests { #[actix_rt::test] async fn test_basics() { - let req = TestRequest::with_hdr(header::ContentType::json()) + let req = TestRequest::default() .version(Version::HTTP_2) - .set(header::Date(SystemTime::now().into())) + .insert_header(header::ContentType::json()) + .insert_header(header::Date(SystemTime::now().into())) .param("test", "123") .data(10u32) .app_data(20u64) @@ -1068,7 +1055,7 @@ mod tests { let put_req = TestRequest::put() .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); let result = read_response(&mut app, put_req).await; @@ -1076,7 +1063,7 @@ mod tests { let patch_req = TestRequest::patch() .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); let result = read_response(&mut app, patch_req).await; @@ -1099,7 +1086,7 @@ mod tests { let req = TestRequest::post() .uri("/index.html") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); let result = read_response(&mut app, req).await; @@ -1144,7 +1131,7 @@ mod tests { let req = TestRequest::post() .uri("/people") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .set_payload(payload) .to_request(); @@ -1165,7 +1152,7 @@ mod tests { let resp = TestRequest::post() .uri("/people") - .header(header::CONTENT_TYPE, "application/json") + .insert_header((header::CONTENT_TYPE, "application/json")) .set_payload(payload) .send_request(&mut app) .await; diff --git a/src/types/form.rs b/src/types/form.rs index 71680b19a..96e09ee1c 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -381,11 +381,11 @@ mod tests { #[actix_rt::test] async fn test_form() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); let Form(s) = Form::::from_request(&req, &mut pl).await.unwrap(); assert_eq!( @@ -414,25 +414,26 @@ mod tests { #[actix_rt::test] async fn test_urlencoded_error() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "xxxx") - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, "xxxx")) + .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await; assert!(eq(info.err().unwrap(), UrlencodedError::UnknownLength)); - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "1000000") - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, "1000000")) + .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await; assert!(eq( info.err().unwrap(), UrlencodedError::Overflow { size: 0, limit: 0 } )); - let (req, mut pl) = TestRequest::with_header(CONTENT_TYPE, "text/plain") - .header(CONTENT_LENGTH, "10") + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "text/plain")) + .insert_header((CONTENT_LENGTH, 10)) .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await; assert!(eq(info.err().unwrap(), UrlencodedError::ContentType)); @@ -440,11 +441,11 @@ mod tests { #[actix_rt::test] async fn test_urlencoded() { - let (req, mut pl) = - TestRequest::with_header(CONTENT_TYPE, "application/x-www-form-urlencoded") - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header((CONTENT_TYPE, "application/x-www-form-urlencoded")) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); assert_eq!( @@ -455,13 +456,14 @@ mod tests { } ); - let (req, mut pl) = TestRequest::with_header( - CONTENT_TYPE, - "application/x-www-form-urlencoded; charset=utf-8", - ) - .header(CONTENT_LENGTH, "11") - .set_payload(Bytes::from_static(b"hello=world&counter=123")) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + CONTENT_TYPE, + "application/x-www-form-urlencoded; charset=utf-8", + )) + .insert_header((CONTENT_LENGTH, 11)) + .set_payload(Bytes::from_static(b"hello=world&counter=123")) + .to_http_parts(); let info = UrlEncoded::::new(&req, &mut pl).await.unwrap(); assert_eq!( @@ -497,8 +499,8 @@ mod tests { let ctype = HeaderValue::from_static("application/x-www-form-urlencoded"); let (req, mut pl) = TestRequest::default() - .header(CONTENT_TYPE, ctype) - .header(CONTENT_LENGTH, HeaderValue::from_static("20")) + .insert_header((CONTENT_TYPE, ctype)) + .insert_header((CONTENT_LENGTH, HeaderValue::from_static("20"))) .set_payload(Bytes::from_static(b"hello=test&counter=4")) .app_data(web::Data::new(FormConfig::default().limit(10))) .to_http_parts(); diff --git a/src/types/json.rs b/src/types/json.rs index edfb775f3..7b94f66f6 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -427,7 +427,7 @@ mod tests { use crate::{ error::InternalError, http::{ - header::{self, HeaderValue, CONTENT_LENGTH, CONTENT_TYPE}, + header::{self, CONTENT_LENGTH, CONTENT_TYPE}, StatusCode, }, test::{load_stream, TestRequest}, @@ -469,14 +469,14 @@ mod tests { #[actix_rt::test] async fn test_custom_error_responder() { let (req, mut pl) = TestRequest::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\"}")) .app_data(JsonConfig::default().limit(10).error_handler(|err, _| { let msg = MyObject { @@ -500,14 +500,14 @@ mod tests { #[actix_rt::test] async fn test_extract() { let (req, mut pl) = TestRequest::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\"}")) .to_http_parts(); @@ -521,14 +521,14 @@ mod tests { ); let (req, mut pl) = TestRequest::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\"}")) .app_data(JsonConfig::default().limit(10)) .to_http_parts(); @@ -538,14 +538,14 @@ mod tests { .contains("Json payload size is bigger than allowed")); let (req, mut pl) = TestRequest::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\"}")) .app_data( JsonConfig::default() @@ -564,23 +564,23 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/text"), - ) + )) .to_http_parts(); let json = JsonBody::::new(&req, &mut pl, None).await; assert!(json_eq(json.err().unwrap(), JsonPayloadError::ContentType)); let (req, mut pl) = TestRequest::default() - .header( + .insert_header(( header::CONTENT_TYPE, header::HeaderValue::from_static("application/json"), - ) - .header( + )) + .insert_header(( header::CONTENT_LENGTH, header::HeaderValue::from_static("10000"), - ) + )) .to_http_parts(); let json = JsonBody::::new(&req, &mut pl, None) @@ -589,14 +589,14 @@ mod tests { assert!(json_eq(json.err().unwrap(), JsonPayloadError::Overflow)); let (req, mut pl) = TestRequest::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\"}")) .to_http_parts(); @@ -611,17 +611,18 @@ mod tests { #[actix_rt::test] async fn test_with_json_and_bad_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().limit(4096)) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().limit(4096)) + .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; assert!(s.is_err()) @@ -629,19 +630,20 @@ mod tests { #[actix_rt::test] async fn test_with_json_and_good_custom_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/plain"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/plain"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; assert!(s.is_ok()) @@ -649,19 +651,20 @@ mod tests { #[actix_rt::test] async fn test_with_json_and_bad_custom_content_type() { - let (req, mut pl) = TestRequest::with_header( - header::CONTENT_TYPE, - header::HeaderValue::from_static("text/html"), - ) - .header( - header::CONTENT_LENGTH, - header::HeaderValue::from_static("16"), - ) - .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) - .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { - mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN - })) - .to_http_parts(); + let (req, mut pl) = TestRequest::default() + .insert_header(( + header::CONTENT_TYPE, + header::HeaderValue::from_static("text/html"), + )) + .insert_header(( + header::CONTENT_LENGTH, + header::HeaderValue::from_static("16"), + )) + .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) + .app_data(JsonConfig::default().content_type(|mime: mime::Mime| { + mime.type_() == mime::TEXT && mime.subtype() == mime::PLAIN + })) + .to_http_parts(); let s = Json::::from_request(&req, &mut pl).await; assert!(s.is_err()) @@ -670,8 +673,8 @@ mod tests { #[actix_rt::test] async fn test_with_config_in_data_wrapper() { let (req, mut pl) = TestRequest::default() - .header(CONTENT_TYPE, HeaderValue::from_static("application/json")) - .header(CONTENT_LENGTH, HeaderValue::from_static("16")) + .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON)) + .insert_header((CONTENT_LENGTH, 16)) .set_payload(Bytes::from_static(b"{\"name\": \"test\"}")) .app_data(web::Data::new(JsonConfig::default().limit(10))) .to_http_parts(); diff --git a/src/types/payload.rs b/src/types/payload.rs index 22528031c..bee1b695a 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -364,14 +364,13 @@ mod tests { let cfg = PayloadConfig::default().mimetype(mime::APPLICATION_JSON); assert!(cfg.check_mimetype(&req).is_err()); - let req = TestRequest::with_header( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) - .to_http_request(); + let req = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/x-www-form-urlencoded")) + .to_http_request(); assert!(cfg.check_mimetype(&req).is_err()); - let req = TestRequest::with_header(header::CONTENT_TYPE, "application/json") + let req = TestRequest::default() + .insert_header((header::CONTENT_TYPE, "application/json")) .to_http_request(); assert!(cfg.check_mimetype(&req).is_ok()); } @@ -432,25 +431,25 @@ mod tests { assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-app-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/bytes-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-app-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-data") - .header(header::CONTENT_TYPE, mime::APPLICATION_JSON) + .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); let resp = call_service(&mut srv, req).await; assert_eq!(resp.status(), StatusCode::OK); @@ -458,7 +457,8 @@ mod tests { #[actix_rt::test] async fn test_bytes() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "11")) .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); @@ -468,7 +468,8 @@ mod tests { #[actix_rt::test] async fn test_string() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "11") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "11")) .set_payload(Bytes::from_static(b"hello=world")) .to_http_parts(); @@ -478,7 +479,8 @@ mod tests { #[actix_rt::test] async fn test_message_body() { - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "xxxx") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "xxxx")) .to_srv_request() .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; @@ -487,7 +489,8 @@ mod tests { _ => unreachable!("error"), } - let (req, mut pl) = TestRequest::with_header(header::CONTENT_LENGTH, "1000000") + let (req, mut pl) = TestRequest::default() + .insert_header((header::CONTENT_LENGTH, "1000000")) .to_srv_request() .into_parts(); let res = HttpMessageBody::new(&req, &mut pl).await; diff --git a/tests/test_server.rs b/tests/test_server.rs index 43ee1230d..e8735966f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -108,7 +108,7 @@ async fn test_body_gzip() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -137,7 +137,7 @@ async fn test_body_gzip2() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -178,7 +178,7 @@ async fn test_body_encoding_override() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "deflate") + .append_header((ACCEPT_ENCODING, "deflate")) .send() .await .unwrap(); @@ -197,7 +197,7 @@ async fn test_body_encoding_override() { let mut response = srv .request(actix_web::http::Method::GET, srv.url("/raw")) .no_decompress() - .header(ACCEPT_ENCODING, "deflate") + .append_header((ACCEPT_ENCODING, "deflate")) .send() .await .unwrap(); @@ -231,7 +231,7 @@ async fn test_body_gzip_large() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -269,7 +269,7 @@ async fn test_body_gzip_large_random() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -300,7 +300,7 @@ async fn test_body_chunked_implicit() { let mut response = srv .get("/") .no_decompress() - .header(ACCEPT_ENCODING, "gzip") + .append_header((ACCEPT_ENCODING, "gzip")) .send() .await .unwrap(); @@ -333,7 +333,7 @@ async fn test_body_br_streaming() { let mut response = srv .get("/") - .header(ACCEPT_ENCODING, "br") + .append_header((ACCEPT_ENCODING, "br")) .no_decompress() .send() .await @@ -406,7 +406,7 @@ async fn test_body_deflate() { // client request let mut response = srv .get("/") - .header(ACCEPT_ENCODING, "deflate") + .append_header((ACCEPT_ENCODING, "deflate")) .no_decompress() .send() .await @@ -433,7 +433,7 @@ async fn test_body_brotli() { // client request let mut response = srv .get("/") - .header(ACCEPT_ENCODING, "br") + .append_header((ACCEPT_ENCODING, "br")) .no_decompress() .send() .await @@ -466,7 +466,7 @@ async fn test_encoding() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .insert_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -492,7 +492,7 @@ async fn test_gzip_encoding() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .append_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -519,7 +519,7 @@ async fn test_gzip_encoding_large() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .append_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -551,7 +551,7 @@ async fn test_reading_gzip_encoding_large_random() { let request = srv .post("/") - .header(CONTENT_ENCODING, "gzip") + .append_header((CONTENT_ENCODING, "gzip")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -578,7 +578,7 @@ async fn test_reading_deflate_encoding() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "deflate") + .append_header((CONTENT_ENCODING, "deflate")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -605,7 +605,7 @@ async fn test_reading_deflate_encoding_large() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "deflate") + .append_header((CONTENT_ENCODING, "deflate")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -637,7 +637,7 @@ async fn test_reading_deflate_encoding_large_random() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "deflate") + .append_header((CONTENT_ENCODING, "deflate")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -664,7 +664,7 @@ async fn test_brotli_encoding() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "br") + .append_header((CONTENT_ENCODING, "br")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -699,7 +699,7 @@ async fn test_brotli_encoding_large() { // client request let request = srv .post("/") - .header(CONTENT_ENCODING, "br") + .append_header((CONTENT_ENCODING, "br")) .send_body(enc.clone()); let mut response = request.await.unwrap(); assert!(response.status().is_success()); @@ -739,7 +739,7 @@ async fn test_brotli_encoding_large_openssl() { // client request let mut response = srv .post("/") - .header(actix_web::http::header::CONTENT_ENCODING, "br") + .append_header((actix_web::http::header::CONTENT_ENCODING, "br")) .send_body(enc) .await .unwrap(); @@ -788,7 +788,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() { // client request let req = srv .post("/") - .header(actix_web::http::header::CONTENT_ENCODING, "deflate") + .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) .send_stream(TestBody::new(Bytes::from(enc), 1024)); let mut response = req.await.unwrap(); From f976150b6704beff34e02669d16370f71bb79f9b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 15 Jan 2021 04:22:42 +0000 Subject: [PATCH 124/187] return option item from Extensions::insert (#1904) --- actix-http/CHANGES.md | 2 + actix-http/src/extensions.rs | 89 ++++++++++++++++++++++++++------ actix-http/tests/test_openssl.rs | 4 +- actix-http/tests/test_server.rs | 4 +- 4 files changed, 82 insertions(+), 17 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ed3764587..8b0255286 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -12,6 +12,7 @@ `mime` types. [#1894] * Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] +* `Extensions::insert` returns Option of replaced item. [#1904] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -22,6 +23,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 +[#1904]: https://github.com/actix/actix-web/pull/1904 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/src/extensions.rs b/actix-http/src/extensions.rs index e978c1749..5fdcefd6d 100644 --- a/actix-http/src/extensions.rs +++ b/actix-http/src/extensions.rs @@ -5,7 +5,9 @@ use std::{ use ahash::AHashMap; -/// A type map of request extensions. +/// A type map for request extensions. +/// +/// All entries into this map must be owned types (or static references). #[derive(Default)] pub struct Extensions { /// Use FxHasher with a std HashMap with for faster @@ -14,7 +16,7 @@ pub struct Extensions { } impl Extensions { - /// Create an empty `Extensions`. + /// Creates an empty `Extensions`. #[inline] pub fn new() -> Extensions { Extensions { @@ -22,43 +24,96 @@ impl Extensions { } } - /// Insert a type into this `Extensions`. + /// Insert an item into the map. /// - /// If a extension of this type already existed, it will - /// be returned. - pub fn insert(&mut self, val: T) { - self.map.insert(TypeId::of::(), Box::new(val)); + /// If an item of this type was already stored, it will be replaced and returned. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// assert_eq!(map.insert(""), None); + /// assert_eq!(map.insert(1u32), None); + /// assert_eq!(map.insert(2u32), Some(1u32)); + /// assert_eq!(*map.get::().unwrap(), 2u32); + /// ``` + pub fn insert(&mut self, val: T) -> Option { + self.map + .insert(TypeId::of::(), Box::new(val)) + .and_then(downcast_owned) } - /// Check if container contains entry + /// Check if map contains an item of a given type. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// assert!(!map.contains::()); + /// + /// assert_eq!(map.insert(1u32), None); + /// assert!(map.contains::()); + /// ``` pub fn contains(&self) -> bool { self.map.contains_key(&TypeId::of::()) } - /// Get a reference to a type previously inserted on this `Extensions`. + /// Get a reference to an item of a given type. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// map.insert(1u32); + /// assert_eq!(map.get::(), Some(&1u32)); + /// ``` pub fn get(&self) -> Option<&T> { self.map .get(&TypeId::of::()) .and_then(|boxed| boxed.downcast_ref()) } - /// Get a mutable reference to a type previously inserted on this `Extensions`. + /// Get a mutable reference to an item of a given type. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// map.insert(1u32); + /// assert_eq!(map.get_mut::(), Some(&mut 1u32)); + /// ``` pub fn get_mut(&mut self) -> Option<&mut T> { self.map .get_mut(&TypeId::of::()) .and_then(|boxed| boxed.downcast_mut()) } - /// Remove a type from this `Extensions`. + /// Remove an item from the map of a given type. /// - /// If a extension of this type existed, it will be returned. + /// If an item of this type was already stored, it will be returned. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// + /// map.insert(1u32); + /// assert_eq!(map.get::(), Some(&1u32)); + /// + /// assert_eq!(map.remove::(), Some(1u32)); + /// assert!(!map.contains::()); + /// ``` pub fn remove(&mut self) -> Option { - self.map - .remove(&TypeId::of::()) - .and_then(|boxed| boxed.downcast().ok().map(|boxed| *boxed)) + self.map.remove(&TypeId::of::()).and_then(downcast_owned) } /// Clear the `Extensions` of all inserted extensions. + /// + /// ``` + /// # use actix_http::Extensions; + /// let mut map = Extensions::new(); + /// + /// map.insert(1u32); + /// assert!(map.contains::()); + /// + /// map.clear(); + /// assert!(!map.contains::()); + /// ``` #[inline] pub fn clear(&mut self) { self.map.clear(); @@ -81,6 +136,10 @@ impl fmt::Debug for Extensions { } } +fn downcast_owned(boxed: Box) -> Option { + boxed.downcast().ok().map(|boxed| *boxed) +} + #[cfg(test)] mod tests { use super::*; diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index fe7825d78..f20cfd70c 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -408,7 +408,9 @@ async fn test_h2_service_error() { async fn test_h2_on_connect() { let srv = test_server(move || { HttpService::build() - .on_connect_ext(|_, data| data.insert(20isize)) + .on_connect_ext(|_, data| { + data.insert(20isize); + }) .h2(|req: Request| { assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index e1cfe9db4..b4ef74406 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -662,7 +662,9 @@ async fn test_h1_service_error() { async fn test_h1_on_connect() { let srv = test_server(|| { HttpService::build() - .on_connect_ext(|_, data| data.insert(20isize)) + .on_connect_ext(|_, data| { + data.insert(20isize); + }) .h1(|req: Request| { assert!(req.extensions().contains::()); future::ok::<_, ()>(Response::Ok().finish()) From b2a9ba2ee432be1b60709b4b29b1b4cd31b2512b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 15 Jan 2021 04:54:23 +0000 Subject: [PATCH 125/187] Update PULL_REQUEST_TEMPLATE.md --- .github/PULL_REQUEST_TEMPLATE.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md index b779b33fa..42deadf5a 100644 --- a/.github/PULL_REQUEST_TEMPLATE.md +++ b/.github/PULL_REQUEST_TEMPLATE.md @@ -1,21 +1,21 @@ - + ## PR Type -INSERT_PR_TYPE +PR_TYPE ## PR Checklist -Check your PR fulfills the following: - + - [ ] Tests for the changes have been added / updated. - [ ] Documentation comments have been added / updated. - [ ] A changelog entry has been made for the appropriate packages. -- [ ] Format code with the latest stable rustfmt +- [ ] Format code with the latest stable rustfmt. +- [ ] (Team) Label with affected crates and semver status. ## Overview From 0a506bf2e9f0d07c505df725a68808c6343f7a4e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 15 Jan 2021 05:38:50 +0000 Subject: [PATCH 126/187] cleanup top level doc comments --- actix-http/src/client/config.rs | 5 +- actix-http/src/client/h2proto.rs | 2 +- actix-http/src/config.rs | 2 +- actix-http/src/error.rs | 1 - .../src/header/common/content_disposition.rs | 14 ++--- actix-http/src/header/mod.rs | 4 +- actix-http/src/ws/mask.rs | 8 +-- actix-http/src/ws/mod.rs | 6 --- awc/tests/test_client.rs | 5 ++ awc/tests/test_rustls_client.rs | 1 + src/app_service.rs | 4 +- src/handler.rs | 2 +- src/middleware/compat.rs | 3 +- src/types/json.rs | 2 +- src/types/payload.rs | 2 +- tests/test_server.rs | 26 +--------- tests/test_weird_poll.rs | 52 +++++++++---------- 17 files changed, 57 insertions(+), 82 deletions(-) diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index c86c697a2..369732964 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -1,8 +1,7 @@ use std::time::Duration; -// These values are taken from hyper/src/proto/h2/client.rs -const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2mb -const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb +const DEFAULT_H2_CONN_WINDOW: u32 = 1024 * 1024 * 2; // 2MB +const DEFAULT_H2_STREAM_WINDOW: u32 = 1024 * 1024; // 1MB /// Connector configuration #[derive(Clone)] diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 4c609ef22..76915f214 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -171,7 +171,7 @@ async fn send_body( } } -// release SendRequest object +/// release SendRequest object fn release( io: SendRequest, pool: Option>, diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 1cd7e4aea..f178db274 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -9,7 +9,7 @@ use bytes::BytesMut; use futures_util::{future, FutureExt}; use time::OffsetDateTime; -// "Sun, 06 Nov 1994 08:49:37 GMT".len() +/// "Sun, 06 Nov 1994 08:49:37 GMT".len() const DATE_VALUE_LENGTH: usize = 29; #[derive(Debug, PartialEq, Clone, Copy)] diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index a585962be..9ff154240 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -18,7 +18,6 @@ use serde::de::value::Error as DeError; use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; -// re-export for convenience use crate::body::Body; pub use crate::cookie::ParseError as CookieParseError; use crate::helpers::Writer; diff --git a/actix-http/src/header/common/content_disposition.rs b/actix-http/src/header/common/content_disposition.rs index ae4a97902..ecc59aba3 100644 --- a/actix-http/src/header/common/content_disposition.rs +++ b/actix-http/src/header/common/content_disposition.rs @@ -1,10 +1,10 @@ -// # References -// -// "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt -// "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt -// "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt -// Browser conformance tests at: http://greenbytes.de/tech/tc2231/ -// IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml +//! # References +//! +//! "The Content-Disposition Header Field" https://www.ietf.org/rfc/rfc2183.txt +//! "The Content-Disposition Header Field in the Hypertext Transfer Protocol (HTTP)" https://www.ietf.org/rfc/rfc6266.txt +//! "Returning Values from Forms: multipart/form-data" https://www.ietf.org/rfc/rfc7578.txt +//! Browser conformance tests at: http://greenbytes.de/tech/tc2231/ +//! IANA assignment: http://www.iana.org/assignments/cont-disp/cont-disp.xhtml use lazy_static::lazy_static; use regex::Regex; diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index e4e15cd91..dc97bf5ff 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -80,8 +80,8 @@ impl From for HeaderMap { } } -// This encode set is used for HTTP header values and is defined at -// https://tools.ietf.org/html/rfc5987#section-3.2. +/// This encode set is used for HTTP header values and is defined at +/// https://tools.ietf.org/html/rfc5987#section-3.2. pub(crate) const HTTP_VALUE: &AsciiSet = &CONTROLS .add(b' ') .add(b'"') diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index d37d57eb1..79e015f79 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -3,7 +3,7 @@ use std::ptr::copy_nonoverlapping; use std::slice; -// Holds a slice guaranteed to be shorter than 8 bytes +/// Holds a slice guaranteed to be shorter than 8 bytes. struct ShortSlice<'a> { inner: &'a mut [u8], } @@ -80,8 +80,10 @@ unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) } -// Splits a slice into three parts: an unaligned short head and tail, plus an aligned -// u64 mid section. +/// Splits a slice into three parts: +/// - an unaligned short head +/// - an aligned `u64` slice mid section +/// - an unaligned short tail #[inline] fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) { let start_ptr = buf.as_ptr() as usize; diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index f8142693f..dad2646c1 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -128,18 +128,12 @@ impl ResponseError for HandshakeError { } /// Verify `WebSocket` handshake request and create handshake response. -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; Ok(handshake_response(req)) } /// Verify `WebSocket` handshake request. -// /// `protocols` is a sequence of known protocols. On successful handshake, -// /// the returned response headers contain the first protocol in this list -// /// which the server also knows. pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { // WebSocket accepts only GET if req.method != Method::GET { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index c31921560..3f5bf2958 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -557,6 +557,7 @@ async fn test_client_brotli_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } +// TODO: why is test ignored // #[actix_rt::test] // async fn test_client_deflate_encoding() { // let srv = test::TestServer::start(|app| { @@ -585,6 +586,7 @@ async fn test_client_brotli_encoding_large_random() { // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } +// TODO: why is test ignored // #[actix_rt::test] // async fn test_client_deflate_encoding_large_random() { // let data = rand::thread_rng() @@ -618,6 +620,7 @@ async fn test_client_brotli_encoding_large_random() { // assert_eq!(bytes, Bytes::from(data)); // } +// TODO: why is test ignored // #[actix_rt::test] // async fn test_client_streaming_explicit() { // let srv = test::TestServer::start(|app| { @@ -645,6 +648,7 @@ async fn test_client_brotli_encoding_large_random() { // assert_eq!(bytes, Bytes::from_static(STR.as_ref())); // } +// TODO: why is test ignored // #[actix_rt::test] // async fn test_body_streaming_implicit() { // let srv = test::TestServer::start(|app| { @@ -734,6 +738,7 @@ async fn test_client_cookie_handling() { assert_eq!(c2, cookie2); } +// TODO: why is test ignored // #[actix_rt::test] // fn client_read_until_eof() { // let addr = test::TestServer::unused_addr(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 3fa76d4a9..2f5ffeca2 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -50,6 +50,7 @@ mod danger { } } +// TODO: why is test ignored // #[actix_rt::test] async fn _test_connection_reuse_h2() { let num = Arc::new(AtomicUsize::new(0)); diff --git a/src/app_service.rs b/src/app_service.rs index 8169be517..4c099d4a4 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -149,7 +149,7 @@ where } } -/// Service to convert `Request` to a `ServiceRequest` +/// Service that takes a [`Request`] and delegates to a service that take a [`ServiceRequest`]. pub struct AppInitService where T: Service, Error = Error>, @@ -159,7 +159,7 @@ where app_state: Rc, } -// a collection of AppInitService state that shared between HttpRequests. +/// A collection of [`AppInitService`] state that shared across `HttpRequest`s. pub(crate) struct AppInitServiceState { rmap: Rc, config: AppConfig, diff --git a/src/handler.rs b/src/handler.rs index 47656cd84..1526f050d 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -101,7 +101,7 @@ where } } -// HandlerService is both it's ServiceFactory and Service Type. +/// HandlerService is both it's ServiceFactory and Service Type. impl Service for HandlerService where F: Handler, diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index eabd1190d..d5661fc77 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -110,8 +110,7 @@ where } } -// trait for convert ServiceResponse's ResponseBody generic type -// to ResponseBody +/// Convert `ServiceResponse`'s `ResponseBody` generic type to `ResponseBody`. pub trait MapServiceResponseBody { fn map_body(self) -> ServiceResponse; } diff --git a/src/types/json.rs b/src/types/json.rs index 7b94f66f6..41618b409 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -269,7 +269,7 @@ impl JsonConfig { } } -// Allow shared refs to default. +/// Allow shared refs used as default. const DEFAULT_CONFIG: JsonConfig = JsonConfig { limit: 32_768, // 2^15 bytes, (~32kB) err_handler: None, diff --git a/src/types/payload.rs b/src/types/payload.rs index bee1b695a..ad0bfd14f 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -247,7 +247,7 @@ impl PayloadConfig { } } -// Allow shared refs to default. +/// Allow shared refs used as defaults. const DEFAULT_CONFIG: PayloadConfig = PayloadConfig { limit: DEFAULT_CONFIG_LIMIT, mimetype: None, diff --git a/tests/test_server.rs b/tests/test_server.rs index e8735966f..7756d44d2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -800,6 +800,7 @@ async fn test_reading_deflate_encoding_large_random_rustls() { assert_eq!(bytes, Bytes::from(data)); } +// TODO: why is test ignored // #[test] // fn test_server_cookies() { // use actix_web::http; @@ -889,28 +890,3 @@ async fn test_normalize() { let response = srv.get("/one/").send().await.unwrap(); assert!(response.status().is_success()); } - -// #[cfg(feature = "openssl")] -// #[actix_rt::test] -// async fn test_ssl_handshake_timeout() { -// use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; -// use std::net; - -// // load ssl keys -// let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); -// builder -// .set_private_key_file("tests/key.pem", SslFiletype::PEM) -// .unwrap(); -// builder -// .set_certificate_chain_file("tests/cert.pem") -// .unwrap(); - -// let srv = test::start_with(test::config().openssl(builder.build()), || { -// App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok()))) -// }); - -// let mut stream = net::TcpStream::connect(srv.addr()).unwrap(); -// let mut data = String::new(); -// let _ = stream.read_to_string(&mut data); -// assert!(data.is_empty()); -// } diff --git a/tests/test_weird_poll.rs b/tests/test_weird_poll.rs index 7e4300901..5844ea2c2 100644 --- a/tests/test_weird_poll.rs +++ b/tests/test_weird_poll.rs @@ -1,30 +1,30 @@ -// Regression test for #/1321 +//! Regression test for https://github.com/actix/actix-web/issues/1321 -/* -use futures::task::{noop_waker, Context}; -use futures::stream::once; -use actix_http::body::{MessageBody, BodyStream}; -use bytes::Bytes; +// use actix_http::body::{BodyStream, MessageBody}; +// use bytes::Bytes; +// use futures_channel::oneshot; +// use futures_util::{ +// stream::once, +// task::{noop_waker, Context}, +// }; -Disable weird poll until actix-web is based on actix-http 2.0.0 +// #[test] +// fn weird_poll() { +// let (sender, receiver) = oneshot::channel(); +// let mut body_stream = Ok(BodyStream::new(once(async { +// let x = Box::new(0); +// let y = &x; +// receiver.await.unwrap(); +// let _z = **y; +// Ok::<_, ()>(Bytes::new()) +// }))); -#[test] -fn weird_poll() { - let (sender, receiver) = futures::channel::oneshot::channel(); - let mut body_stream = Ok(BodyStream::new(once(async { - let x = Box::new(0); - let y = &x; - receiver.await.unwrap(); - let _z = **y; - Ok::<_, ()>(Bytes::new()) - }))); +// let waker = noop_waker(); +// let mut cx = Context::from_waker(&waker); - let waker = noop_waker(); - let mut context = Context::from_waker(&waker); - - let _ = body_stream.as_mut().unwrap().poll_next(&mut context); - sender.send(()).unwrap(); - let _ = std::mem::replace(&mut body_stream, Err([0; 32])).unwrap().poll_next(&mut context); -} - -*/ +// let _ = body_stream.as_mut().unwrap().poll_next(&mut cx); +// sender.send(()).unwrap(); +// let _ = std::mem::replace(&mut body_stream, Err([0; 32])) +// .unwrap() +// .poll_next(&mut cx); +// } From da69bb4d12c35d170f85273eeda190ae86551d35 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 15 Jan 2021 23:37:33 +0000 Subject: [PATCH 127/187] implement `App::data` as `App::app_data(Data::new(T)))` (#1906) --- CHANGES.md | 7 ++++ src/app.rs | 25 ++++---------- src/app_service.rs | 17 +++------- src/config.rs | 82 ++++++++++++++++++++++------------------------ src/data.rs | 29 ++++++++++++---- src/resource.rs | 17 +++++----- src/scope.rs | 24 +++----------- src/service.rs | 6 ++-- 8 files changed, 99 insertions(+), 108 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 12caa2df9..a6bcd56cc 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -12,15 +12,22 @@ `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +### Fixed +* Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] + ### Removed * Public field of `web::Path` has been made private. [#1894] * Public field of `web::Query` has been made private. [#1894] * `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +* `AppService::set_service_data`; for custom HTTP service factories adding application data, use the + layered data model by calling `ServiceRequest::add_data_container` when handling + requests instead. [#1906] [#1891]: https://github.com/actix/actix-web/pull/1891 [#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1869]: https://github.com/actix/actix-web/pull/1869 +[#1906]: https://github.com/actix/actix-web/pull/1906 ## 4.0.0-beta.1 - 2021-01-07 diff --git a/src/app.rs b/src/app.rs index fcb491a21..9c5b806ea 100644 --- a/src/app.rs +++ b/src/app.rs @@ -34,7 +34,6 @@ pub struct App { services: Vec>, default: Option>, factory_ref: Rc>>, - data: Vec>, data_factories: Vec, external: Vec, extensions: Extensions, @@ -48,7 +47,6 @@ impl App { let fref = Rc::new(RefCell::new(None)); App { endpoint: AppEntry::new(fref.clone()), - data: Vec::new(), data_factories: Vec::new(), services: Vec::new(), default: None, @@ -101,9 +99,8 @@ where /// web::resource("/index.html").route( /// web::get().to(index))); /// ``` - pub fn data(mut self, data: U) -> Self { - self.data.push(Box::new(Data::new(data))); - self + pub fn data(self, data: U) -> Self { + self.app_data(Data::new(data)) } /// Set application data factory. This function is @@ -157,8 +154,7 @@ where /// some of the resource's configuration could be moved to different module. /// /// ```rust - /// # extern crate actix_web; - /// use actix_web::{web, middleware, App, HttpResponse}; + /// use actix_web::{web, App, HttpResponse}; /// /// // this function could be located in different module /// fn config(cfg: &mut web::ServiceConfig) { @@ -168,12 +164,9 @@ where /// ); /// } /// - /// fn main() { - /// let app = App::new() - /// .wrap(middleware::Logger::default()) - /// .configure(config) // <- register resources - /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); - /// } + /// App::new() + /// .configure(config) // <- register resources + /// .route("/index.html", web::get().to(|| HttpResponse::Ok())); /// ``` pub fn configure(mut self, f: F) -> Self where @@ -181,10 +174,9 @@ where { let mut cfg = ServiceConfig::new(); f(&mut cfg); - self.data.extend(cfg.data); self.services.extend(cfg.services); self.external.extend(cfg.external); - self.extensions.extend(cfg.extensions); + self.extensions.extend(cfg.app_data); self } @@ -374,7 +366,6 @@ where { App { endpoint: apply(mw, self.endpoint), - data: self.data, data_factories: self.data_factories, services: self.services, default: self.default, @@ -436,7 +427,6 @@ where { App { endpoint: apply_fn_factory(self.endpoint, mw), - data: self.data, data_factories: self.data_factories, services: self.services, default: self.default, @@ -462,7 +452,6 @@ where { fn into_factory(self) -> AppInit { AppInit { - data_factories: self.data.into_boxed_slice().into(), async_data_factories: self.data_factories.into_boxed_slice().into(), endpoint: self.endpoint, services: Rc::new(RefCell::new(self.services)), diff --git a/src/app_service.rs b/src/app_service.rs index 4c099d4a4..8a00f59eb 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -10,7 +10,7 @@ use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; use crate::config::{AppConfig, AppService}; -use crate::data::{DataFactory, FnDataFactory}; +use crate::data::FnDataFactory; use crate::error::Error; use crate::guard::Guard; use crate::request::{HttpRequest, HttpRequestPool}; @@ -35,7 +35,6 @@ where { pub(crate) endpoint: T, pub(crate) extensions: RefCell>, - pub(crate) data_factories: Rc<[Box]>, pub(crate) async_data_factories: Rc<[FnDataFactory]>, pub(crate) services: Rc>>>, pub(crate) default: Option>, @@ -71,8 +70,7 @@ where }); // App config - let mut config = - AppService::new(config, default.clone(), self.data_factories.clone()); + let mut config = AppService::new(config, default.clone()); // register services std::mem::take(&mut *self.services.borrow_mut()) @@ -119,8 +117,6 @@ where .take() .unwrap_or_else(Extensions::new); - let data_factories = self.data_factories.clone(); - Box::pin(async move { // async data factories let async_data_factories = factory_futs @@ -133,12 +129,9 @@ where let service = endpoint_fut.await?; // populate app data container from (async) data factories. - data_factories - .iter() - .chain(&async_data_factories) - .for_each(|factory| { - factory.create(&mut app_data); - }); + async_data_factories.iter().for_each(|factory| { + factory.create(&mut app_data); + }); Ok(AppInitService { service, diff --git a/src/config.rs b/src/config.rs index 2b93ae892..8e22dc90d 100644 --- a/src/config.rs +++ b/src/config.rs @@ -5,7 +5,7 @@ use actix_http::Extensions; use actix_router::ResourceDef; use actix_service::{boxed, IntoServiceFactory, ServiceFactory}; -use crate::data::{Data, DataFactory}; +use crate::data::Data; use crate::error::Error; use crate::guard::Guard; use crate::resource::Resource; @@ -31,20 +31,14 @@ pub struct AppService { Option, Option>, )>, - service_data: Rc<[Box]>, } impl AppService { - /// Crate server settings instance - pub(crate) fn new( - config: AppConfig, - default: Rc, - service_data: Rc<[Box]>, - ) -> Self { + /// Crate server settings instance. + pub(crate) fn new(config: AppConfig, default: Rc) -> Self { AppService { config, default, - service_data, root: true, services: Vec::new(), } @@ -75,7 +69,6 @@ impl AppService { default: self.default.clone(), services: Vec::new(), root: false, - service_data: self.service_data.clone(), } } @@ -89,15 +82,7 @@ impl AppService { self.default.clone() } - /// Set global route data - pub fn set_service_data(&self, extensions: &mut Extensions) -> bool { - for f in self.service_data.iter() { - f.create(extensions); - } - !self.service_data.is_empty() - } - - /// Register http service + /// Register HTTP service. pub fn register_service( &mut self, rdef: ResourceDef, @@ -168,47 +153,60 @@ impl Default for AppConfig { } } -/// Service config is used for external configuration. -/// Part of application configuration could be offloaded -/// to set of external methods. This could help with -/// modularization of big application configuration. +/// Enables parts of app configuration to be declared separately from the app itself. Helpful for +/// modularizing large applications. +/// +/// Merge a `ServiceConfig` into an app using [`App::configure`](crate::App::configure). Scope and +/// resources services have similar methods. +/// +/// ``` +/// use actix_web::{web, App, HttpResponse}; +/// +/// // this function could be located in different module +/// fn config(cfg: &mut web::ServiceConfig) { +/// cfg.service(web::resource("/test") +/// .route(web::get().to(|| HttpResponse::Ok())) +/// .route(web::head().to(|| HttpResponse::MethodNotAllowed())) +/// ); +/// } +/// +/// // merge `/test` routes from config function to App +/// App::new().configure(config); +/// ``` pub struct ServiceConfig { pub(crate) services: Vec>, - pub(crate) data: Vec>, pub(crate) external: Vec, - pub(crate) extensions: Extensions, + pub(crate) app_data: Extensions, } impl ServiceConfig { pub(crate) fn new() -> Self { Self { services: Vec::new(), - data: Vec::new(), external: Vec::new(), - extensions: Extensions::new(), + app_data: Extensions::new(), } } - /// Set application data. Application data could be accessed - /// by using `Data` extractor where `T` is data type. + /// Add shared app data item. /// - /// This is same as `App::data()` method. - pub fn data(&mut self, data: S) -> &mut Self { - self.data.push(Box::new(Data::new(data))); + /// Counterpart to [`App::data()`](crate::App::data). + pub fn data(&mut self, data: U) -> &mut Self { + self.app_data(Data::new(data)); self } - /// Set arbitrary data item. + /// Add arbitrary app data item. /// - /// This is same as `App::data()` method. + /// Counterpart to [`App::app_data()`](crate::App::app_data). pub fn app_data(&mut self, ext: U) -> &mut Self { - self.extensions.insert(ext); + self.app_data.insert(ext); self } /// Configure route for a specific path. /// - /// This is same as `App::route()` method. + /// Counterpart to [`App::route()`](crate::App::route). pub fn route(&mut self, path: &str, mut route: Route) -> &mut Self { self.service( Resource::new(path) @@ -217,9 +215,9 @@ impl ServiceConfig { ) } - /// Register http service. + /// Register HTTP service factory. /// - /// This is same as `App::service()` method. + /// Counterpart to [`App::service()`](crate::App::service). pub fn service(&mut self, factory: F) -> &mut Self where F: HttpServiceFactory + 'static, @@ -231,11 +229,11 @@ impl ServiceConfig { /// Register an external resource. /// - /// External resources are useful for URL generation purposes only - /// and are never considered for matching at request time. Calls to - /// `HttpRequest::url_for()` will work as expected. + /// External resources are useful for URL generation purposes only and are never considered for + /// matching at request time. Calls to [`HttpRequest::url_for()`](crate::HttpRequest::url_for) + /// will work as expected. /// - /// This is same as `App::external_service()` method. + /// Counterpart to [`App::external_resource()`](crate::App::external_resource). pub fn external_resource(&mut self, name: N, url: U) -> &mut Self where N: AsRef, diff --git a/src/data.rs b/src/data.rs index 19c258ff0..dd0fbed0e 100644 --- a/src/data.rs +++ b/src/data.rs @@ -10,8 +10,9 @@ use crate::dev::Payload; use crate::extract::FromRequest; use crate::request::HttpRequest; -/// Application data factory +/// Data factory. pub(crate) trait DataFactory { + /// Return true if modifications were made to extensions map. fn create(&self, extensions: &mut Extensions) -> bool; } @@ -126,12 +127,8 @@ impl FromRequest for Data { impl DataFactory for Data { fn create(&self, extensions: &mut Extensions) -> bool { - if !extensions.contains::>() { - extensions.insert(Data(self.0.clone())); - true - } else { - false - } + extensions.insert(Data(self.0.clone())); + true } } @@ -167,6 +164,24 @@ mod tests { let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); + + let mut srv = init_service( + App::new() + .data(10u32) + .data(13u32) + .app_data(12u64) + .app_data(15u64) + .default_service(web::to(|n: web::Data, req: HttpRequest| { + // in each case, the latter insertion should be preserved + assert_eq!(*req.app_data::().unwrap(), 15); + assert_eq!(*n.into_inner(), 13); + HttpResponse::Ok() + })), + ) + .await; + let req = TestRequest::default().to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] diff --git a/src/resource.rs b/src/resource.rs index 843237079..3844b429d 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -203,10 +203,10 @@ where /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { - if self.app_data.is_none() { - self.app_data = Some(Extensions::new()); - } - self.app_data.as_mut().unwrap().insert(data); + self.app_data + .get_or_insert_with(Extensions::new) + .insert(data); + self } @@ -382,18 +382,16 @@ where } else { Some(std::mem::take(&mut self.guards)) }; + let mut rdef = if config.is_root() || !self.rdef.is_empty() { ResourceDef::new(insert_slash(self.rdef.clone())) } else { ResourceDef::new(self.rdef.clone()) }; + if let Some(ref name) = self.name { *rdef.name_mut() = name.clone(); } - // custom app data storage - if let Some(ref mut ext) = self.app_data { - config.set_service_data(ext); - } config.register_service(rdef, guards, self, None) } @@ -479,12 +477,15 @@ impl Service for ResourceService { if let Some(ref app_data) = self.app_data { req.add_data_container(app_data.clone()); } + return route.call(req); } } + if let Some(ref app_data) = self.app_data { req.add_data_container(app_data.clone()); } + self.default.call(req) } } diff --git a/src/scope.rs b/src/scope.rs index 2da4f5546..290a35eb1 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -155,10 +155,10 @@ where /// /// Data of different types from parent contexts will still be accessible. pub fn app_data(mut self, data: U) -> Self { - if self.app_data.is_none() { - self.app_data = Some(Extensions::new()); - } - self.app_data.as_mut().unwrap().insert(data); + self.app_data + .get_or_insert_with(Extensions::new) + .insert(data); + self } @@ -200,18 +200,9 @@ where self.services.extend(cfg.services); self.external.extend(cfg.external); - if !cfg.data.is_empty() { - let mut data = self.app_data.unwrap_or_else(Extensions::new); - - for value in cfg.data.iter() { - value.create(&mut data); - } - - self.app_data = Some(data); - } self.app_data .get_or_insert_with(Extensions::new) - .extend(cfg.extensions); + .extend(cfg.app_data); self } @@ -432,11 +423,6 @@ where rmap.add(&mut rdef, None); } - // custom app data storage - if let Some(ref mut ext) = self.app_data { - config.set_service_data(ext); - } - // complete scope pipeline creation *self.factory_ref.borrow_mut() = Some(ScopeFactory { app_data: self.app_data.take().map(Rc::new), diff --git a/src/service.rs b/src/service.rs index d4fa4acca..596eedd7f 100644 --- a/src/service.rs +++ b/src/service.rs @@ -231,8 +231,10 @@ impl ServiceRequest { self.payload = payload; } - #[doc(hidden)] - /// Add app data container to request's resolution set. + /// Add data container to request's resolution set. + /// + /// In middleware, prefer [`extensions_mut`](ServiceRequest::extensions_mut) for request-local + /// data since it is assumed that the same app data is presented for every request. pub fn add_data_container(&mut self, extensions: Rc) { Rc::get_mut(&mut (self.req).inner) .unwrap() From 1c95fc26547043376a665cbd5f2a35aebc6297e0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 16 Jan 2021 08:15:06 +0800 Subject: [PATCH 128/187] Refactor poll_keepalive for readability (#1901) --- actix-http/src/h1/dispatcher.rs | 121 ++++++++++++++++---------------- 1 file changed, 60 insertions(+), 61 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index feea7f34a..eb5f48105 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -645,83 +645,82 @@ where cx: &mut Context<'_>, ) -> Result<(), DispatchError> { let mut this = self.as_mut().project(); - if this.ka_timer.is_none() { - // shutdown timeout - if this.flags.contains(Flags::SHUTDOWN) { - if let Some(interval) = this.codec.config().client_disconnect_timer() { - this.ka_timer.set(Some(sleep_until(interval))); - } else { - this.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = this.payload.take() { - payload.set_error(PayloadError::Incomplete(None)); - } - return Ok(()); - } - } else { - return Ok(()); - } - } - match this.ka_timer.as_mut().as_pin_mut().unwrap().poll(cx) { - Poll::Ready(()) => { - // if we get timeout during shutdown, drop connection + // when a branch is not explicit return early it's meant to fall through + // and return as Ok(()) + match this.ka_timer.as_mut().as_pin_mut() { + None => { + // conditionally go into shutdown timeout if this.flags.contains(Flags::SHUTDOWN) { - return Err(DispatchError::DisconnectTimeout); - } else if this.ka_timer.as_mut().as_pin_mut().unwrap().deadline() - >= *this.ka_expire - { - // check for any outstanding tasks - if this.state.is_empty() && this.write_buf.is_empty() { - if this.flags.contains(Flags::STARTED) { - trace!("Keep-alive timeout, close connection"); - this.flags.insert(Flags::SHUTDOWN); + if let Some(deadline) = this.codec.config().client_disconnect_timer() + { + // write client disconnect time out and poll again to + // go into Some> branch + this.ka_timer.set(Some(sleep_until(deadline))); + return self.poll_keepalive(cx); + } else { + this.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = this.payload.take() { + payload.set_error(PayloadError::Incomplete(None)); + } + } + } + } + Some(mut timer) => { + // only operate when keep-alive timer is resolved. + if timer.as_mut().poll(cx).is_ready() { + // got timeout during shutdown, drop connection + if this.flags.contains(Flags::SHUTDOWN) { + return Err(DispatchError::DisconnectTimeout); + // exceed deadline. check for any outstanding tasks + } else if timer.deadline() >= *this.ka_expire { + // have no task at hand. + if this.state.is_empty() && this.write_buf.is_empty() { + if this.flags.contains(Flags::STARTED) { + trace!("Keep-alive timeout, close connection"); + this.flags.insert(Flags::SHUTDOWN); - // start shutdown timer - if let Some(deadline) = - this.codec.config().client_disconnect_timer() - { - if let Some(mut timer) = - this.ka_timer.as_mut().as_pin_mut() + // start shutdown timeout + if let Some(deadline) = + this.codec.config().client_disconnect_timer() { timer.as_mut().reset(deadline); let _ = timer.poll(cx); + } else { + // no shutdown timeout, drop socket + this.flags.insert(Flags::WRITE_DISCONNECT); } } else { - // no shutdown timeout, drop socket - this.flags.insert(Flags::WRITE_DISCONNECT); - return Ok(()); + // timeout on first request (slow request) return 408 + if !this.flags.contains(Flags::STARTED) { + trace!("Slow request timeout"); + let _ = self.as_mut().send_response( + Response::RequestTimeout().finish().drop_body(), + ResponseBody::Other(Body::Empty), + ); + this = self.project(); + } else { + trace!("Keep-alive connection timeout"); + } + this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); + this.state.set(State::None); } - } else { - // timeout on first request (slow request) return 408 - if !this.flags.contains(Flags::STARTED) { - trace!("Slow request timeout"); - let _ = self.as_mut().send_response( - Response::RequestTimeout().finish().drop_body(), - ResponseBody::Other(Body::Empty), - ); - this = self.as_mut().project(); - } else { - trace!("Keep-alive connection timeout"); - } - this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); - this.state.set(State::None); - } - } else if let Some(deadline) = - this.codec.config().keep_alive_expire() - { - if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() { + // still have unfinished task. try to reset and register keep-alive. + } else if let Some(deadline) = + this.codec.config().keep_alive_expire() + { timer.as_mut().reset(deadline); let _ = timer.poll(cx); } + // timer resolved but still have not met the keep-alive expire deadline. + // reset and register for later wakeup. + } else { + timer.as_mut().reset(*this.ka_expire); + let _ = timer.poll(cx); } - } else if let Some(mut timer) = this.ka_timer.as_mut().as_pin_mut() { - timer.as_mut().reset(*this.ka_expire); - let _ = timer.poll(cx); } } - Poll::Pending => {} } - Ok(()) } } From ee1014844487245efd79fbbe68e388751ae5b2b6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 17 Jan 2021 05:19:32 +0000 Subject: [PATCH 129/187] revive commented out tests (#1912) --- actix-http/CHANGES.md | 2 + .../src/header/common/content_encoding.rs | 44 +++- awc/Cargo.toml | 2 + awc/tests/test_client.rs | 239 ++++++++---------- awc/tests/test_rustls_client.rs | 77 +++--- src/middleware/compress.rs | 3 +- tests/test_server.rs | 98 ++++--- 7 files changed, 243 insertions(+), 222 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 8b0255286..c535b9866 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -6,6 +6,7 @@ * `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] * `ResponseBuilder::append_header` method which allows using typed headers. [#1869] * `TestRequest::insert_header` method which allows using typed headers. [#1869] +* `ContentEncoding` implements all necessary header traits. [#1912] ### Changed * `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed @@ -24,6 +25,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1904]: https://github.com/actix/actix-web/pull/1904 +[#1912]: https://github.com/actix/actix-web/pull/1912 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/src/header/common/content_encoding.rs b/actix-http/src/header/common/content_encoding.rs index 49f0ff78f..b93d66101 100644 --- a/actix-http/src/header/common/content_encoding.rs +++ b/actix-http/src/header/common/content_encoding.rs @@ -1,3 +1,13 @@ +use std::{convert::Infallible, str::FromStr}; + +use http::header::InvalidHeaderValue; + +use crate::{ + error::ParseError, + header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue}, + HttpMessage, +}; + /// Represents a supported content encoding. #[derive(Copy, Clone, PartialEq, Debug)] pub enum ContentEncoding { @@ -47,6 +57,20 @@ impl ContentEncoding { } } +impl Default for ContentEncoding { + fn default() -> Self { + Self::Identity + } +} + +impl FromStr for ContentEncoding { + type Err = Infallible; + + fn from_str(val: &str) -> Result { + Ok(Self::from(val)) + } +} + impl From<&str> for ContentEncoding { fn from(val: &str) -> ContentEncoding { let val = val.trim(); @@ -58,7 +82,25 @@ impl From<&str> for ContentEncoding { } else if val.eq_ignore_ascii_case("deflate") { ContentEncoding::Deflate } else { - ContentEncoding::Identity + ContentEncoding::default() } } } + +impl IntoHeaderValue for ContentEncoding { + type Error = InvalidHeaderValue; + + fn try_into_value(self) -> Result { + Ok(HeaderValue::from_static(self.as_str())) + } +} + +impl Header for ContentEncoding { + fn name() -> HeaderName { + header::CONTENT_ENCODING + } + + fn parse(msg: &T) -> Result { + from_one_raw_str(msg.headers().get(Self::name())) + } +} diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 0dbf80d33..90f33c9ba 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -67,8 +67,10 @@ actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.2" actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } + brotli2 = "0.3.2" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } env_logger = "0.7" +rcgen = "0.8" webpki = "0.21" diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 3f5bf2958..88987e639 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -9,17 +9,20 @@ use bytes::Bytes; use flate2::read::GzDecoder; use flate2::write::GzEncoder; use flate2::Compression; -use futures_util::future::ok; +use futures_util::{future::ok, stream}; use rand::Rng; -use actix_http::HttpService; +use actix_http::{ + http::{self, StatusCode}, + HttpService, +}; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory}; -use actix_web::dev::{AppConfig, BodyEncoding}; -use actix_web::http::Cookie; -use actix_web::middleware::Compress; use actix_web::{ - http::header, test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, + dev::{AppConfig, BodyEncoding}, + http::{header, Cookie}, + middleware::Compress, + test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; use awc::error::SendRequestError; @@ -557,117 +560,94 @@ async fn test_client_brotli_encoding_large_random() { assert_eq!(bytes, Bytes::from(data)); } -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_client_deflate_encoding() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[actix_rt::test] +async fn test_client_deflate_encoding() { + let srv = test::start(|| { + App::new().default_service(web::to(|body: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Br) + .body(body) + })) + }); -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(STR) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let req = srv.post("/").send_body(STR); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + let mut res = req.await.unwrap(); + assert_eq!(res.status(), StatusCode::OK); -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_client_deflate_encoding_large_random() { -// let data = rand::thread_rng() -// .sample_iter(&rand::distributions::Alphanumeric) -// .take(70_000) -// .collect::(); + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .and_then(|bytes: Bytes| { -// Ok(HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Br) -// .body(bytes)) -// }) -// .responder() -// }) -// }); +#[actix_rt::test] +async fn test_client_deflate_encoding_large_random() { + let data = rand::thread_rng() + .sample_iter(rand::distributions::Alphanumeric) + .map(char::from) + .take(70_000) + .collect::(); -// // client request -// let request = srv -// .post() -// .content_encoding(http::ContentEncoding::Deflate) -// .body(data.clone()) -// .unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let srv = test::start(|| { + App::new().default_service(web::to(|body: Bytes| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Br) + .body(body) + })) + }); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from(data)); -// } + let req = srv.post("/").send_body(data.clone()); -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_client_streaming_explicit() { -// let srv = test::TestServer::start(|app| { -// app.handler(|req: &HttpRequest| { -// req.body() -// .map_err(Error::from) -// .and_then(|body| { -// Ok(HttpResponse::Ok() -// .chunked() -// .content_encoding(http::ContentEncoding::Identity) -// .body(body)) -// }) -// .responder() -// }) -// }); + let mut res = req.await.unwrap(); + let bytes = res.body().await.unwrap(); -// let body = once(Ok(Bytes::from_static(STR.as_ref()))); + assert_eq!(res.status(), StatusCode::OK); + assert_eq!(bytes, Bytes::from(data)); +} -// let request = srv.get("/").body(Body::Streaming(Box::new(body))).unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); +#[actix_rt::test] +async fn test_client_streaming_explicit() { + let srv = test::start(|| { + App::new().default_service(web::to(|body: web::Payload| { + HttpResponse::Ok() + .encoding(http::ContentEncoding::Identity) + .streaming(body) + })) + }); -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } + let body = stream::once(async { + Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) + }); + let req = srv.post("/").send_stream(Box::pin(body)); -// TODO: why is test ignored -// #[actix_rt::test] -// async fn test_body_streaming_implicit() { -// let srv = test::TestServer::start(|app| { -// app.handler(|_| { -// let body = once(Ok(Bytes::from_static(STR.as_ref()))); -// HttpResponse::Ok() -// .content_encoding(http::ContentEncoding::Gzip) -// .body(Body::Streaming(Box::new(body))) -// }) -// }); + let mut res = req.await.unwrap(); + assert!(res.status().is_success()); -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} -// // read response -// let bytes = srv.execute(response.body()).unwrap(); -// assert_eq!(bytes, Bytes::from_static(STR.as_ref())); -// } +#[actix_rt::test] +async fn test_body_streaming_implicit() { + let srv = test::start(|| { + App::new().default_service(web::to(|| { + let body = stream::once(async { + Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) + }); + + HttpResponse::Ok() + .encoding(http::ContentEncoding::Gzip) + .streaming(Box::pin(body)) + })) + }); + + let req = srv.get("/").send(); + + let mut res = req.await.unwrap(); + assert!(res.status().is_success()); + + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(STR.as_ref())); +} #[actix_rt::test] async fn test_client_cookie_handling() { @@ -738,36 +718,35 @@ async fn test_client_cookie_handling() { assert_eq!(c2, cookie2); } -// TODO: why is test ignored -// #[actix_rt::test] -// fn client_read_until_eof() { -// let addr = test::TestServer::unused_addr(); +#[actix_rt::test] +async fn client_unread_response() { + let addr = test::unused_addr(); -// thread::spawn(move || { -// let lst = net::TcpListener::bind(addr).unwrap(); + std::thread::spawn(move || { + let lst = std::net::TcpListener::bind(addr).unwrap(); -// for stream in lst.incoming() { -// let mut stream = stream.unwrap(); -// let mut b = [0; 1000]; -// let _ = stream.read(&mut b).unwrap(); -// let _ = stream -// .write_all(b"HTTP/1.1 200 OK\r\nconnection: close\r\n\r\nwelcome!"); -// } -// }); + for stream in lst.incoming() { + let mut stream = stream.unwrap(); + let mut b = [0; 1000]; + let _ = stream.read(&mut b).unwrap(); + let _ = stream.write_all( + b"HTTP/1.1 200 OK\r\n\ + connection: close\r\n\ + \r\n\ + welcome!", + ); + } + }); -// let mut sys = actix::System::new("test"); + // client request + let req = awc::Client::new().get(format!("http://{}/", addr).as_str()); + let mut res = req.send().await.unwrap(); + assert!(res.status().is_success()); -// // client request -// let req = client::ClientRequest::get(format!("http://{}/", addr).as_str()) -// .finish() -// .unwrap(); -// let response = req.send().await.unwrap(); -// assert!(response.status().is_success()); - -// // read response -// let bytes = response.body().await.unwrap(); -// assert_eq!(bytes, Bytes::from_static(b"welcome!")); -// } + // awc does not read all bytes unless content-length is specified + let bytes = res.body().await.unwrap(); + assert_eq!(bytes, Bytes::from_static(b"")); +} #[actix_rt::test] async fn client_basic_auth() { diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 2f5ffeca2..2da3d9696 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,58 +1,57 @@ #![cfg(feature = "rustls")] -use std::sync::atomic::{AtomicUsize, Ordering}; -use std::sync::Arc; + +extern crate rust_tls as rustls; + +use std::{ + io::BufReader, + sync::{ + atomic::{AtomicUsize, Ordering}, + Arc, + }, +}; use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; -use actix_web::http::Version; -use actix_web::{dev::AppConfig, web, App, HttpResponse}; +use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse}; use futures_util::future::ok; -use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod, SslVerifyMode}; -use rust_tls::ClientConfig; +use rustls::internal::pemfile::{certs, pkcs8_private_keys}; +use rustls::{ClientConfig, NoClientAuth, ServerConfig}; -#[allow(unused)] -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder.set_verify_callback(SslVerifyMode::NONE, |_, _| true); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); - builder.set_alpn_select_callback(|_, protos| { - const H2: &[u8] = b"\x02h2"; - if protos.windows(3).any(|window| window == H2) { - Ok(b"h2") - } else { - Err(open_ssl::ssl::AlpnError::NOACK) - } - }); - builder.set_alpn_protos(b"\x02h2").unwrap(); - builder.build() +fn tls_config() -> ServerConfig { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + + let mut config = ServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + + config } mod danger { - pub struct NoCertificateVerification {} + pub struct NoCertificateVerification; - impl rust_tls::ServerCertVerifier for NoCertificateVerification { + impl rustls::ServerCertVerifier for NoCertificateVerification { fn verify_server_cert( &self, - _roots: &rust_tls::RootCertStore, - _presented_certs: &[rust_tls::Certificate], + _roots: &rustls::RootCertStore, + _presented_certs: &[rustls::Certificate], _dns_name: webpki::DNSNameRef<'_>, _ocsp: &[u8], - ) -> Result { - Ok(rust_tls::ServerCertVerified::assertion()) + ) -> Result { + Ok(rustls::ServerCertVerified::assertion()) } } } -// TODO: why is test ignored -// #[actix_rt::test] -async fn _test_connection_reuse_h2() { +#[actix_rt::test] +async fn test_connection_reuse_h2() { let num = Arc::new(AtomicUsize::new(0)); let num2 = num.clone(); @@ -69,19 +68,19 @@ async fn _test_connection_reuse_h2() { .service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .rustls(tls_config()) .map_err(|_| ()), ) }) .await; - // disable ssl verification + // disable TLS verification let mut config = ClientConfig::new(); let protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()]; config.set_protocols(&protos); config .dangerous() - .set_certificate_verifier(Arc::new(danger::NoCertificateVerification {})); + .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); let client = awc::Client::builder() .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 376719ab2..1f05b7ae3 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -16,6 +16,7 @@ use actix_http::{ Error, }; use actix_service::{Service, Transform}; +use futures_core::ready; use futures_util::future::{ok, Ready}; use pin_project::pin_project; @@ -131,7 +132,7 @@ where fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.project(); - match futures_util::ready!(this.fut.poll(cx)) { + match ready!(this.fut.poll(cx)) { Ok(resp) => { let enc = if let Some(enc) = resp.response().get_encoding() { enc diff --git a/tests/test_server.rs b/tests/test_server.rs index 7756d44d2..0f51a2fdb 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -800,62 +800,58 @@ async fn test_reading_deflate_encoding_large_random_rustls() { assert_eq!(bytes, Bytes::from(data)); } -// TODO: why is test ignored -// #[test] -// fn test_server_cookies() { -// use actix_web::http; +#[actix_rt::test] +async fn test_server_cookies() { + use actix_web::{http, HttpMessage}; -// let srv = test::TestServer::with_factory(|| { -// App::new().resource("/", |r| { -// r.f(|_| { -// HttpResponse::Ok() -// .cookie( -// http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(), -// ) -// .cookie(http::Cookie::new("second", "first_value")) -// .cookie(http::Cookie::new("second", "second_value")) -// .finish() -// }) -// }) -// }); + let srv = test::start(|| { + App::new().default_service(web::to(|| { + HttpResponse::Ok() + .cookie( + http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(), + ) + .cookie(http::Cookie::new("second", "first_value")) + .cookie(http::Cookie::new("second", "second_value")) + .finish() + })) + }); -// let first_cookie = http::CookieBuilder::new("first", "first_value") -// .http_only(true) -// .finish(); -// let second_cookie = http::Cookie::new("second", "second_value"); + let first_cookie = http::CookieBuilder::new("first", "first_value") + .http_only(true) + .finish(); + let second_cookie = http::Cookie::new("second", "second_value"); -// let request = srv.get("/").finish().unwrap(); -// let response = srv.execute(request.send()).unwrap(); -// assert!(response.status().is_success()); + let req = srv.get("/"); + let res = req.send().await.unwrap(); + assert!(res.status().is_success()); -// let cookies = response.cookies().expect("To have cookies"); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } + let cookies = res.cookies().expect("To have cookies"); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } -// let first_cookie = first_cookie.to_string(); -// let second_cookie = second_cookie.to_string(); -// //Check that we have exactly two instances of raw cookie headers -// let cookies = response -// .headers() -// .get_all(http::header::SET_COOKIE) -// .iter() -// .map(|header| header.to_str().expect("To str").to_string()) -// .collect::>(); -// assert_eq!(cookies.len(), 2); -// if cookies[0] == first_cookie { -// assert_eq!(cookies[1], second_cookie); -// } else { -// assert_eq!(cookies[0], second_cookie); -// assert_eq!(cookies[1], first_cookie); -// } -// } + let first_cookie = first_cookie.to_string(); + let second_cookie = second_cookie.to_string(); + // Check that we have exactly two instances of raw cookie headers + let cookies = res + .headers() + .get_all(http::header::SET_COOKIE) + .map(|header| header.to_str().expect("To str").to_string()) + .collect::>(); + assert_eq!(cookies.len(), 2); + if cookies[0] == first_cookie { + assert_eq!(cookies[1], second_cookie); + } else { + assert_eq!(cookies[0], second_cookie); + assert_eq!(cookies[1], first_cookie); + } +} #[actix_rt::test] async fn test_slow_request() { From 0c8196f8b07022e56fb4f967c26426cd3076ffdf Mon Sep 17 00:00:00 2001 From: Logan Magee Date: Mon, 18 Jan 2021 03:14:29 -0900 Subject: [PATCH 130/187] Remove HttpResponseBuilder::json2() (#1903) It's not necessary to keep both json() and json2() around since the former reduces the ownership of its parameter to a borrow only to pass the reference to the latter. Users can instead borrow themselves when passing an owned value: there doesn't need to be two separate functions. This change also makes HttpResponseBuilder::json() take T: Deref so it can accept both references and web extractors like web::Json. --- actix-http/CHANGES.md | 3 +++ actix-http/src/response.rs | 37 ++++++++----------------------------- src/test.rs | 20 ++++++-------------- 3 files changed, 17 insertions(+), 43 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c535b9866..34de7727a 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -14,6 +14,8 @@ * Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] * `Extensions::insert` returns Option of replaced item. [#1904] +* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by + reference. [#1903] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -24,6 +26,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 +[#1903]: https://github.com/actix/actix-web/pull/1903 [#1904]: https://github.com/actix/actix-web/pull/1904 [#1912]: https://github.com/actix/actix-web/pull/1912 diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 880f93c6a..110514e05 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -5,6 +5,7 @@ use std::{ convert::TryInto, fmt, future::Future, + ops, pin::Pin, str, task::{Context, Poll}, @@ -651,19 +652,15 @@ impl ResponseBuilder { self.body(Body::from_message(BodyStream::new(stream))) } - #[inline] /// Set a json body and generate `Response` /// /// `ResponseBuilder` can not be used after this call. - pub fn json(&mut self, value: T) -> Response { - self.json2(&value) - } - - /// Set a json body and generate `Response` - /// - /// `ResponseBuilder` can not be used after this call. - pub fn json2(&mut self, value: &T) -> Response { - match serde_json::to_string(value) { + pub fn json(&mut self, value: T) -> Response + where + T: ops::Deref, + T::Target: Serialize, + { + match serde_json::to_string(&*value) { Ok(body) => { let contains = if let Some(parts) = parts(&mut self.head, &self.err) { parts.headers.contains_key(header::CONTENT_TYPE) @@ -979,25 +976,7 @@ mod tests { fn test_json_ct() { let resp = Response::build(StatusCode::OK) .insert_header((CONTENT_TYPE, "text/json")) - .json(vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("text/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json2() { - let resp = Response::build(StatusCode::OK).json2(&vec!["v1", "v2", "v3"]); - let ct = resp.headers().get(CONTENT_TYPE).unwrap(); - assert_eq!(ct, HeaderValue::from_static("application/json")); - assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); - } - - #[test] - fn test_json2_ct() { - let resp = Response::build(StatusCode::OK) - .insert_header((CONTENT_TYPE, "text/json")) - .json2(&vec!["v1", "v2", "v3"]); + .json(&vec!["v1", "v2", "v3"]); let ct = resp.headers().get(CONTENT_TYPE).unwrap(); assert_eq!(ct, HeaderValue::from_static("text/json")); assert_eq!(resp.body().get_ref(), b"[\"v1\",\"v2\",\"v3\"]"); diff --git a/src/test.rs b/src/test.rs index 55b436084..769cece55 100644 --- a/src/test.rs +++ b/src/test.rs @@ -239,7 +239,7 @@ where /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { /// HttpResponse::Ok() -/// .json(person.into_inner())}) +/// .json(person)}) /// )) /// ).await; /// @@ -299,7 +299,7 @@ where /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { /// HttpResponse::Ok() -/// .json(person.into_inner())}) +/// .json(person)}) /// )) /// ).await; /// @@ -1121,9 +1121,7 @@ mod tests { #[actix_rt::test] async fn test_response_json() { let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) - }), + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1142,9 +1140,7 @@ mod tests { #[actix_rt::test] async fn test_body_json() { let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) - }), + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1164,9 +1160,7 @@ mod tests { #[actix_rt::test] async fn test_request_response_form() { let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Form| { - HttpResponse::Ok().json(person.into_inner()) - }), + web::post().to(|person: web::Form| HttpResponse::Ok().json(person)), ))) .await; @@ -1190,9 +1184,7 @@ mod tests { #[actix_rt::test] async fn test_request_response_json() { let mut app = init_service(App::new().service(web::resource("/people").route( - web::post().to(|person: web::Json| { - HttpResponse::Ok().json(person.into_inner()) - }), + web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; From c201c15f8c19d0056f27b8a7e1fc675bbdfca9e8 Mon Sep 17 00:00:00 2001 From: Jonas Platte Date: Sun, 24 Jan 2021 01:32:10 +0100 Subject: [PATCH 131/187] Improve documentation for PayloadConfig (#1923) --- src/types/payload.rs | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index ad0bfd14f..ce4a9cb1b 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -191,6 +191,9 @@ fn bytes_to_string(body: Bytes, encoding: &'static Encoding) -> Result Self { Self { limit, @@ -206,7 +209,7 @@ impl PayloadConfig { } } - /// Set maximum accepted payload size. The default limit is 256kB. + /// Set maximum accepted payload size in bytes. The default limit is 256kB. pub fn limit(mut self, limit: usize) -> Self { self.limit = limit; self From 51e54dac8b18ea0ac0b1d55f6a9d8af903e0e811 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 27 Jan 2021 02:49:57 -0800 Subject: [PATCH 132/187] fix limit not working on HttpMessageBody::limit (#1938) --- src/types/payload.rs | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/types/payload.rs b/src/types/payload.rs index ce4a9cb1b..e8fba0632 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -289,10 +289,12 @@ impl HttpMessageBody { if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { match l.to_str() { Ok(s) => match s.parse::() { - Ok(l) if l > DEFAULT_CONFIG_LIMIT => { - err = Some(PayloadError::Overflow) + Ok(l) => { + if l > DEFAULT_CONFIG_LIMIT { + err = Some(PayloadError::Overflow); + } + length = Some(l) } - Ok(l) => length = Some(l), Err(_) => err = Some(PayloadError::UnknownLength), }, Err(_) => err = Some(PayloadError::UnknownLength), @@ -316,9 +318,11 @@ impl HttpMessageBody { /// Change max size of payload. By default max size is 256kB pub fn limit(mut self, limit: usize) -> Self { if let Some(l) = self.length { - if l > limit { - self.err = Some(PayloadError::Overflow); - } + self.err = if l > limit { + Some(PayloadError::Overflow) + } else { + None + }; } self.limit = limit; self From 83fb4978ad9c7758449d9fb71dc9eab5af442eb7 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 08:05:33 -0800 Subject: [PATCH 133/187] fix awc test_client test (#1960) --- awc/tests/test_client.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 88987e639..8ca94ec91 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -722,9 +722,9 @@ async fn test_client_cookie_handling() { async fn client_unread_response() { let addr = test::unused_addr(); - std::thread::spawn(move || { - let lst = std::net::TcpListener::bind(addr).unwrap(); + let lst = std::net::TcpListener::bind(addr).unwrap(); + std::thread::spawn(move || { for stream in lst.incoming() { let mut stream = stream.unwrap(); let mut b = [0; 1000]; From 20cf0094e591f53c31aa87987c0875bcf3595a05 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 08:23:59 -0800 Subject: [PATCH 134/187] fix master branch build. change web::block output type. (#1957) --- CHANGES.md | 2 + Cargo.toml | 6 +- actix-files/Cargo.toml | 2 +- actix-files/src/chunked.rs | 106 +++++++++++++++++------------ actix-files/src/named.rs | 16 +---- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 2 + actix-http/Cargo.toml | 2 +- actix-http/src/encoding/decoder.rs | 11 +-- actix-http/src/encoding/encoder.rs | 13 +--- actix-http/src/error.rs | 27 +++----- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- src/test.rs | 2 +- src/web.rs | 13 ++-- 17 files changed, 98 insertions(+), 114 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index a6bcd56cc..3f6b09d7a 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,6 +11,7 @@ * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957] ### Fixed * Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] @@ -28,6 +29,7 @@ [#1894]: https://github.com/actix/actix-web/pull/1894 [#1869]: https://github.com/actix/actix-web/pull/1869 [#1906]: https://github.com/actix/actix-web/pull/1906 +[#1957]: https://github.com/actix/actix-web/pull/1957 ## 4.0.0-beta.1 - 2021-01-07 diff --git a/Cargo.toml b/Cargo.toml index bae6cb6cb..28cee0dad 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,11 +74,11 @@ required-features = ["rustls"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-macros = "0.1.0" +actix-macros = "=0.2.0-beta.1" actix-router = "0.2.4" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-server = "2.0.0-beta.2" -actix-service = "2.0.0-beta.3" +actix-service = "=2.0.0-beta.3" actix-utils = "3.0.0-beta.1" actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index bde2cb717..b72631eb1 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-web = "4.0.0-beta.1" diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 5b7b17dc4..2a62b1d26 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -8,7 +8,7 @@ use std::{ }; use actix_web::{ - error::{Error, ErrorInternalServerError}, + error::{BlockingError, Error}, rt::task::{spawn_blocking, JoinHandle}, }; use bytes::Bytes; @@ -18,11 +18,26 @@ use futures_core::{ready, Stream}; /// A helper created from a `std::fs::File` which reads the file /// chunk-by-chunk on a `ThreadPool`. pub struct ChunkedReadFile { - pub(crate) size: u64, - pub(crate) offset: u64, - pub(crate) file: Option, - pub(crate) fut: Option>>, - pub(crate) counter: u64, + size: u64, + offset: u64, + state: ChunkedReadFileState, + counter: u64, +} + +enum ChunkedReadFileState { + File(Option), + Future(JoinHandle>), +} + +impl ChunkedReadFile { + pub(crate) fn new(size: u64, offset: u64, file: File) -> Self { + Self { + size, + offset, + state: ChunkedReadFileState::File(Some(file)), + counter: 0, + } + } } impl fmt::Debug for ChunkedReadFile { @@ -38,49 +53,52 @@ impl Stream for ChunkedReadFile { mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Poll> { - if let Some(ref mut fut) = self.fut { - let res = match ready!(Pin::new(fut).poll(cx)) { - Ok(Ok((file, bytes))) => { - self.fut.take(); - self.file = Some(file); + let this = self.as_mut().get_mut(); + match this.state { + ChunkedReadFileState::File(ref mut file) => { + let size = this.size; + let offset = this.offset; + let counter = this.counter; - self.offset += bytes.len() as u64; - self.counter += bytes.len() as u64; + if size == counter { + Poll::Ready(None) + } else { + let mut file = file + .take() + .expect("ChunkedReadFile polled after completion"); - Ok(bytes) + let fut = spawn_blocking(move || { + let max_bytes = + cmp::min(size.saturating_sub(counter), 65_536) as usize; + + let mut buf = Vec::with_capacity(max_bytes); + file.seek(io::SeekFrom::Start(offset))?; + + let n_bytes = file + .by_ref() + .take(max_bytes as u64) + .read_to_end(&mut buf)?; + + if n_bytes == 0 { + return Err(io::ErrorKind::UnexpectedEof.into()); + } + + Ok((file, Bytes::from(buf))) + }); + this.state = ChunkedReadFileState::Future(fut); + self.poll_next(cx) } - Ok(Err(e)) => Err(e.into()), - Err(_) => Err(ErrorInternalServerError("Unexpected error")), - }; - return Poll::Ready(Some(res)); - } + } + ChunkedReadFileState::Future(ref mut fut) => { + let (file, bytes) = + ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + this.state = ChunkedReadFileState::File(Some(file)); - let size = self.size; - let offset = self.offset; - let counter = self.counter; + this.offset += bytes.len() as u64; + this.counter += bytes.len() as u64; - if size == counter { - Poll::Ready(None) - } else { - let mut file = self.file.take().expect("Use after completion"); - - self.fut = Some(spawn_blocking(move || { - let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; - - let mut buf = Vec::with_capacity(max_bytes); - file.seek(io::SeekFrom::Start(offset))?; - - let n_bytes = - file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; - - if n_bytes == 0 { - return Err(io::ErrorKind::UnexpectedEof.into()); - } - - Ok((file, Bytes::from(buf))) - })); - - self.poll_next(cx) + Poll::Ready(Some(Ok(bytes))) + } } } } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 8cd2a23f9..6fa3f7c6c 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -298,13 +298,7 @@ impl NamedFile { res.encoding(current_encoding); } - let reader = ChunkedReadFile { - size: self.md.len(), - offset: 0, - file: Some(self.file), - fut: None, - counter: 0, - }; + let reader = ChunkedReadFile::new(self.md.len(), 0, self.file); return res.streaming(reader); } @@ -426,13 +420,7 @@ impl NamedFile { return resp.status(StatusCode::NOT_MODIFIED).finish(); } - let reader = ChunkedReadFile { - offset, - size: length, - file: Some(self.file), - fut: None, - counter: 0, - }; + let reader = ChunkedReadFile::new(length, offset, self.file); if offset != 0 || length != self.md.len() { resp.status(StatusCode::PARTIAL_CONTENT); diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 772b60f76..bc71f967a 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -33,7 +33,7 @@ actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-tls = "3.0.0-beta.2" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-server = "2.0.0-beta.2" awc = "3.0.0-beta.1" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 34de7727a..73339974d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -16,6 +16,7 @@ * `Extensions::insert` returns Option of replaced item. [#1904] * Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by reference. [#1903] +* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -29,6 +30,7 @@ [#1903]: https://github.com/actix/actix-web/pull/1903 [#1904]: https://github.com/actix/actix-web/pull/1904 [#1912]: https://github.com/actix/actix-web/pull/1912 +[#1957]: https://github.com/actix/actix-web/pull/1957 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index af6209248..8f638c1d7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -43,7 +43,7 @@ actors = ["actix"] actix-service = "2.0.0-beta.3" actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-tls = "3.0.0-beta.2" actix = { version = "0.11.0-beta.1", optional = true } diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index b26609911..2cf2f6e03 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -79,15 +79,8 @@ where ) -> Poll> { loop { if let Some(ref mut fut) = self.fut { - let (chunk, decoder) = match ready!(Pin::new(fut).poll(cx)) { - Ok(Ok(item)) => item, - Ok(Err(e)) => { - return Poll::Ready(Some(Err(BlockingError::Error(e).into()))) - } - Err(_) => { - return Poll::Ready(Some(Err(BlockingError::Canceled.into()))) - } - }; + let (chunk, decoder) = + ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; self.decoder = Some(decoder); self.fut.take(); if let Some(chunk) = chunk { diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 28c757076..1d4a8e933 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -136,17 +136,8 @@ impl MessageBody for Encoder { } if let Some(ref mut fut) = this.fut { - let mut encoder = match ready!(Pin::new(fut).poll(cx)) { - Ok(Ok(item)) => item, - Ok(Err(e)) => { - return Poll::Ready(Some(Err(BlockingError::Error(e).into()))) - } - Err(_) => { - return Poll::Ready(Some(Err( - BlockingError::::Canceled.into(), - ))) - } - }; + let mut encoder = + ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; let chunk = encoder.take(); *this.encoder = Some(encoder); this.fut.take(); diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 9ff154240..28697cbbf 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -297,17 +297,13 @@ impl From for ParseError { /// A set of errors that can occur running blocking tasks in thread pool. #[derive(Debug, Display)] -pub enum BlockingError { - #[display(fmt = "{:?}", _0)] - Error(E), - #[display(fmt = "Thread pool is gone")] - Canceled, -} +#[display(fmt = "Blocking thread pool is gone")] +pub struct BlockingError; -impl std::error::Error for BlockingError {} +impl std::error::Error for BlockingError {} /// `InternalServerError` for `BlockingError` -impl ResponseError for BlockingError {} +impl ResponseError for BlockingError {} #[derive(Display, Debug)] /// A set of errors that can occur during payload parsing @@ -372,15 +368,12 @@ impl From for PayloadError { } } -impl From> for PayloadError { - fn from(err: BlockingError) -> Self { - match err { - BlockingError::Error(e) => PayloadError::Io(e), - BlockingError::Canceled => PayloadError::Io(io::Error::new( - io::ErrorKind::Other, - "Operation is canceled", - )), - } +impl From for PayloadError { + fn from(_: BlockingError) -> Self { + PayloadError::Io(io::Error::new( + io::ErrorKind::Other, + "Operation is canceled", + )) } } diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 44a7e8d16..60620bce7 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -28,5 +28,5 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 0f90edb07..34bf713ae 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" env_logger = "0.7" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 00875cf1b..70ab55681 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 90f33c9ba..675110e5a 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -40,7 +40,7 @@ compress = ["actix-http/compress"] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.3" actix-http = "3.0.0-beta.1" -actix-rt = "2.0.0-beta.2" +actix-rt = "=2.0.0-beta.2" base64 = "0.13" bytes = "1" diff --git a/src/test.rs b/src/test.rs index 769cece55..f3347bbbf 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1213,7 +1213,7 @@ mod tests { match res { Ok(value) => Ok(HttpResponse::Ok() .content_type("text/plain") - .body(format!("Async with block value: {}", value))), + .body(format!("Async with block value: {:?}", value))), Err(_) => panic!("Unexpected"), } } diff --git a/src/web.rs b/src/web.rs index 88071f551..3b4475b63 100644 --- a/src/web.rs +++ b/src/web.rs @@ -274,14 +274,11 @@ pub fn service(path: T) -> WebService { /// Execute blocking function on a thread pool, returns future that resolves /// to result of the function execution. -pub async fn block(f: F) -> Result> +pub fn block(f: F) -> impl Future> where - F: FnOnce() -> Result + Send + 'static, - I: Send + 'static, - E: Send + std::fmt::Debug + 'static, + F: FnOnce() -> R + Send + 'static, + R: Send + 'static, { - match actix_rt::task::spawn_blocking(f).await { - Ok(res) => res.map_err(BlockingError::Error), - Err(_) => Err(BlockingError::Canceled), - } + let fut = actix_rt::task::spawn_blocking(f); + async { fut.await.map_err(|_| BlockingError) } } From 41bc04b1c43c860225b5914d97cf9f7741f1d74d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 17:00:40 -0800 Subject: [PATCH 135/187] Use immutable reference of service state. Update awc dns resolver. (#1905) --- CHANGES.md | 7 +- Cargo.toml | 16 +-- actix-files/Cargo.toml | 4 +- actix-files/src/lib.rs | 146 +++++++++++++------------- actix-files/src/service.rs | 17 +-- actix-files/tests/encoding.rs | 9 +- actix-http-test/Cargo.toml | 10 +- actix-http-test/src/lib.rs | 5 +- actix-http/CHANGES.md | 5 +- actix-http/Cargo.toml | 16 +-- actix-http/src/client/config.rs | 2 +- actix-http/src/client/connection.rs | 3 +- actix-http/src/client/connector.rs | 14 +-- actix-http/src/client/error.rs | 3 +- actix-http/src/client/pool.rs | 23 ++-- actix-http/src/h1/dispatcher.rs | 22 ++-- actix-http/src/h1/expect.rs | 8 +- actix-http/src/h1/service.rs | 16 +-- actix-http/src/h1/upgrade.rs | 8 +- actix-http/src/h2/dispatcher.rs | 9 +- actix-http/src/h2/service.rs | 11 +- actix-http/src/header/into_value.rs | 2 +- actix-http/src/service.rs | 24 ++--- actix-http/tests/test_ws.rs | 9 +- actix-multipart/Cargo.toml | 4 +- actix-web-actors/Cargo.toml | 4 +- actix-web-actors/src/context.rs | 11 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 3 +- actix-web-codegen/tests/test_macro.rs | 10 +- awc/CHANGES.md | 4 + awc/Cargo.toml | 11 +- awc/README.md | 2 +- awc/src/builder.rs | 9 +- awc/src/connect.rs | 16 +-- awc/src/lib.rs | 7 +- awc/src/sender.rs | 10 +- awc/src/ws.rs | 6 +- awc/tests/test_client.rs | 4 +- benches/responder.rs | 4 +- benches/server.rs | 2 +- benches/service.rs | 4 +- src/app.rs | 32 +++--- src/app_service.rs | 8 +- src/config.rs | 16 +-- src/data.rs | 16 +-- src/handler.rs | 4 +- src/middleware/compat.rs | 16 +-- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 12 +-- src/middleware/default_headers.rs | 8 +- src/middleware/err_handlers.rs | 10 +- src/middleware/logger.rs | 8 +- src/middleware/normalize.rs | 56 +++++----- src/request.rs | 34 +++--- src/request_data.rs | 4 +- src/resource.rs | 52 ++++----- src/responder.rs | 2 +- src/route.rs | 22 ++-- src/scope.rs | 129 +++++++++++------------ src/server.rs | 2 +- src/service.rs | 6 +- src/test.rs | 72 ++++++------- src/types/payload.rs | 18 ++-- tests/test_httpserver.rs | 4 +- 65 files changed, 497 insertions(+), 538 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 3f6b09d7a..e9a788950 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -11,8 +11,12 @@ * `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] +* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` + in argument [#1905] +* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure + argument [#1905] * `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957] - + ### Fixed * Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] @@ -28,6 +32,7 @@ [#1893]: https://github.com/actix/actix-web/pull/1893 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 [#1906]: https://github.com/actix/actix-web/pull/1906 [#1957]: https://github.com/actix/actix-web/pull/1957 diff --git a/Cargo.toml b/Cargo.toml index 28cee0dad..1e2c35d6a 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -74,13 +74,13 @@ required-features = ["rustls"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-macros = "=0.2.0-beta.1" -actix-router = "0.2.4" -actix-rt = "=2.0.0-beta.2" -actix-server = "2.0.0-beta.2" -actix-service = "=2.0.0-beta.3" -actix-utils = "3.0.0-beta.1" -actix-tls = { version = "3.0.0-beta.2", default-features = false, optional = true } +actix-macros = "0.2.0" +actix-router = "0.2.7" +actix-rt = "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.3", default-features = false, optional = true } actix-web-codegen = "0.4.0" actix-http = "3.0.0-beta.1" @@ -108,7 +108,7 @@ rust-tls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] -actix = "0.11.0-beta.1" +actix = { version = "0.11.0-beta.2", default-features = false } actix-http = { version = "3.0.0-beta.1", features = ["actors"] } rand = "0.8" env_logger = "0.8" diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b72631eb1..3371ab060 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } @@ -31,5 +31,5 @@ percent-encoding = "2.1" v_htmlescape = "0.12" [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" actix-web = "4.0.0-beta.1" diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 7b3e9adb5..b2f6384a8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -73,7 +73,8 @@ mod tests { }, middleware::Compress, test::{self, TestRequest}, - web, App, HttpResponse, Responder, + web::{self, Bytes}, + App, HttpResponse, Responder, }; use futures_util::future::ok; @@ -365,7 +366,7 @@ mod tests { DispositionType::Attachment } - let mut srv = test::init_service( + let srv = test::init_service( App::new().service( Files::new("/", ".") .mime_override(all_attachment) @@ -375,7 +376,7 @@ mod tests { .await; let request = TestRequest::get().uri("/").to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::OK); let content_disposition = response @@ -390,7 +391,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_ranges_status_code() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/test", ".").index_file("Cargo.toml")), ) .await; @@ -400,7 +401,7 @@ mod tests { .uri("/t%65st/Cargo.toml") .insert_header((header::RANGE, "bytes=10-20")) .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::PARTIAL_CONTENT); // Invalid range header @@ -408,7 +409,7 @@ mod tests { .uri("/t%65st/Cargo.toml") .insert_header((header::RANGE, "bytes=1-0")) .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::RANGE_NOT_SATISFIABLE); } @@ -495,14 +496,14 @@ mod tests { #[actix_rt::test] async fn test_static_files_with_spaces() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").index_file("Cargo.toml")), ) .await; let request = TestRequest::get() .uri("/tests/test%20space.binary") .to_request(); - let response = test::call_service(&mut srv, request).await; + let response = test::call_service(&srv, request).await; assert_eq!(response.status(), StatusCode::OK); let bytes = test::read_body(response).await; @@ -512,28 +513,28 @@ mod tests { #[actix_rt::test] async fn test_files_not_allowed() { - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default() .uri("/Cargo.toml") .method(Method::POST) .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default() .method(Method::PUT) .uri("/Cargo.toml") .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); } #[actix_rt::test] async fn test_files_guards() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").use_guards(guard::Post())), ) .await; @@ -543,13 +544,13 @@ mod tests { .method(Method::POST) .to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_named_file_content_encoding() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { NamedFile::open("Cargo.toml") .unwrap() @@ -562,14 +563,14 @@ mod tests { .uri("/") .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); - let res = test::call_service(&mut srv, request).await; + let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); } #[actix_rt::test] async fn test_named_file_content_encoding_gzip() { - let mut srv = test::init_service(App::new().wrap(Compress::default()).service( + let srv = test::init_service(App::new().wrap(Compress::default()).service( web::resource("/").to(|| async { NamedFile::open("Cargo.toml") .unwrap() @@ -582,7 +583,7 @@ mod tests { .uri("/") .insert_header((header::ACCEPT_ENCODING, "gzip")) .to_request(); - let res = test::call_service(&mut srv, request).await; + let res = test::call_service(&srv, request).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( res.headers() @@ -604,27 +605,27 @@ mod tests { #[actix_rt::test] async fn test_static_files() { - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ) .await; let req = TestRequest::with_uri("/missing").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service(App::new().service(Files::new("/", "."))).await; + let srv = test::init_service(App::new().service(Files::new("/", "."))).await; let req = TestRequest::default().to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").show_files_listing()), ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), "text/html; charset=utf-8" @@ -637,16 +638,16 @@ mod tests { #[actix_rt::test] async fn test_redirect_to_slash_directory() { // should not redirect if no index - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", ".").redirect_to_slash_directory()), ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); // should redirect if index present - let mut srv = test::init_service( + let srv = test::init_service( App::new().service( Files::new("/", ".") .index_file("test.png") @@ -655,12 +656,12 @@ mod tests { ) .await; let req = TestRequest::with_uri("/tests").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::FOUND); // should not redirect if the path is wrong let req = TestRequest::with_uri("/not_existing").to_request(); - let resp = test::call_service(&mut srv, req).await; + let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); } @@ -672,7 +673,7 @@ mod tests { #[actix_rt::test] async fn test_default_handler_file_missing() { - let mut st = Files::new("/", ".") + let st = Files::new("/", ".") .default_handler(|req: ServiceRequest| { ok(req.into_response(HttpResponse::Ok().body("default content"))) }) @@ -681,7 +682,7 @@ mod tests { .unwrap(); let req = TestRequest::with_uri("/missing").to_srv_request(); - let resp = test::call_service(&mut st, req).await; + let resp = test::call_service(&st, req).await; assert_eq!(resp.status(), StatusCode::OK); let bytes = test::read_body(resp).await; assert_eq!(bytes, web::Bytes::from_static(b"default content")); @@ -750,54 +751,49 @@ mod tests { // ); // } - // #[actix_rt::test] - // fn integration_serve_index() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); + #[actix_rt::test] + async fn integration_serve_index() { + let srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("Cargo.toml")), + ) + .await; - // let request = srv.get().uri(srv.url("/test")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); + let req = TestRequest::get().uri("/test").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); - // let request = srv.get().uri(srv.url("/test/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // let bytes = srv.execute(response.body()).unwrap(); - // let data = Bytes::from(fs::read("Cargo.toml").unwrap()); - // assert_eq!(bytes, data); + let bytes = test::read_body(res).await; - // // nonexistent index file - // let request = srv.get().uri(srv.url("/test/unknown")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); - // let request = srv.get().uri(srv.url("/test/unknown/")).finish().unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::NOT_FOUND); - // } + let req = TestRequest::get().uri("/test/").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); - // #[actix_rt::test] - // fn integration_percent_encoded() { - // let mut srv = test::TestServer::with_factory(|| { - // App::new().handler( - // "test", - // Files::new(".").index_file("Cargo.toml"), - // ) - // }); + let bytes = test::read_body(res).await; + let data = Bytes::from(fs::read("Cargo.toml").unwrap()); + assert_eq!(bytes, data); - // let request = srv - // .get() - // .uri(srv.url("/test/%43argo.toml")) - // .finish() - // .unwrap(); - // let response = srv.execute(request.send()).unwrap(); - // assert_eq!(response.status(), StatusCode::OK); - // } + // nonexistent index file + let req = TestRequest::get().uri("/test/unknown").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + + let req = TestRequest::get().uri("/test/unknown/").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn integration_percent_encoded() { + let srv = test::init_service( + App::new().service(Files::new("test", ".").index_file("Cargo.toml")), + ) + .await; + + let req = TestRequest::get().uri("/test/%43argo.toml").to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::OK); + } } diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index 25e285efc..a9822f486 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -1,9 +1,4 @@ -use std::{ - fmt, io, - path::PathBuf, - rc::Rc, - task::{Context, Poll}, -}; +use std::{fmt, io, path::PathBuf, rc::Rc, task::Poll}; use actix_service::Service; use actix_web::{ @@ -40,10 +35,10 @@ type FilesServiceFuture = Either< >; impl FilesService { - fn handle_err(&mut self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { + fn handle_err(&self, e: io::Error, req: ServiceRequest) -> FilesServiceFuture { log::debug!("Failed to handle {}: {}", req.path(), e); - if let Some(ref mut default) = self.default { + if let Some(ref default) = self.default { Either::Right(default.call(req)) } else { Either::Left(ok(req.error_response(e))) @@ -62,11 +57,9 @@ impl Service for FilesService { type Error = Error; type Future = FilesServiceFuture; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let is_method_valid = if let Some(guard) = &self.guards { // execute user defined guards (**guard).check(req.head()) diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index d7e01b305..6cfa3a7f7 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -11,11 +11,10 @@ use actix_web::{ #[actix_rt::test] async fn test_utf8_file_contents() { // use default ISO-8859-1 encoding - let mut srv = - test::init_service(App::new().service(Files::new("/", "./tests"))).await; + let srv = test::init_service(App::new().service(Files::new("/", "./tests"))).await; let req = TestRequest::with_uri("/utf8.txt").to_request(); - let res = test::call_service(&mut srv, req).await; + let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( @@ -24,13 +23,13 @@ async fn test_utf8_file_contents() { ); // prefer UTF-8 encoding - let mut srv = test::init_service( + let srv = test::init_service( App::new().service(Files::new("/", "./tests").prefer_utf8(true)), ) .await; let req = TestRequest::with_uri("/utf8.txt").to_request(); - let res = test::call_service(&mut srv, req).await; + let res = test::call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); assert_eq!( diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index bc71f967a..0a804e109 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,12 +29,12 @@ default = [] openssl = ["open-ssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-tls = "3.0.0-beta.2" -actix-utils = "3.0.0-beta.1" -actix-rt = "=2.0.0-beta.2" -actix-server = "2.0.0-beta.2" +actix-tls = "3.0.0-beta.3" +actix-utils = "3.0.0-beta.2" +actix-rt = "2" +actix-server = "2.0.0-beta.3" awc = "3.0.0-beta.1" base64 = "0.13" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 4fd74d6eb..2958b7f59 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -60,7 +60,7 @@ pub async fn test_server_with_addr>( // run server in separate thread thread::spawn(move || { - let sys = System::new("actix-test-server"); + let sys = System::new(); let local_addr = tcp.local_addr().unwrap(); let srv = Server::build() @@ -69,7 +69,7 @@ pub async fn test_server_with_addr>( .disable_signals(); sys.block_on(async { - srv.start(); + srv.run(); tx.send((System::current(), local_addr)).unwrap(); }); @@ -106,7 +106,6 @@ pub async fn test_server_with_addr>( Client::builder().connector(connector).finish() }; - actix_tls::connect::start_default_resolver().await.unwrap(); TestServer { addr, diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 73339974d..52980fb5d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -16,7 +16,9 @@ * `Extensions::insert` returns Option of replaced item. [#1904] * Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by reference. [#1903] -* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] +* `client::error::ConnectError` Resolver variant contains `Box` type [#1905] +* `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -29,6 +31,7 @@ [#1894]: https://github.com/actix/actix-web/pull/1894 [#1903]: https://github.com/actix/actix-web/pull/1903 [#1904]: https://github.com/actix/actix-web/pull/1904 +[#1905]: https://github.com/actix/actix-web/pull/1905 [#1912]: https://github.com/actix/actix-web/pull/1912 [#1957]: https://github.com/actix/actix-web/pull/1957 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8f638c1d7..f55dc3b69 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -40,12 +40,12 @@ secure-cookies = ["cookie/secure"] actors = ["actix"] [dependencies] -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" actix-codec = "0.4.0-beta.1" -actix-utils = "3.0.0-beta.1" -actix-rt = "=2.0.0-beta.2" +actix-utils = "3.0.0-beta.2" +actix-rt = "2" actix-tls = "3.0.0-beta.2" -actix = { version = "0.11.0-beta.1", optional = true } +actix = { version = "0.11.0-beta.2", default-features = false, optional = true } base64 = "0.13" bitflags = "1.2" @@ -55,9 +55,9 @@ cookie = { version = "0.14.1", features = ["percent-encode"] } derive_more = "0.99.5" either = "1.5.3" encoding_rs = "0.8" -futures-channel = { version = "0.3.7", default-features = false } -futures-core = { version = "0.3.7", default-features = false } -futures-util = { version = "0.3.7", default-features = false, features = ["sink"] } +futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } +futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } ahash = "0.6" h2 = "0.3.0" http = "0.2.2" @@ -85,7 +85,7 @@ brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } [dev-dependencies] -actix-server = "2.0.0-beta.2" +actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" diff --git a/actix-http/src/client/config.rs b/actix-http/src/client/config.rs index 369732964..fad902d04 100644 --- a/actix-http/src/client/config.rs +++ b/actix-http/src/client/config.rs @@ -18,7 +18,7 @@ pub(crate) struct ConnectorConfig { impl Default for ConnectorConfig { fn default() -> Self { Self { - timeout: Duration::from_secs(1), + timeout: Duration::from_secs(5), conn_lifetime: Duration::from_secs(75), conn_keep_alive: Duration::from_secs(15), disconnect_timeout: Some(Duration::from_millis(3000)), diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index d22f2c7ac..26d392120 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -5,7 +5,8 @@ use std::{fmt, io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use bytes::Bytes; -use futures_util::future::{err, Either, FutureExt, LocalBoxFuture, Ready}; +use futures_core::future::LocalBoxFuture; +use futures_util::future::{err, Either, FutureExt, Ready}; use h2::client::SendRequest; use pin_project::pin_project; diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 326a2fc60..425ee0f70 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -100,9 +100,9 @@ impl Connector<(), ()> { fn build_ssl(protocols: Vec>) -> SslConnector { let mut config = ClientConfig::new(); config.set_protocols(&protocols); - config - .root_store - .add_server_trust_anchors(&actix_tls::accept::rustls::TLS_SERVER_ROOTS); + config.root_store.add_server_trust_anchors( + &actix_tls::connect::ssl::rustls::TLS_SERVER_ROOTS, + ); SslConnector::Rustls(Arc::new(config)) } @@ -392,11 +392,11 @@ mod connect_impl { Ready, ConnectError>>, >; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { + fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => { Either::Right(err(ConnectError::SslIsNotSupported)) @@ -460,11 +460,11 @@ mod connect_impl { InnerConnectorResponseB, >; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { + fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { fut: self.ssl_pool.call(req), diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index a5f1b2e8e..8d609f546 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -1,6 +1,5 @@ use std::io; -use actix_tls::connect::resolver::ResolveError; use derive_more::{Display, From}; #[cfg(feature = "openssl")] @@ -23,7 +22,7 @@ pub enum ConnectError { /// Failed to resolve the hostname #[display(fmt = "Failed resolving hostname: {}", _0)] - Resolver(ResolveError), + Resolver(Box), /// No dns records #[display(fmt = "No dns records found for the input")] diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index af862cdd7..1eebef53b 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -13,7 +13,8 @@ use actix_utils::task::LocalWaker; use ahash::AHashMap; use bytes::Bytes; use futures_channel::oneshot; -use futures_util::future::{poll_fn, FutureExt, LocalBoxFuture}; +use futures_core::future::LocalBoxFuture; +use futures_util::future::{poll_fn, FutureExt}; use h2::client::{Connection, SendRequest}; use http::uri::Authority; use indexmap::IndexSet; @@ -45,7 +46,7 @@ impl From for Key { } /// Connections pool -pub(crate) struct ConnectionPool(Rc>, Rc>>); +pub(crate) struct ConnectionPool(Rc, Rc>>); impl ConnectionPool where @@ -53,7 +54,7 @@ where T: Service + 'static, { pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { - let connector_rc = Rc::new(RefCell::new(connector)); + let connector_rc = Rc::new(connector); let inner_rc = Rc::new(RefCell::new(Inner { config, acquired: 0, @@ -98,12 +99,12 @@ where type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.0.poll_ready(cx) } - fn call(&mut self, req: Connect) -> Self::Future { - let mut connector = self.0.clone(); + fn call(&self, req: Connect) -> Self::Future { + let connector = self.0.clone(); let inner = self.1.clone(); let fut = async move { @@ -325,7 +326,7 @@ where { if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = conn.io { - actix_rt::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)); } } } else { @@ -340,7 +341,7 @@ where if let ConnectionType::H1(io) = io { actix_rt::spawn(CloseConnection::new( io, timeout, - )) + )); } } continue; @@ -372,7 +373,7 @@ where self.acquired -= 1; if let Some(timeout) = self.config.disconnect_timeout { if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new(io, timeout)) + actix_rt::spawn(CloseConnection::new(io, timeout)); } } self.check_availability(); @@ -428,7 +429,7 @@ struct ConnectorPoolSupport where Io: AsyncRead + AsyncWrite + Unpin + 'static, { - connector: T, + connector: Rc, inner: Rc>>, } @@ -535,7 +536,7 @@ where rx: Some(rx), inner: Some(inner), config, - }) + }); } } diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index eb5f48105..90a5f0e06 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -1,5 +1,4 @@ use std::{ - cell::RefCell, collections::VecDeque, fmt, future::Future, @@ -91,7 +90,7 @@ where U: Service<(Request, Framed), Response = ()>, U::Error: fmt::Display, { - flow: Rc>>, + flow: Rc>, on_connect_data: OnConnectData, flags: Flags, peer_addr: Option, @@ -177,7 +176,7 @@ where pub(crate) fn new( stream: T, config: ServiceConfig, - services: Rc>>, + services: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { @@ -200,7 +199,7 @@ where config: ServiceConfig, read_buf: BytesMut, timeout: Option, - services: Rc>>, + services: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { @@ -377,7 +376,7 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); this = self.as_mut().project(); - let fut = this.flow.borrow_mut().service.call(req); + let fut = this.flow.service.call(req); this.state.set(State::ServiceCall(fut)); continue; } @@ -467,12 +466,12 @@ where if req.head().expect() { // set dispatcher state so the future is pinned. let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().expect.call(req); + let task = this.flow.expect.call(req); this.state.set(State::ExpectCall(task)); } else { // the same as above. let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().service.call(req); + let task = this.flow.service.call(req); this.state.set(State::ServiceCall(task)); }; @@ -485,7 +484,7 @@ where Poll::Ready(Ok(req)) => { self.as_mut().send_continue(); let mut this = self.as_mut().project(); - let task = this.flow.borrow_mut().service.call(req); + let task = this.flow.service.call(req); this.state.set(State::ServiceCall(task)); continue; } @@ -556,9 +555,7 @@ where // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - if pl == MessageType::Stream - && this.flow.borrow().upgrade.is_some() - { + if pl == MessageType::Stream && this.flow.upgrade.is_some() { this.messages.push_back(DispatcherMessage::Upgrade(req)); break; } @@ -811,9 +808,8 @@ where let framed = Framed::from_parts(parts); let upgrade = inner_p .flow - .borrow_mut() .upgrade - .take() + .as_ref() .unwrap() .call((req, framed)); self.as_mut() diff --git a/actix-http/src/h1/expect.rs b/actix-http/src/h1/expect.rs index c3e4ccdaa..65856edf6 100644 --- a/actix-http/src/h1/expect.rs +++ b/actix-http/src/h1/expect.rs @@ -1,4 +1,4 @@ -use std::task::{Context, Poll}; +use std::task::Poll; use actix_service::{Service, ServiceFactory}; use futures_util::future::{ready, Ready}; @@ -26,11 +26,9 @@ impl Service for ExpectHandler { type Error = Error; type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { ready(Ok(req)) // TODO: add some way to trigger error // Err(error::ErrorExpectationFailed("test")) diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index aed700eed..b79453ebd 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -367,7 +366,7 @@ where X: Service, U: Service<(Request, Framed)>, { - flow: Rc>>, + flow: Rc>, on_connect_ext: Option>>, cfg: ServiceConfig, _phantom: PhantomData, @@ -417,9 +416,9 @@ where type Error = DispatchError; type Future = Dispatcher; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut flow = self.flow.borrow_mut(); - let ready = flow + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let ready = self + .flow .expect .poll_ready(cx) .map_err(|e| { @@ -429,7 +428,8 @@ where })? .is_ready(); - let ready = flow + let ready = self + .flow .service .poll_ready(cx) .map_err(|e| { @@ -440,7 +440,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = flow.upgrade { + let ready = if let Some(ref upg) = self.flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -460,7 +460,7 @@ where } } - fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { + fn call(&self, (io, addr): (T, Option)) -> Self::Future { let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); diff --git a/actix-http/src/h1/upgrade.rs b/actix-http/src/h1/upgrade.rs index 007aff1bf..5e24d84e3 100644 --- a/actix-http/src/h1/upgrade.rs +++ b/actix-http/src/h1/upgrade.rs @@ -1,4 +1,4 @@ -use std::task::{Context, Poll}; +use std::task::Poll; use actix_codec::Framed; use actix_service::{Service, ServiceFactory}; @@ -28,11 +28,9 @@ impl Service<(Request, Framed)> for UpgradeHandler { type Error = Error; type Future = Ready>; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } + actix_service::always_ready!(); - fn call(&mut self, _: (Request, Framed)) -> Self::Future { + fn call(&self, _: (Request, Framed)) -> Self::Future { ready(Ok(())) } } diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 959c34f13..5ccd2a9d1 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::net; @@ -37,7 +36,7 @@ where S: Service, B: MessageBody, { - flow: Rc>>, + flow: Rc>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -56,7 +55,7 @@ where B: MessageBody, { pub(crate) fn new( - services: Rc>>, + flow: Rc>, connection: Connection, on_connect_data: OnConnectData, config: ServiceConfig, @@ -80,7 +79,7 @@ where }; Dispatcher { - flow: services, + flow, config, peer_addr, connection, @@ -138,7 +137,7 @@ where let svc = ServiceResponse:: { state: ServiceResponseState::ServiceCall( - this.flow.borrow_mut().service.call(req), + this.flow.service.call(req), Some(res), ), config: this.config.clone(), diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 36f7dc311..36c76b17c 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; @@ -249,7 +248,7 @@ pub struct H2ServiceHandler where S: Service, { - flow: Rc>>, + flow: Rc>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -290,15 +289,15 @@ where type Error = DispatchError; type Future = H2ServiceHandlerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - self.flow.borrow_mut().service.poll_ready(cx).map_err(|e| { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + self.flow.service.poll_ready(cx).map_err(|e| { let e = e.into(); error!("Service readiness error: {:?}", e); DispatchError::Service(e) }) } - fn call(&mut self, (io, addr): (T, Option)) -> Self::Future { + fn call(&self, (io, addr): (T, Option)) -> Self::Future { let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); @@ -321,7 +320,7 @@ where { Incoming(Dispatcher), Handshake( - Option>>>, + Option>>, Option, Option, OnConnectData, diff --git a/actix-http/src/header/into_value.rs b/actix-http/src/header/into_value.rs index 4b1e6cbbd..4ba58e726 100644 --- a/actix-http/src/header/into_value.rs +++ b/actix-http/src/header/into_value.rs @@ -126,6 +126,6 @@ impl IntoHeaderValue for Mime { #[inline] fn try_into_value(self) -> Result { - HeaderValue::try_from(format!("{}", self)) + HeaderValue::from_str(self.as_ref()) } } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 357ac4c53..f0121db97 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; @@ -440,7 +439,7 @@ where X: Service, U: Service<(Request, Framed)>, { - flow: Rc>>, + flow: Rc>, cfg: ServiceConfig, on_connect_ext: Option>>, _phantom: PhantomData, @@ -454,12 +453,12 @@ pub(super) struct HttpFlow { } impl HttpFlow { - pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc> { - Rc::new(RefCell::new(Self { + pub(super) fn new(service: S, expect: X, upgrade: Option) -> Rc { + Rc::new(Self { service, expect, upgrade, - })) + }) } } @@ -509,9 +508,9 @@ where type Error = DispatchError; type Future = HttpServiceHandlerResponse; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - let mut flow = self.flow.borrow_mut(); - let ready = flow + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { + let ready = self + .flow .expect .poll_ready(cx) .map_err(|e| { @@ -521,7 +520,8 @@ where })? .is_ready(); - let ready = flow + let ready = self + .flow .service .poll_ready(cx) .map_err(|e| { @@ -532,7 +532,7 @@ where .is_ready() && ready; - let ready = if let Some(ref mut upg) = flow.upgrade { + let ready = if let Some(ref upg) = self.flow.upgrade { upg.poll_ready(cx) .map_err(|e| { let e = e.into(); @@ -553,7 +553,7 @@ where } fn call( - &mut self, + &self, (io, proto, peer_addr): (T, Protocol, Option), ) -> Self::Future { let on_connect_data = @@ -604,7 +604,7 @@ where Option<( Handshake, ServiceConfig, - Rc>>, + Rc>, OnConnectData, Option, )>, diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index 976fc9164..7ed9b0df1 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -21,7 +21,7 @@ impl WsService { WsService(Arc::new(Mutex::new((PhantomData, Cell::new(false))))) } - fn set_polled(&mut self) { + fn set_polled(&self) { *self.0.lock().unwrap().1.get_mut() = true; } @@ -44,15 +44,12 @@ where type Error = Error; type Future = Pin>>>; - fn poll_ready(&mut self, _ctx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _ctx: &mut Context<'_>) -> Poll> { self.set_polled(); Poll::Ready(Ok(())) } - fn call( - &mut self, - (req, mut framed): (Request, Framed), - ) -> Self::Future { + fn call(&self, (req, mut framed): (Request, Framed)) -> Self::Future { let fut = async move { let res = ws::handshake(req.head()).unwrap().message_body(()); diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 60620bce7..beecd2e9d 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -17,7 +17,7 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } -actix-utils = "3.0.0-beta.1" +actix-utils = "3.0.0-beta.2" bytes = "1" derive_more = "0.99.5" @@ -28,5 +28,5 @@ mime = "0.3" twoway = "0.2" [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" actix-http = "3.0.0-beta.1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 34bf713ae..c6fd1c233 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = "0.11.0-beta.1" +actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.1" actix-web = { version = "4.0.0-beta.1", default-features = false } @@ -28,6 +28,6 @@ pin-project = "1.0.0" tokio = { version = "1", features = ["sync"] } [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" env_logger = "0.7" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index 2dd93c727..afe17cd21 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -233,14 +233,13 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = - init_service(App::new().service(web::resource("/test").to(|| { - HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) - }))) - .await; + let srv = init_service(App::new().service(web::resource("/test").to(|| { + HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 70ab55681..04bd10421 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -19,7 +19,7 @@ syn = { version = "1", features = ["full", "parsing"] } proc-macro2 = "1" [dev-dependencies] -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" actix-web = "4.0.0-beta.1" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 50e5be712..ede1e0005 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -175,7 +175,6 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { let vis = &input.vis; let sig = &mut input.sig; let body = &input.block; - let name = &sig.ident; if sig.asyncness.is_none() { return syn::Error::new_spanned(sig.fn_token, "only async fn is supported") @@ -188,7 +187,7 @@ pub fn main(_: TokenStream, item: TokenStream) -> TokenStream { (quote! { #(#attrs)* #vis #sig { - actix_web::rt::System::new(stringify!(#name)) + actix_web::rt::System::new() .block_on(async move { #body }) } }) diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 389d09c82..252be1b95 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -1,5 +1,4 @@ use std::future::Future; -use std::pin::Pin; use std::task::{Context, Poll}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; @@ -8,7 +7,7 @@ use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; use actix_web_codegen::{ connect, delete, get, head, options, patch, post, put, route, trace, }; -use futures_util::future; +use futures_util::future::{self, LocalBoxFuture}; // Make sure that we can name function as 'config' #[get("/config")] @@ -117,14 +116,13 @@ where { type Response = ServiceResponse; type Error = Error; - #[allow(clippy::type_complexity)] - type Future = Pin>>>; + type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let fut = self.service.call(req); Box::pin(async move { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c5c1185be..16ec7ad1a 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -5,6 +5,9 @@ * `ClientRequest::insert_header` method which allows using typed headers. [#1869] * `ClientRequest::append_header` method which allows using typed headers. [#1869] +### Changed +* Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] + ### Removed * `ClientRequest::set`; use `ClientRequest::insert_header`. [#1869] * `ClientRequest::set_header`; use `ClientRequest::insert_header`. [#1869] @@ -12,6 +15,7 @@ * `ClientRequest::header`; use `ClientRequest::append_header`. [#1869] [#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 675110e5a..24275b6f5 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -38,9 +38,9 @@ compress = ["actix-http/compress"] [dependencies] actix-codec = "0.4.0-beta.1" -actix-service = "2.0.0-beta.3" +actix-service = "2.0.0-beta.4" actix-http = "3.0.0-beta.1" -actix-rt = "=2.0.0-beta.2" +actix-rt = "2" base64 = "0.13" bytes = "1" @@ -58,15 +58,12 @@ open-ssl = { version = "0.10", package = "openssl", optional = true } rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } [dev-dependencies] -# TODO: actix is temporary added as dev dep for actix-macro reason. -# Can be removed when it does not impact tests. -actix = "0.11.0-beta.1" actix-web = { version = "4.0.0-beta.1", features = ["openssl"] } actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-utils = "3.0.0-beta.1" -actix-server = "2.0.0-beta.2" -actix-tls = { version = "3.0.0-beta.2", features = ["openssl", "rustls"] } +actix-server = "2.0.0-beta.3" +actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" diff --git a/awc/README.md b/awc/README.md index 972a80140..3d18a07c5 100644 --- a/awc/README.md +++ b/awc/README.md @@ -21,7 +21,7 @@ use actix_rt::System; use awc::Client; fn main() { - System::new("test").block_on(async { + System::new().block_on(async { let client = Client::default(); let res = client diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 351a493af..39eb24c39 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -1,4 +1,3 @@ -use std::cell::RefCell; use std::convert::TryFrom; use std::fmt; use std::rc::Rc; @@ -24,7 +23,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option>>, + connector: Option>, } impl Default for ClientBuilder { @@ -56,7 +55,7 @@ impl ClientBuilder { ::Future: 'static, T::Future: 'static, { - self.connector = Some(RefCell::new(Box::new(ConnectorWrapper(connector)))); + self.connector = Some(Box::new(ConnectorWrapper(connector))); self } @@ -182,9 +181,7 @@ impl ClientBuilder { if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; - RefCell::new( - Box::new(ConnectorWrapper(connector.finish())) as Box - ) + Box::new(ConnectorWrapper(connector.finish())) as _ }; let config = ClientConfig { headers: self.headers, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 8ee239f76..2ffb8ec37 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -20,14 +20,14 @@ pub(crate) struct ConnectorWrapper(pub T); pub(crate) trait Connect { fn send_request( - &mut self, + &self, head: RequestHead, body: Body, addr: Option, ) -> Pin>>>; fn send_request_extra( - &mut self, + &self, head: Rc, extra_headers: Option, body: Body, @@ -36,7 +36,7 @@ pub(crate) trait Connect { /// Send request, returns Response and Framed fn open_tunnel( - &mut self, + &self, head: RequestHead, addr: Option, ) -> Pin< @@ -52,7 +52,7 @@ pub(crate) trait Connect { /// Send request and extra headers, returns Response and Framed fn open_tunnel_extra( - &mut self, + &self, head: Rc, extra_headers: Option, addr: Option, @@ -78,7 +78,7 @@ where T::Future: 'static, { fn send_request( - &mut self, + &self, head: RequestHead, body: Body, addr: Option, @@ -101,7 +101,7 @@ where } fn send_request_extra( - &mut self, + &self, head: Rc, extra_headers: Option, body: Body, @@ -126,7 +126,7 @@ where } fn open_tunnel( - &mut self, + &self, head: RequestHead, addr: Option, ) -> Pin< @@ -158,7 +158,7 @@ where } fn open_tunnel_extra( - &mut self, + &self, head: Rc, extra_headers: Option, addr: Option, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index f750a3df2..a92125b5a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -93,7 +93,6 @@ #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -use std::cell::RefCell; use std::convert::TryFrom; use std::rc::Rc; use std::time::Duration; @@ -145,7 +144,7 @@ use self::connect::{Connect, ConnectorWrapper}; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: RefCell>, + pub(crate) connector: Box, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -153,9 +152,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: RefCell::new(Box::new(ConnectorWrapper( - Connector::new().finish(), - ))), + connector: Box::new(ConnectorWrapper(Connector::new().finish())), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 9fb821a0e..5f790a038 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -183,15 +183,13 @@ impl RequestSender { where B: Into, { - let mut connector = config.connector.borrow_mut(); - let fut = match self { RequestSender::Owned(head) => { - connector.send_request(head, body.into(), addr) - } - RequestSender::Rc(head, extra_headers) => { - connector.send_request_extra(head, extra_headers, body.into(), addr) + config.connector.send_request(head, body.into(), addr) } + RequestSender::Rc(head, extra_headers) => config + .connector + .send_request_extra(head, extra_headers, body.into(), addr), }; SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 17086cf2a..7c795226b 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -325,11 +325,7 @@ impl WebsocketsRequest { let max_size = self.max_size; let server_mode = self.server_mode; - let fut = self - .config - .connector - .borrow_mut() - .open_tunnel(head, self.addr); + let fut = self.config.connector.open_tunnel(head, self.addr); // set request timeout let (head, framed) = if let Some(to) = self.config.timeout { diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 8ca94ec91..a9f5dc370 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -120,9 +120,7 @@ async fn test_timeout() { }); let connector = awc::Connector::new() - .connector(actix_tls::connect::new_connector( - actix_tls::connect::start_default_resolver().await.unwrap(), - )) + .connector(actix_tls::connect::default_connector()) .timeout(Duration::from_secs(15)) .finish(); diff --git a/benches/responder.rs b/benches/responder.rs index 61180d575..8cfdbd3ea 100644 --- a/benches/responder.rs +++ b/benches/responder.rs @@ -68,7 +68,7 @@ impl Responder for OptionResponder { } fn future_responder(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let req = TestRequest::default().to_http_request(); c.bench_function("future_responder", move |b| { @@ -91,7 +91,7 @@ fn future_responder(c: &mut Criterion) { } fn responder(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let req = TestRequest::default().to_http_request(); c.bench_function("responder", move |b| { b.iter_custom(|_| { diff --git a/benches/server.rs b/benches/server.rs index 117b6136e..ce79f077d 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -29,7 +29,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ fn bench_async_burst(c: &mut Criterion) { // We are using System here, since Runtime requires preinitialized tokio // Maybe add to actix_rt docs - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = rt.block_on(async { test::start(|| { diff --git a/benches/service.rs b/benches/service.rs index 8ca6cbe28..0d3264857 100644 --- a/benches/service.rs +++ b/benches/service.rs @@ -25,7 +25,7 @@ pub fn bench_async_service(c: &mut Criterion, srv: S, name: &str) where S: Service + 'static, { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = Rc::new(RefCell::new(srv)); let req = TestRequest::default().to_srv_request(); @@ -67,7 +67,7 @@ async fn index(req: ServiceRequest) -> Result { // Sample results on MacBook Pro '14 // time: [2.0724 us 2.1345 us 2.2074 us] fn async_web_service(c: &mut Criterion) { - let rt = actix_rt::System::new("test"); + let rt = actix_rt::System::new(); let srv = Rc::new(RefCell::new(rt.block_on(init_service( App::new().service(web::service("/").finish(index)), )))); diff --git a/src/app.rs b/src/app.rs index 9c5b806ea..1660c4b94 100644 --- a/src/app.rs +++ b/src/app.rs @@ -422,7 +422,7 @@ where > where B1: MessageBody, - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future, Error>>, { App { @@ -480,7 +480,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new().service(web::resource("/test").to(HttpResponse::Ok)), ) .await; @@ -492,7 +492,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let mut srv = init_service( + let srv = init_service( App::new() .service(web::resource("/test").to(HttpResponse::Ok)) .service( @@ -525,7 +525,7 @@ mod tests { #[actix_rt::test] async fn test_data_factory() { - let mut srv = + let srv = init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -534,7 +534,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -557,7 +557,7 @@ mod tests { #[actix_rt::test] async fn test_extension() { - let mut srv = init_service(App::new().app_data(10usize).service( + let srv = init_service(App::new().app_data(10usize).service( web::resource("/").to(|req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 10); HttpResponse::Ok() @@ -571,7 +571,7 @@ mod tests { #[actix_rt::test] async fn test_wrap() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap( DefaultHeaders::new() @@ -581,7 +581,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -591,7 +591,7 @@ mod tests { #[actix_rt::test] async fn test_router_wrap() { - let mut srv = init_service( + let srv = init_service( App::new() .route("/test", web::get().to(HttpResponse::Ok)) .wrap( @@ -601,7 +601,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -611,7 +611,7 @@ mod tests { #[actix_rt::test] async fn test_wrap_fn() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { let fut = srv.call(req); @@ -628,7 +628,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -638,7 +638,7 @@ mod tests { #[actix_rt::test] async fn test_router_wrap_fn() { - let mut srv = init_service( + let srv = init_service( App::new() .route("/test", web::get().to(HttpResponse::Ok)) .wrap_fn(|req, srv| { @@ -655,7 +655,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -665,7 +665,7 @@ mod tests { #[actix_rt::test] async fn test_external_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .external_resource("youtube", "https://youtube.com/watch/{video_id}") .route( @@ -679,7 +679,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); diff --git a/src/app_service.rs b/src/app_service.rs index 8a00f59eb..a38f1d652 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -196,7 +196,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: Request) -> Self::Future { + fn call(&self, req: Request) -> Self::Future { let (head, payload) = req.into_parts(); let req = if let Some(mut req) = self.app_state.pool().pop() { @@ -287,8 +287,8 @@ impl Service for AppRouting { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let res = self.router.recognize_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { @@ -354,7 +354,7 @@ mod tests { let data = Arc::new(AtomicBool::new(false)); { - let mut app = init_service( + let app = init_service( App::new() .data(DropData(data.clone())) .service(web::resource("/test").to(HttpResponse::Ok)), diff --git a/src/config.rs b/src/config.rs index 8e22dc90d..24afca295 100644 --- a/src/config.rs +++ b/src/config.rs @@ -263,7 +263,7 @@ mod tests { cfg.app_data(15u8); }; - let mut srv = init_service(App::new().configure(cfg).service( + let srv = init_service(App::new().configure(cfg).service( web::resource("/").to(|_: web::Data, req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 15u8); HttpResponse::Ok() @@ -286,7 +286,7 @@ mod tests { // }); // }; - // let mut srv = + // let srv = // init_service(App::new().configure(cfg).service( // web::resource("/").to(|_: web::Data| HttpResponse::Ok()), // )); @@ -297,7 +297,7 @@ mod tests { // let cfg2 = |cfg: &mut ServiceConfig| { // cfg.data_factory(|| Ok::<_, ()>(10u32)); // }; - // let mut srv = init_service( + // let srv = init_service( // App::new() // .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())) // .configure(cfg2), @@ -309,7 +309,7 @@ mod tests { #[actix_rt::test] async fn test_external_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .configure(|cfg| { cfg.external_resource( @@ -328,7 +328,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345")); @@ -336,7 +336,7 @@ mod tests { #[actix_rt::test] async fn test_service() { - let mut srv = init_service(App::new().configure(|cfg| { + let srv = init_service(App::new().configure(|cfg| { cfg.service( web::resource("/test").route(web::get().to(HttpResponse::Created)), ) @@ -347,13 +347,13 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/index.html") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/data.rs b/src/data.rs index dd0fbed0e..12a1f5cf8 100644 --- a/src/data.rs +++ b/src/data.rs @@ -144,7 +144,7 @@ mod tests { #[actix_rt::test] async fn test_data_extractor() { - let mut srv = init_service(App::new().data("TEST".to_string()).service( + let srv = init_service(App::new().data("TEST".to_string()).service( web::resource("/").to(|data: web::Data| { assert_eq!(data.to_lowercase(), "test"); HttpResponse::Ok() @@ -156,7 +156,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().data(10u32).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -165,7 +165,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); - let mut srv = init_service( + let srv = init_service( App::new() .data(10u32) .data(13u32) @@ -186,7 +186,7 @@ mod tests { #[actix_rt::test] async fn test_app_data_extractor() { - let mut srv = + let srv = init_service(App::new().app_data(Data::new(10usize)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -196,7 +196,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let mut srv = + let srv = init_service(App::new().app_data(Data::new(10u32)).service( web::resource("/").to(|_: web::Data| HttpResponse::Ok()), )) @@ -208,7 +208,7 @@ mod tests { #[actix_rt::test] async fn test_route_data_extractor() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/") .data(10usize) @@ -222,7 +222,7 @@ mod tests { assert_eq!(resp.status(), StatusCode::OK); // different type - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/") .data(10u32) @@ -237,7 +237,7 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::resource("/").data(10usize).route(web::get().to( |data: web::Data| { assert_eq!(**data, 10); diff --git a/src/handler.rs b/src/handler.rs index 1526f050d..0016b741e 100644 --- a/src/handler.rs +++ b/src/handler.rs @@ -113,11 +113,11 @@ where type Error = Error; type Future = HandlerServiceFuture; - fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let (req, mut payload) = req.into_parts(); let fut = T::from_request(&req, &mut payload); HandlerServiceFuture::Extract(fut, Some(req), self.hnd.clone()) diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index d5661fc77..2df535280 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -80,11 +80,11 @@ where type Error = Error; type Future = CompatMiddlewareFuture; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx).map_err(From::from) } - fn call(&mut self, req: Req) -> Self::Future { + fn call(&self, req: Req) -> Self::Future { let fut = self.service.call(req); CompatMiddlewareFuture { fut } } @@ -138,7 +138,7 @@ mod tests { let logger = Logger::default(); let compress = Compress::default(); - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap(Compat::new(logger)) @@ -151,7 +151,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -160,7 +160,7 @@ mod tests { let logger = Logger::default(); let compress = Compress::default(); - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("app/test") .wrap(Compat::new(logger)) @@ -171,7 +171,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -185,11 +185,11 @@ mod tests { let logger = Logger::default(); - let mut mw = Condition::new(true, Compat::new(logger)) + let mw = Condition::new(true, Compat::new(logger)) .new_transform(srv.into_service()) .await .unwrap(); - let resp = call_service(&mut mw, TestRequest::default().to_srv_request()).await; + let resp = call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); } } diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 1f05b7ae3..7a45e4c8d 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -90,7 +90,7 @@ where actix_service::forward_ready!(service); #[allow(clippy::borrow_interior_mutable_const)] - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { // negotiate content-encoding let encoding = if let Some(val) = req.headers().get(&ACCEPT_ENCODING) { if let Ok(enc) = val.to_str() { diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index d61e7d576..85eba3fb8 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -76,14 +76,14 @@ where type Error = E::Error; type Future = Either; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { match self { ConditionMiddleware::Enable(service) => service.poll_ready(cx), ConditionMiddleware::Disable(service) => service.poll_ready(cx), } } - fn call(&mut self, req: Req) -> Self::Future { + 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)), @@ -123,12 +123,12 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(true, mw) + let mw = Condition::new(true, mw) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -141,13 +141,13 @@ mod tests { let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); - let mut mw = Condition::new(false, mw) + let mw = Condition::new(false, mw) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index 5d55fd830..a9a50ec2c 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -143,7 +143,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let inner = self.inner.clone(); let fut = self.service.call(req); @@ -200,7 +200,7 @@ mod tests { #[actix_rt::test] async fn test_default_headers() { - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") .new_transform(ok_service()) .await @@ -218,7 +218,7 @@ mod tests { .finish(), )) }; - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .header(CONTENT_TYPE, "0001") .new_transform(srv.into_service()) .await @@ -231,7 +231,7 @@ mod tests { async fn test_content_type() { let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); - let mut mw = DefaultHeaders::new() + let mw = DefaultHeaders::new() .add_content_type() .new_transform(srv.into_service()) .await diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 44962aa98..70933241d 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -123,7 +123,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { let handlers = self.handlers.clone(); let fut = self.service.call(req); ErrorHandlersFuture::ServiceFuture { fut, handlers } @@ -196,14 +196,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mut mw = ErrorHandlers::new() + let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -223,14 +223,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mut mw = ErrorHandlers::new() + let mw = ErrorHandlers::new() .handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async) .new_transform(srv.into_service()) .await .unwrap(); let resp = - test::call_service(&mut mw, TestRequest::default().to_srv_request()).await; + test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index 66bd7e464..969aa9be5 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -219,7 +219,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { if self.inner.exclude.contains(req.path()) || self.inner.exclude_regex.is_match(req.path()) { @@ -609,7 +609,7 @@ mod tests { }; let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test"); - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + let srv = logger.new_transform(srv.into_service()).await.unwrap(); let req = TestRequest::default() .insert_header(( @@ -632,7 +632,7 @@ mod tests { let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test") .exclude_regex("\\w"); - let mut srv = logger.new_transform(srv.into_service()).await.unwrap(); + let srv = logger.new_transform(srv.into_service()).await.unwrap(); let req = TestRequest::default() .insert_header(( @@ -806,7 +806,7 @@ mod tests { captured.to_owned() }); - let mut srv = logger.new_transform(test::ok_service()).await.unwrap(); + let srv = logger.new_transform(test::ok_service()).await.unwrap(); let req = TestRequest::default().to_srv_request(); srv.call(req).await.unwrap(); diff --git a/src/middleware/normalize.rs b/src/middleware/normalize.rs index 89cab9073..8519f041a 100644 --- a/src/middleware/normalize.rs +++ b/src/middleware/normalize.rs @@ -57,7 +57,7 @@ impl Default for TrailingSlash { /// ```rust /// use actix_web::{web, middleware, App}; /// -/// # actix_web::rt::System::new("doctest").block_on(async { +/// # actix_web::rt::System::new().block_on(async { /// let app = App::new() /// .wrap(middleware::NormalizePath::default()) /// .route("/test", web::get().to(|| async { "test" })) @@ -66,22 +66,22 @@ impl Default for TrailingSlash { /// use actix_web::http::StatusCode; /// use actix_web::test::{call_service, init_service, TestRequest}; /// -/// let mut app = init_service(app).await; +/// let app = init_service(app).await; /// /// let req = TestRequest::with_uri("/test").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::OK); /// /// let req = TestRequest::with_uri("/test/").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::OK); /// /// let req = TestRequest::with_uri("/unmatchable").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// /// let req = TestRequest::with_uri("/unmatchable/").to_request(); -/// let res = call_service(&mut app, req).await; +/// let res = call_service(&app, req).await; /// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// # }) /// ``` @@ -132,7 +132,7 @@ where actix_service::forward_ready!(service); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { + fn call(&self, mut req: ServiceRequest) -> Self::Future { let head = req.head_mut(); let original_path = head.uri.path(); @@ -195,7 +195,7 @@ mod tests { #[actix_rt::test] async fn test_wrap() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath::default()) .service(web::resource("/").to(HttpResponse::Ok)) @@ -204,37 +204,37 @@ mod tests { .await; let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/v1//something////").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req2 = TestRequest::with_uri("//v1/something").to_request(); - let res2 = call_service(&mut app, req2).await; + let res2 = call_service(&app, req2).await; assert!(res2.status().is_success()); let req3 = TestRequest::with_uri("//v1//////something").to_request(); - let res3 = call_service(&mut app, req3).await; + let res3 = call_service(&app, req3).await; assert!(res3.status().is_success()); let req4 = TestRequest::with_uri("/v1//something").to_request(); - let res4 = call_service(&mut app, req4).await; + let res4 = call_service(&app, req4).await; assert!(res4.status().is_success()); } #[actix_rt::test] async fn trim_trailing_slashes() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::Trim)) .service(web::resource("/").to(HttpResponse::Ok)) @@ -244,37 +244,37 @@ mod tests { // root paths should still work let req = TestRequest::with_uri("/").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/?query=test").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("///").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req = TestRequest::with_uri("/v1/something////").to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert!(res.status().is_success()); let req2 = TestRequest::with_uri("/v1/something/").to_request(); - let res2 = call_service(&mut app, req2).await; + let res2 = call_service(&app, req2).await; assert!(res2.status().is_success()); let req3 = TestRequest::with_uri("//v1//something//").to_request(); - let res3 = call_service(&mut app, req3).await; + let res3 = call_service(&app, req3).await; assert!(res3.status().is_success()); let req4 = TestRequest::with_uri("//v1//something").to_request(); - let res4 = call_service(&mut app, req4).await; + let res4 = call_service(&app, req4).await; assert!(res4.status().is_success()); } #[actix_rt::test] async fn keep_trailing_slash_unchanged() { - let mut app = init_service( + let app = init_service( App::new() .wrap(NormalizePath(TrailingSlash::MergeOnly)) .service(web::resource("/").to(HttpResponse::Ok)) @@ -299,7 +299,7 @@ mod tests { for (path, success) in tests { let req = TestRequest::with_uri(path).to_request(); - let res = call_service(&mut app, req).await; + let res = call_service(&app, req).await; assert_eq!(res.status().is_success(), success); } } @@ -311,7 +311,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); @@ -342,7 +342,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); @@ -359,7 +359,7 @@ mod tests { ready(Ok(req.into_response(HttpResponse::Ok().finish()))) }; - let mut normalize = NormalizePath::default() + let normalize = NormalizePath::default() .new_transform(srv.into_service()) .await .unwrap(); diff --git a/src/request.rs b/src/request.rs index fe60cb169..a563518e0 100644 --- a/src/request.rs +++ b/src/request.rs @@ -556,7 +556,7 @@ mod tests { #[actix_rt::test] async fn test_drop_http_request_pool() { - let mut srv = init_service(App::new().service(web::resource("/").to( + let srv = init_service(App::new().service(web::resource("/").to( |req: HttpRequest| { HttpResponse::Ok() .insert_header(("pool_cap", req.app_state().pool().cap)) @@ -566,7 +566,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; drop(srv); @@ -575,7 +575,7 @@ mod tests { #[actix_rt::test] async fn test_data() { - let mut srv = init_service(App::new().app_data(10usize).service( + let srv = init_service(App::new().app_data(10usize).service( web::resource("/").to(|req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() @@ -587,10 +587,10 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); - let mut srv = init_service(App::new().app_data(10u32).service( + let srv = init_service(App::new().app_data(10u32).service( web::resource("/").to(|req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() @@ -602,7 +602,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -614,7 +614,7 @@ mod tests { HttpResponse::Ok().body(num.to_string()) } - let mut srv = init_service( + let srv = init_service( App::new() .app_data(88usize) .service(web::resource("/").route(web::get().to(echo_usize))) @@ -645,7 +645,7 @@ mod tests { HttpResponse::Ok().body(num.to_string()) } - let mut srv = init_service( + let srv = init_service( App::new() .app_data(88usize) .service(web::resource("/").route(web::get().to(echo_usize))) @@ -685,7 +685,7 @@ mod tests { let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); { let tracker2 = Rc::clone(&tracker); - let mut srv = init_service(App::new().data(10u32).service( + let srv = init_service(App::new().data(10u32).service( web::resource("/").to(move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), @@ -696,7 +696,7 @@ mod tests { .await; let req = TestRequest::default().to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } @@ -705,7 +705,7 @@ mod tests { #[actix_rt::test] async fn extract_path_pattern() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/user/{id}") .service(web::resource("/profile").route(web::get().to( @@ -727,17 +727,17 @@ mod tests { .await; let req = TestRequest::get().uri("/user/22/profile").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/user/22/not-exist").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } #[actix_rt::test] async fn extract_path_pattern_complex() { - let mut srv = init_service( + let srv = init_service( App::new() .service(web::scope("/user").service(web::scope("/{id}").service( web::resource("").to(move |req: HttpRequest| { @@ -759,15 +759,15 @@ mod tests { .await; let req = TestRequest::get().uri("/user/test").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); let req = TestRequest::get().uri("/not-exist").to_request(); - let res = call_service(&mut srv, req).await; + let res = call_service(&srv, req).await; assert_eq!(res.status(), StatusCode::OK); } } diff --git a/src/request_data.rs b/src/request_data.rs index 285154884..beee8ac12 100644 --- a/src/request_data.rs +++ b/src/request_data.rs @@ -102,7 +102,7 @@ mod tests { #[actix_rt::test] async fn req_data_extractor() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { if req.method() == Method::POST { @@ -142,7 +142,7 @@ mod tests { #[actix_rt::test] async fn req_data_internal_mutability() { - let mut srv = init_service( + let srv = init_service( App::new() .wrap_fn(|req, srv| { let data_before = Rc::new(RefCell::new(42u32)); diff --git a/src/resource.rs b/src/resource.rs index 3844b429d..188e6fa43 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -328,7 +328,7 @@ where >, > where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future>, { Resource { @@ -471,8 +471,8 @@ impl Service for ResourceService { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - for route in self.routes.iter_mut() { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + for route in self.routes.iter() { if route.check(&mut req) { if let Some(ref app_data) = self.app_data { req.add_data_container(app_data.clone()); @@ -530,7 +530,7 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let mut srv = + let srv = init_service( App::new().service( web::resource("/test") @@ -544,7 +544,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -554,7 +554,7 @@ mod tests { #[actix_rt::test] async fn test_middleware_fn() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/test") .wrap_fn(|req, srv| { @@ -574,7 +574,7 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -584,20 +584,20 @@ mod tests { #[actix_rt::test] async fn test_to() { - let mut srv = + let srv = init_service(App::new().service(web::resource("/test").to(|| async { sleep(Duration::from_millis(100)).await; Ok::<_, Error>(HttpResponse::Ok()) }))) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_pattern() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource(["/test", "/test2"]) .to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }), @@ -605,16 +605,16 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test2").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new() .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))) .default_service(|r: ServiceRequest| { @@ -623,16 +623,16 @@ mod tests { ) .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); - let mut srv = init_service( + let srv = init_service( App::new().service( web::resource("/test") .route(web::get().to(HttpResponse::Ok)) @@ -644,19 +644,19 @@ mod tests { .await; let req = TestRequest::with_uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } #[actix_rt::test] async fn test_resource_guards() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/test/{p}") @@ -679,25 +679,25 @@ mod tests { let req = TestRequest::with_uri("/test/it") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test/it") .method(Method::PUT) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test/it") .method(Method::DELETE) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NO_CONTENT); } #[actix_rt::test] async fn test_data() { - let mut srv = init_service( + let srv = init_service( App::new() .data(1.0f64) .data(1usize) @@ -723,13 +723,13 @@ mod tests { .await; let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_data_default_service() { - let mut srv = init_service( + let srv = init_service( App::new().data(1usize).service( web::resource("/test") .data(10usize) @@ -742,7 +742,7 @@ mod tests { .await; let req = TestRequest::get().uri("/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } } diff --git a/src/responder.rs b/src/responder.rs index d5771d520..dcad45e0f 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -260,7 +260,7 @@ pub(crate) mod tests { #[actix_rt::test] async fn test_option_responder() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/none").to(|| async { Option::<&'static str>::None }), diff --git a/src/route.rs b/src/route.rs index 8a3d1da9f..c5e297411 100644 --- a/src/route.rs +++ b/src/route.rs @@ -121,11 +121,11 @@ impl Service for RouteService { type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { self.service.call(req) } } @@ -299,11 +299,11 @@ where type Error = Error; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { + fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.service.poll_ready(cx) } - fn call(&mut self, req: ServiceRequest) -> Self::Future { + fn call(&self, req: ServiceRequest) -> Self::Future { Box::pin(self.service.call(req)) } } @@ -327,7 +327,7 @@ mod tests { #[actix_rt::test] async fn test_route() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/test") @@ -356,35 +356,35 @@ mod tests { let req = TestRequest::with_uri("/test") .method(Method::GET) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/test") .method(Method::POST) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::CREATED); let req = TestRequest::with_uri("/test") .method(Method::PUT) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::DELETE) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/test") .method(Method::HEAD) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::METHOD_NOT_ALLOWED); let req = TestRequest::with_uri("/json").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; diff --git a/src/scope.rs b/src/scope.rs index 290a35eb1..d17acd843 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -380,7 +380,7 @@ where >, > where - F: FnMut(ServiceRequest, &mut T::Service) -> R + Clone, + F: Fn(ServiceRequest, &T::Service) -> R + Clone, R: Future>, { Scope { @@ -526,8 +526,8 @@ impl Service for ScopeService { actix_service::always_ready!(); - fn call(&mut self, mut req: ServiceRequest) -> Self::Future { - let res = self.router.recognize_mut_checked(&mut req, |req, guards| { + fn call(&self, mut req: ServiceRequest) -> Self::Future { + let res = self.router.recognize_checked(&mut req, |req, guards| { if let Some(ref guards) = guards { for f in guards { if !f.check(req.head()) { @@ -589,7 +589,7 @@ mod tests { #[actix_rt::test] async fn test_scope() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), )) .await; @@ -601,7 +601,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .service(web::resource("").to(HttpResponse::Ok)) @@ -621,7 +621,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root2() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)), )) .await; @@ -637,7 +637,7 @@ mod tests { #[actix_rt::test] async fn test_scope_root3() { - let mut srv = init_service(App::new().service( + let srv = init_service(App::new().service( web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)), )) .await; @@ -653,7 +653,7 @@ mod tests { #[actix_rt::test] async fn test_scope_route() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .route("/path1", web::get().to(HttpResponse::Ok)) @@ -681,7 +681,7 @@ mod tests { #[actix_rt::test] async fn test_scope_route_without_leading_slash() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app").service( web::resource("path1") @@ -711,7 +711,7 @@ mod tests { #[actix_rt::test] async fn test_scope_guard() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .guard(guard::Get()) @@ -735,14 +735,13 @@ mod tests { #[actix_rt::test] async fn test_scope_variable_segment() { - let mut srv = - init_service(App::new().service(web::scope("/ab-{project}").service( - web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) - }), - ))) - .await; + let srv = init_service(App::new().service(web::scope("/ab-{project}").service( + web::resource("/path1").to(|r: HttpRequest| { + HttpResponse::Ok() + .body(format!("project: {}", &r.match_info()["project"])) + }), + ))) + .await; let req = TestRequest::with_uri("/ab-project1/path1").to_request(); let resp = srv.call(req).await.unwrap(); @@ -763,7 +762,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; @@ -775,7 +774,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_no_slash() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)), ))) .await; @@ -787,7 +786,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_root() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") @@ -809,7 +808,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_filter() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app").service( web::scope("/t1") @@ -835,7 +834,7 @@ mod tests { #[actix_rt::test] async fn test_nested_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project_id}").service(web::resource("/path1").to( |r: HttpRequest| { HttpResponse::Created() @@ -860,7 +859,7 @@ mod tests { #[actix_rt::test] async fn test_nested2_scope_with_variable_segment() { - let mut srv = init_service(App::new().service(web::scope("/app").service( + let srv = init_service(App::new().service(web::scope("/app").service( web::scope("/{project}").service(web::scope("/{id}").service( web::resource("/path1").to(|r: HttpRequest| { HttpResponse::Created().body(format!( @@ -892,7 +891,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("/app") .service(web::resource("/path1").to(HttpResponse::Ok)) @@ -914,7 +913,7 @@ mod tests { #[actix_rt::test] async fn test_default_resource_propagation() { - let mut srv = init_service( + let srv = init_service( App::new() .service( web::scope("/app1") @@ -942,7 +941,7 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap( @@ -959,7 +958,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -969,7 +968,7 @@ mod tests { #[actix_rt::test] async fn test_middleware_fn() { - let mut srv = init_service( + let srv = init_service( App::new().service( web::scope("app") .wrap_fn(|req, srv| { @@ -989,7 +988,7 @@ mod tests { .await; let req = TestRequest::with_uri("/app/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); assert_eq!( resp.headers().get(header::CONTENT_TYPE).unwrap(), @@ -999,7 +998,7 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).route( "/t", web::get().to(|data: web::Data| { @@ -1011,13 +1010,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_data_default_service() { - let mut srv = init_service(App::new().data(1usize).service( + let srv = init_service(App::new().data(1usize).service( web::scope("app").data(10usize).default_service(web::to( |data: web::Data| { assert_eq!(**data, 10); @@ -1028,13 +1027,13 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_override_app_data() { - let mut srv = init_service(App::new().app_data(web::Data::new(1usize)).service( + let srv = init_service(App::new().app_data(web::Data::new(1usize)).service( web::scope("app").app_data(web::Data::new(10usize)).route( "/t", web::get().to(|data: web::Data| { @@ -1046,17 +1045,16 @@ mod tests { .await; let req = TestRequest::with_uri("/app/t").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } #[actix_rt::test] async fn test_scope_config() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.route("/path1", web::get().to(HttpResponse::Ok)); - }))) - .await; + let srv = init_service(App::new().service(web::scope("/app").configure(|s| { + s.route("/path1", web::get().to(HttpResponse::Ok)); + }))) + .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1065,13 +1063,12 @@ mod tests { #[actix_rt::test] async fn test_scope_config_2() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.route("/", web::get().to(HttpResponse::Ok)); - })); - }))) - .await; + let srv = init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.route("/", web::get().to(HttpResponse::Ok)); + })); + }))) + .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1080,24 +1077,20 @@ mod tests { #[actix_rt::test] async fn test_url_for_external() { - let mut srv = - init_service(App::new().service(web::scope("/app").configure(|s| { - s.service(web::scope("/v1").configure(|s| { - s.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); - s.route( - "/", - web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), - ) - }), - ); - })); - }))) - .await; + let srv = init_service(App::new().service(web::scope("/app").configure(|s| { + s.service(web::scope("/v1").configure(|s| { + s.external_resource("youtube", "https://youtube.com/watch/{video_id}"); + s.route( + "/", + web::get().to(|req: HttpRequest| { + HttpResponse::Ok().body( + req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), + ) + }), + ); + })); + }))) + .await; let req = TestRequest::with_uri("/app/v1/").to_request(); let resp = srv.call(req).await.unwrap(); @@ -1108,7 +1101,7 @@ mod tests { #[actix_rt::test] async fn test_url_for_nested() { - let mut srv = init_service(App::new().service(web::scope("/a").service( + let srv = init_service(App::new().service(web::scope("/a").service( web::scope("/b").service(web::resource("/c/{stuff}").name("c").route( web::get().to(|req: HttpRequest| { HttpResponse::Ok() @@ -1119,7 +1112,7 @@ mod tests { .await; let req = TestRequest::with_uri("/a/b/c/test").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let body = read_body(resp).await; assert_eq!( diff --git a/src/server.rs b/src/server.rs index 8bfb27b77..59d589439 100644 --- a/src/server.rs +++ b/src/server.rs @@ -630,7 +630,7 @@ where /// } /// ``` pub fn run(self) -> Server { - self.builder.start() + self.builder.run() } } diff --git a/src/service.rs b/src/service.rs index 596eedd7f..86949dca6 100644 --- a/src/service.rs +++ b/src/service.rs @@ -542,7 +542,7 @@ mod tests { #[actix_rt::test] async fn test_service() { - let mut srv = init_service( + let srv = init_service( App::new().service(web::service("/test").name("test").finish( |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), )), @@ -552,7 +552,7 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); - let mut srv = init_service( + let srv = init_service( App::new().service(web::service("/test").guard(guard::Get()).finish( |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())), )), @@ -567,7 +567,7 @@ mod tests { #[actix_rt::test] async fn test_service_data() { - let mut srv = init_service( + let srv = init_service( App::new() .data(42u32) .service(web::service("/test").name("test").finish( diff --git a/src/test.rs b/src/test.rs index f3347bbbf..3acb520ff 100644 --- a/src/test.rs +++ b/src/test.rs @@ -60,7 +60,7 @@ pub fn default_service( /// /// #[actix_rt::test] /// async fn test_init_service() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { HttpResponse::Ok() })) /// ).await; @@ -116,7 +116,7 @@ where /// /// #[actix_rt::test] /// async fn test_response() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new() /// .service(web::resource("/test").to(|| async { /// HttpResponse::Ok() @@ -127,11 +127,11 @@ where /// let req = test::TestRequest::with_uri("/test").to_request(); /// /// // Call application -/// let resp = test::call_service(&mut app, req).await; +/// let resp = test::call_service(&app, req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// } /// ``` -pub async fn call_service(app: &mut S, req: R) -> S::Response +pub async fn call_service(app: &S, req: R) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, @@ -147,7 +147,7 @@ where /// /// #[actix_rt::test] /// async fn test_index() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/index.html") /// .route(web::post().to(|| async { @@ -160,11 +160,11 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let result = test::read_response(&mut app, req).await; +/// let result = test::read_response(&app, req).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } /// ``` -pub async fn read_response(app: &mut S, req: Request) -> Bytes +pub async fn read_response(app: &S, req: Request) -> Bytes where S: Service, Error = Error>, B: MessageBody + Unpin, @@ -190,7 +190,7 @@ where /// /// #[actix_rt::test] /// async fn test_index() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/index.html") /// .route(web::post().to(|| async { @@ -203,7 +203,7 @@ where /// .header(header::CONTENT_TYPE, "application/json") /// .to_request(); /// -/// let resp = test::call_service(&mut app, req).await; +/// let resp = test::call_service(&app, req).await; /// let result = test::read_body(resp).await; /// assert_eq!(result, Bytes::from_static(b"welcome!")); /// } @@ -234,7 +234,7 @@ where /// /// #[actix_rt::test] /// async fn test_post_person() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { @@ -294,7 +294,7 @@ where /// /// #[actix_rt::test] /// async fn test_add_person() { -/// let mut app = test::init_service( +/// let app = test::init_service( /// App::new().service( /// web::resource("/people") /// .route(web::post().to(|person: web::Json| async { @@ -314,7 +314,7 @@ where /// let result: Person = test::read_response_json(&mut app, req).await; /// } /// ``` -pub async fn read_response_json(app: &mut S, req: Request) -> T +pub async fn read_response_json(app: &S, req: Request) -> T where S: Service, Error = Error>, B: MessageBody + Unpin, @@ -569,7 +569,7 @@ impl TestRequest { } /// Complete request creation, calls service and waits for response future completion. - pub async fn send_request(self, app: &mut S) -> S::Response + pub async fn send_request(self, app: &S) -> S::Response where S: Service, Error = E>, E: std::fmt::Debug, @@ -595,7 +595,7 @@ impl TestRequest { /// /// #[actix_rt::test] /// async fn test_example() { -/// let mut srv = test::start( +/// let srv = test::start( /// || App::new().service( /// web::resource("/").to(my_handler)) /// ); @@ -635,7 +635,7 @@ where /// /// #[actix_rt::test] /// async fn test_example() { -/// let mut srv = test::start_with(test::config().h1(), || +/// let srv = test::start_with(test::config().h1(), || /// App::new().service(web::resource("/").to(my_handler)) /// ); /// @@ -667,7 +667,7 @@ where // run server in separate thread thread::spawn(move || { - let sys = System::new("actix-test-server"); + 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(); @@ -760,7 +760,7 @@ where .unwrap(); sys.block_on(async { - let srv = srv.start(); + let srv = srv.run(); tx.send((System::current(), srv, local_addr)).unwrap(); }); @@ -1043,7 +1043,7 @@ mod tests { #[actix_rt::test] async fn test_request_methods() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::put().to(|| HttpResponse::Ok().body("put!"))) @@ -1058,7 +1058,7 @@ mod tests { .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, put_req).await; + let result = read_response(&app, put_req).await; assert_eq!(result, Bytes::from_static(b"put!")); let patch_req = TestRequest::patch() @@ -1066,17 +1066,17 @@ mod tests { .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, patch_req).await; + let result = read_response(&app, patch_req).await; assert_eq!(result, Bytes::from_static(b"patch!")); let delete_req = TestRequest::delete().uri("/index.html").to_request(); - let result = read_response(&mut app, delete_req).await; + let result = read_response(&app, delete_req).await; assert_eq!(result, Bytes::from_static(b"delete!")); } #[actix_rt::test] async fn test_response() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::post().to(|| HttpResponse::Ok().body("welcome!"))), @@ -1089,13 +1089,13 @@ mod tests { .insert_header((header::CONTENT_TYPE, "application/json")) .to_request(); - let result = read_response(&mut app, req).await; + let result = read_response(&app, req).await; assert_eq!(result, Bytes::from_static(b"welcome!")); } #[actix_rt::test] async fn test_send_request() { - let mut app = init_service( + let app = init_service( App::new().service( web::resource("/index.html") .route(web::get().to(|| HttpResponse::Ok().body("welcome!"))), @@ -1105,7 +1105,7 @@ mod tests { let resp = TestRequest::get() .uri("/index.html") - .send_request(&mut app) + .send_request(&app) .await; let result = read_body(resp).await; @@ -1120,7 +1120,7 @@ mod tests { #[actix_rt::test] async fn test_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1133,13 +1133,13 @@ mod tests { .set_payload(payload) .to_request(); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); } #[actix_rt::test] async fn test_body_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1150,7 +1150,7 @@ mod tests { .uri("/people") .insert_header((header::CONTENT_TYPE, "application/json")) .set_payload(payload) - .send_request(&mut app) + .send_request(&app) .await; let result: Person = read_body_json(resp).await; @@ -1159,7 +1159,7 @@ mod tests { #[actix_rt::test] async fn test_request_response_form() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Form| HttpResponse::Ok().json(person)), ))) .await; @@ -1176,14 +1176,14 @@ mod tests { assert_eq!(req.content_type(), "application/x-www-form-urlencoded"); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } #[actix_rt::test] async fn test_request_response_json() { - let mut app = init_service(App::new().service(web::resource("/people").route( + let app = init_service(App::new().service(web::resource("/people").route( web::post().to(|person: web::Json| HttpResponse::Ok().json(person)), ))) .await; @@ -1200,7 +1200,7 @@ mod tests { assert_eq!(req.content_type(), "application/json"); - let result: Person = read_response_json(&mut app, req).await; + let result: Person = read_response_json(&app, req).await; assert_eq!(&result.id, "12345"); assert_eq!(&result.name, "User name"); } @@ -1218,7 +1218,7 @@ mod tests { } } - let mut app = init_service( + let app = init_service( App::new().service(web::resource("/index.html").to(async_with_block)), ) .await; @@ -1235,7 +1235,7 @@ mod tests { HttpResponse::Ok() } - let mut app = init_service( + let app = init_service( App::new() .data(10usize) .service(web::resource("/index.html").to(handler)), @@ -1291,7 +1291,7 @@ mod tests { .data(addr.clone()) .service(web::resource("/").to(actor_handler)); - let mut app = init_service(srv).await; + let app = init_service(srv).await; let req = TestRequest::post().uri("/").to_request(); let res = app.call(req).await.unwrap(); diff --git a/src/types/payload.rs b/src/types/payload.rs index e8fba0632..724034522 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -392,7 +392,7 @@ mod tests { "payload is probably json string" } - let mut srv = init_service( + let srv = init_service( App::new() .service( web::resource("/bytes-app-data") @@ -422,43 +422,43 @@ mod tests { .await; let req = TestRequest::with_uri("/bytes-app-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/string-app-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/string-data").to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::BAD_REQUEST); let req = TestRequest::with_uri("/bytes-app-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/bytes-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-app-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); let req = TestRequest::with_uri("/string-data") .insert_header(header::ContentType(mime::APPLICATION_JSON)) .to_request(); - let resp = call_service(&mut srv, req).await; + let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); } diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 5eca14931..78d4ef685 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -13,7 +13,7 @@ async fn test_start() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new("test"); + let sys = actix_rt::System::new(); sys.block_on(async { let srv = HttpServer::new(|| { @@ -91,7 +91,7 @@ async fn test_start_ssl() { let (tx, rx) = mpsc::channel(); thread::spawn(move || { - let sys = actix_rt::System::new("test"); + let sys = actix_rt::System::new(); let builder = ssl_acceptor().unwrap(); let srv = HttpServer::new(|| { From 7cfed73be8d4349d0e2795e4fde1b3beba5e77c0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 19:20:35 -0800 Subject: [PATCH 136/187] fix memory usage for h1 and read bug on buffer size. (#1929) --- actix-http/src/h1/decoder.rs | 27 ++++++++++++++++-------- actix-http/src/h1/dispatcher.rs | 37 +++++++++++++++++++++++++-------- 2 files changed, 46 insertions(+), 18 deletions(-) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 85379b084..9da958563 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -14,7 +14,7 @@ use crate::header::HeaderMap; use crate::message::{ConnectionType, ResponseHead}; use crate::request::Request; -const MAX_BUFFER_SIZE: usize = 131_072; +pub(crate) const MAX_BUFFER_SIZE: usize = 131_072; const MAX_HEADERS: usize = 96; /// Incoming message decoder @@ -203,7 +203,15 @@ impl MessageType for Request { (len, method, uri, version, req.headers.len()) } - httparse::Status::Partial => return Ok(None), + httparse::Status::Partial => { + return if src.len() >= MAX_BUFFER_SIZE { + trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(ParseError::TooLarge) + } else { + // Return None to notify more read are needed for parsing request + Ok(None) + }; + } } }; @@ -222,9 +230,6 @@ impl MessageType for Request { PayloadLength::None => { if method == Method::CONNECT { PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - trace!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); } else { PayloadType::None } @@ -273,7 +278,14 @@ impl MessageType for ResponseHead { (len, version, status, res.headers.len()) } - httparse::Status::Partial => return Ok(None), + httparse::Status::Partial => { + return if src.len() >= MAX_BUFFER_SIZE { + error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); + Err(ParseError::TooLarge) + } else { + Ok(None) + } + } } }; @@ -289,9 +301,6 @@ impl MessageType for ResponseHead { } else if status == StatusCode::SWITCHING_PROTOCOLS { // switching protocol or connect PayloadType::Stream(PayloadDecoder::eof()) - } else if src.len() >= MAX_BUFFER_SIZE { - error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); - return Err(ParseError::TooLarge); } else { // for HTTP/1.0 read to eof and close connection if msg.version == Version::HTTP_10 { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 90a5f0e06..e65a1bd56 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -29,8 +29,8 @@ use super::codec::Codec; use super::payload::{Payload, PayloadSender, PayloadStatus}; use super::{Message, MessageType}; -const LW_BUFFER_SIZE: usize = 4096; -const HW_BUFFER_SIZE: usize = 32_768; +const LW_BUFFER_SIZE: usize = 1024; +const HW_BUFFER_SIZE: usize = 1024 * 8; const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { @@ -404,7 +404,7 @@ where }, StateProj::SendPayload(mut stream) => { loop { - if this.write_buf.len() < HW_BUFFER_SIZE { + if this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { match stream.as_mut().poll_next(cx) { Poll::Ready(Some(Ok(item))) => { this.codec.encode( @@ -605,6 +605,8 @@ where } } } + // decode is partial and buffer is not full yet. + // break and wait for more read. Ok(None) => break, Err(ParseError::Io(e)) => { self.as_mut().client_disconnected(); @@ -612,6 +614,18 @@ where *this.error = Some(DispatchError::Io(e)); break; } + Err(ParseError::TooLarge) => { + if let Some(mut payload) = this.payload.take() { + payload.set_error(PayloadError::Overflow); + } + // Requests overflow buffer size should be responded with 413 + this.messages.push_back(DispatcherMessage::Error( + Response::PayloadTooLarge().finish().drop_body(), + )); + this.flags.insert(Flags::READ_DISCONNECT); + *this.error = Some(ParseError::TooLarge.into()); + break; + } Err(e) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); @@ -890,12 +904,7 @@ where let mut read_some = false; loop { - // If buf is full return but do not disconnect since - // there is more reading to be done - if buf.len() >= HW_BUFFER_SIZE { - return Ok(Some(false)); - } - + // reserve capacity for buffer let remaining = buf.capacity() - buf.len(); if remaining < LW_BUFFER_SIZE { buf.reserve(HW_BUFFER_SIZE - remaining); @@ -909,6 +918,16 @@ where if n == 0 { return Ok(Some(true)); } else { + // If buf is full return but do not disconnect since + // there is more reading to be done + if buf.len() >= super::decoder::MAX_BUFFER_SIZE { + // at this point it's not known io is still scheduled to + // be waked up. so force wake up dispatcher just in case. + // TODO: figure out the overhead. + cx.waker().wake_by_ref(); + return Ok(Some(false)); + } + read_some = true; } } From 830fb2cdb2e52c97ffc7a53e1469aaf5d5e39f1a Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sat, 6 Feb 2021 19:51:36 -0800 Subject: [PATCH 137/187] properly drop h2 connection (#1926) --- actix-http/src/client/connection.rs | 82 ++++++++++++++++++++++++++++- actix-http/src/client/h2proto.rs | 5 +- actix-http/src/client/pool.rs | 11 ++-- 3 files changed, 89 insertions(+), 9 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 26d392120..4c6a6dcb8 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,9 +1,11 @@ use std::future::Future; +use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::{Context, Poll}; use std::{fmt, io, time}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; +use actix_rt::task::JoinHandle; use bytes::Bytes; use futures_core::future::LocalBoxFuture; use futures_util::future::{err, Either, FutureExt, Ready}; @@ -21,7 +23,53 @@ use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { H1(Io), - H2(SendRequest), + H2(H2Connection), +} + +// h2 connection has two parts: SendRequest and Connection. +// Connection is spawned as async task on runtime and H2Connection would hold a handle for +// this task. So it can wake up and quit the task when SendRequest is dropped. +pub(crate) struct H2Connection { + handle: JoinHandle<()>, + sender: SendRequest, +} + +impl H2Connection { + pub(crate) fn new( + sender: SendRequest, + connection: h2::client::Connection, + ) -> Self + where + Io: AsyncRead + AsyncWrite + Unpin + 'static, + { + let handle = actix_rt::spawn(async move { + let _ = connection.await; + }); + + Self { handle, sender } + } +} + +// wake up waker when drop +impl Drop for H2Connection { + fn drop(&mut self) { + self.handle.abort(); + } +} + +// only expose sender type to public. +impl Deref for H2Connection { + type Target = SendRequest; + + fn deref(&self) -> &Self::Target { + &self.sender + } +} + +impl DerefMut for H2Connection { + fn deref_mut(&mut self) -> &mut Self::Target { + &mut self.sender + } } pub trait Connection { @@ -266,3 +314,35 @@ where } } } + +#[cfg(test)] +mod test { + use std::net; + + use actix_rt::net::TcpStream; + + use super::*; + + #[actix_rt::test] + async fn test_h2_connection_drop() { + let addr = "127.0.0.1:0".parse::().unwrap(); + let listener = net::TcpListener::bind(addr).unwrap(); + let local = listener.local_addr().unwrap(); + + std::thread::spawn(move || while listener.accept().is_ok() {}); + + let tcp = TcpStream::connect(local).await.unwrap(); + let (sender, connection) = h2::client::handshake(tcp).await.unwrap(); + let conn = H2Connection::new(sender.clone(), connection); + + assert!(sender.clone().ready().await.is_ok()); + assert!(h2::client::SendRequest::clone(&*conn).ready().await.is_ok()); + + drop(conn); + + match sender.ready().await { + Ok(_) => panic!("connection should be gone and can not be ready"), + Err(e) => assert!(e.is_io()), + }; + } +} diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 76915f214..a70bc1738 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -22,9 +22,10 @@ use super::config::ConnectorConfig; use super::connection::{ConnectionType, IoConnection}; use super::error::SendRequestError; use super::pool::Acquired; +use crate::client::connection::H2Connection; pub(crate) async fn send_request( - mut io: SendRequest, + mut io: H2Connection, head: RequestHeadType, body: B, created: time::Instant, @@ -173,7 +174,7 @@ async fn send_body( /// release SendRequest object fn release( - io: SendRequest, + io: H2Connection, pool: Option>, created: time::Instant, close: bool, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 1eebef53b..867ba5c0c 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -26,6 +26,7 @@ use super::connection::{ConnectionType, IoConnection}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; +use crate::client::connection::H2Connection; #[derive(Clone, Copy, PartialEq)] /// Protocol version @@ -139,10 +140,9 @@ where Some(guard.consume()), )) } else { - let (snd, connection) = handshake(io, &config).await?; - actix_rt::spawn(connection.map(|_| ())); + let (sender, connection) = handshake(io, &config).await?; Ok(IoConnection::new( - ConnectionType::H2(snd), + ConnectionType::H2(H2Connection::new(sender, connection)), Instant::now(), Some(guard.consume()), )) @@ -566,11 +566,10 @@ where if let Some(ref mut h2) = this.h2 { return match Pin::new(h2).poll(cx) { - Poll::Ready(Ok((snd, connection))) => { - actix_rt::spawn(connection.map(|_| ())); + Poll::Ready(Ok((sender, connection))) => { let rx = this.rx.take().unwrap(); let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H2(snd), + ConnectionType::H2(H2Connection::new(sender, connection)), Instant::now(), Some(Acquired(this.key.clone(), this.inner.take())), ))); From 9eaea6a2fd2c9005e7e64887569dcc27fec98596 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Feb 2021 03:54:58 +0000 Subject: [PATCH 138/187] tweak feature flags --- Cargo.toml | 12 ++++++------ README.md | 9 ++++----- actix-http-test/Cargo.toml | 6 +++--- actix-http-test/src/lib.rs | 5 ++++- actix-http/Cargo.toml | 10 +++++----- actix-http/tests/test_openssl.rs | 5 ++++- actix-http/tests/test_rustls.rs | 5 ++++- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 10 +++++----- awc/tests/test_connector.rs | 7 +++++-- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 7 +++++-- src/lib.rs | 5 +++++ src/test.rs | 10 +++++----- tests/test_httpserver.rs | 11 ++++++++--- tests/test_server.rs | 11 ++++++++--- 16 files changed, 73 insertions(+), 44 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 1e2c35d6a..479a89f87 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"] secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["actix-tls/accept", "actix-tls/openssl", "awc/openssl", "open-ssl"] +openssl = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] # rustls -rustls = ["actix-tls/accept", "actix-tls/rustls", "awc/rustls", "rust-tls"] +rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] [[example]] name = "basic" @@ -86,7 +86,7 @@ actix-web-codegen = "0.4.0" actix-http = "3.0.0-beta.1" awc = { version = "3.0.0-beta.1", default-features = false } -ahash = "0.6" +ahash = "0.7" bytes = "1" derive_more = "0.99.5" either = "1.5.3" @@ -101,10 +101,10 @@ regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" -time = { version = "0.2.7", default-features = false, features = ["std"] } +time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" -open-ssl = { package = "openssl", version = "0.10", optional = true } -rust-tls = { package = "rustls", version = "0.19.0", optional = true } +tls_openssl = { package = "openssl", version = "0.10.9", optional = true } +tls_rustls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" [dev-dependencies] diff --git a/README.md b/README.md index 956bb3741..8a703117f 100644 --- a/README.md +++ b/README.md @@ -11,10 +11,9 @@ ![License](https://img.shields.io/crates/l/actix-web.svg) [![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2)
-[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) +[![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) -[![Download](https://img.shields.io/crates/d/actix-web.svg)](https://crates.io/crates/actix-web) -[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +![downloads](https://img.shields.io/crates/d/actix-web.svg) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

@@ -99,9 +98,9 @@ One of the fastest web frameworks available according to the This project is licensed under either of * Apache License, Version 2.0, ([LICENSE-APACHE](LICENSE-APACHE) or - [http://www.apache.org/licenses/LICENSE-2.0](http://www.apache.org/licenses/LICENSE-2.0)) + [http://www.apache.org/licenses/LICENSE-2.0]) * MIT license ([LICENSE-MIT](LICENSE-MIT) or - [http://opensource.org/licenses/MIT](http://opensource.org/licenses/MIT)) + [http://opensource.org/licenses/MIT]) at your option. diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 0a804e109..25fd74a10 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -26,7 +26,7 @@ path = "src/lib.rs" default = [] # openssl -openssl = ["open-ssl", "awc/openssl"] +openssl = ["tls-openssl", "awc/openssl"] [dependencies] actix-service = "2.0.0-beta.4" @@ -47,8 +47,8 @@ serde = "1.0" serde_json = "1.0" slab = "0.4" serde_urlencoded = "0.7" -time = { version = "0.2.7", default-features = false, features = ["std"] } -open-ssl = { version = "0.10", package = "openssl", optional = true } +time = { version = "0.2.23", default-features = false, features = ["std"] } +tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = "4.0.0-beta.1" diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 2958b7f59..fcf8b3476 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -4,6 +4,9 @@ #![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; + use std::sync::mpsc; use std::{net, thread, time}; @@ -82,7 +85,7 @@ pub async fn test_server_with_addr>( let connector = { #[cfg(feature = "openssl")] { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index f55dc3b69..22a54f569 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -58,7 +58,7 @@ encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -ahash = "0.6" +ahash = "0.7" h2 = "0.3.0" http = "0.2.2" httparse = "1.3" @@ -78,7 +78,7 @@ sha-1 = "0.9" smallvec = "1.6" slab = "0.4" serde_urlencoded = "0.7" -time = { version = "0.2.7", default-features = false, features = ["std"] } +time = { version = "0.2.23", default-features = false, features = ["std"] } # compression brotli2 = { version="0.3.2", optional = true } @@ -89,10 +89,10 @@ actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" -env_logger = "0.7" +env_logger = "0.8" serde_derive = "1.0" -open-ssl = { version="0.10", package = "openssl" } -rust-tls = { version="0.19", package = "rustls" } +tls-openssl = { version = "0.10", package = "openssl" } +tls-rustls = { version = "0.19", package = "rustls" } [[bench]] name = "write-camel-case" diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index f20cfd70c..73bbe6208 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -1,4 +1,7 @@ #![cfg(feature = "openssl")] + +extern crate tls_openssl as openssl; + use std::io; use actix_http::error::{ErrorBadRequest, PayloadError}; @@ -11,7 +14,7 @@ use actix_service::{fn_service, ServiceFactoryExt}; use bytes::{Bytes, BytesMut}; use futures_util::future::{err, ok, ready}; use futures_util::stream::{once, Stream, StreamExt}; -use open_ssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; +use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; async fn load_body(stream: S) -> Result where diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 2f6b31f49..59ffcfeb0 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -1,4 +1,7 @@ #![cfg(feature = "rustls")] + +extern crate tls_rustls as rustls; + use actix_http::error::PayloadError; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; @@ -9,7 +12,7 @@ use actix_service::{fn_factory_with_config, fn_service}; use bytes::{Bytes, BytesMut}; use futures_util::future::{self, err, ok}; use futures_util::stream::{once, Stream, StreamExt}; -use rust_tls::{ +use rustls::{ internal::pemfile::{certs, pkcs8_private_keys}, NoClientAuth, ServerConfig as RustlsServerConfig, }; diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c6fd1c233..522279fd0 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -29,5 +29,5 @@ tokio = { version = "1", features = ["sync"] } [dev-dependencies] actix-rt = "2" -env_logger = "0.7" +env_logger = "0.8" futures-util = { version = "0.3.7", default-features = false } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 24275b6f5..7a9130780 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -28,10 +28,10 @@ features = ["openssl", "rustls", "compress"] default = ["compress"] # openssl -openssl = ["open-ssl", "actix-http/openssl"] +openssl = ["tls-openssl", "actix-http/openssl"] # rustls -rustls = ["rust-tls", "actix-http/rustls"] +rustls = ["tls-rustls", "actix-http/rustls"] # content-encoding support compress = ["actix-http/compress"] @@ -54,8 +54,8 @@ rand = "0.8" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" -open-ssl = { version = "0.10", package = "openssl", optional = true } -rust-tls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } +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.1", features = ["openssl"] } @@ -68,6 +68,6 @@ actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } brotli2 = "0.3.2" flate2 = "1.0.13" futures-util = { version = "0.3.7", default-features = false } -env_logger = "0.7" +env_logger = "0.8" rcgen = "0.8" webpki = "0.21" diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index e500801c4..4e4fa5833 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -1,10 +1,13 @@ #![cfg(feature = "openssl")] + +extern crate tls_openssl as openssl; + use actix_http::HttpService; use actix_http_test::test_server; use actix_service::{map_config, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; fn ssl_acceptor() -> SslAcceptor { // load ssl keys @@ -20,7 +23,7 @@ fn ssl_acceptor() -> SslAcceptor { if protos.windows(3).any(|window| window == H2) { Ok(b"h2") } else { - Err(open_ssl::ssl::AlpnError::NOACK) + Err(openssl::ssl::AlpnError::NOACK) } }); builder.set_alpn_protos(b"\x02h2").unwrap(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index 2da3d9696..b0f3b71be 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -1,6 +1,6 @@ #![cfg(feature = "rustls")] -extern crate rust_tls as rustls; +extern crate tls_rustls as rustls; use std::{ io::BufReader, diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index de1514042..064824d59 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -1,4 +1,7 @@ #![cfg(feature = "openssl")] + +extern crate tls_openssl as openssl; + use std::sync::atomic::{AtomicUsize, Ordering}; use std::sync::Arc; @@ -8,7 +11,7 @@ use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; -use open_ssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; fn ssl_acceptor() -> SslAcceptor { // load ssl keys @@ -24,7 +27,7 @@ fn ssl_acceptor() -> SslAcceptor { if protos.windows(3).any(|window| window == H2) { Ok(b"h2") } else { - Err(open_ssl::ssl::AlpnError::NOACK) + Err(openssl::ssl::AlpnError::NOACK) } }); builder.set_alpn_protos(b"\x02h2").unwrap(); diff --git a/src/lib.rs b/src/lib.rs index 52471f4b3..46fbc2937 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -71,6 +71,11 @@ #![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; diff --git a/src/test.rs b/src/test.rs index 3acb520ff..62c329c91 100644 --- a/src/test.rs +++ b/src/test.rs @@ -773,7 +773,7 @@ where let connector = { #[cfg(feature = "openssl")] { - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); @@ -825,9 +825,9 @@ enum HttpVer { enum StreamType { Tcp, #[cfg(feature = "openssl")] - Openssl(open_ssl::ssl::SslAcceptor), + Openssl(openssl::ssl::SslAcceptor), #[cfg(feature = "rustls")] - Rustls(rust_tls::ServerConfig), + Rustls(rustls::ServerConfig), } impl Default for TestServerConfig { @@ -865,14 +865,14 @@ impl TestServerConfig { /// Start openssl server #[cfg(feature = "openssl")] - pub fn openssl(mut self, acceptor: open_ssl::ssl::SslAcceptor) -> Self { + 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: rust_tls::ServerConfig) -> Self { + pub fn rustls(mut self, config: rustls::ServerConfig) -> Self { self.stream = StreamType::Rustls(config); self } diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 78d4ef685..cd5a75d49 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -2,7 +2,12 @@ use std::sync::mpsc; use std::{thread, time::Duration}; #[cfg(feature = "openssl")] -use open_ssl::ssl::SslAcceptorBuilder; +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + +#[cfg(feature = "openssl")] +use openssl::ssl::SslAcceptorBuilder; use actix_web::{test, web, App, HttpResponse, HttpServer}; @@ -70,7 +75,7 @@ async fn test_start() { #[allow(clippy::unnecessary_wraps)] #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { - use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder @@ -116,7 +121,7 @@ async fn test_start_ssl() { }); let (srv, sys) = rx.recv().unwrap(); - use open_ssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; + use openssl::ssl::{SslConnector, SslMethod, SslVerifyMode}; let mut builder = SslConnector::builder(SslMethod::tls()).unwrap(); builder.set_verify(SslVerifyMode::NONE); let _ = builder diff --git a/tests/test_server.rs b/tests/test_server.rs index 0f51a2fdb..c626474cf 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -1,3 +1,8 @@ +#[cfg(feature = "openssl")] +extern crate tls_openssl as openssl; +#[cfg(feature = "rustls")] +extern crate tls_rustls as rustls; + use std::{ future::Future, io::{Read, Write}, @@ -713,7 +718,7 @@ async fn test_brotli_encoding_large() { #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { // load ssl keys - use open_ssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; + use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); builder .set_private_key_file("tests/key.pem", SslFiletype::PEM) @@ -753,8 +758,8 @@ async fn test_brotli_encoding_large_openssl() { #[cfg(all(feature = "rustls", feature = "openssl"))] #[actix_rt::test] async fn test_reading_deflate_encoding_large_random_rustls() { - use rust_tls::internal::pemfile::{certs, pkcs8_private_keys}; - use rust_tls::{NoClientAuth, ServerConfig}; + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; use std::fs::File; use std::io::BufReader; From 50309aa295ba38eb74998092850734b8c8bfa981 Mon Sep 17 00:00:00 2001 From: Jens Reidel Date: Sun, 7 Feb 2021 05:50:23 +0100 Subject: [PATCH 139/187] Use askama-escape for html escaping (#1953) --- actix-files/CHANGES.md | 2 ++ actix-files/Cargo.toml | 3 ++- actix-files/src/directory.rs | 4 ++-- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 9f606dfcd..8e5566b61 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -2,8 +2,10 @@ ## Unreleased - 2021-xx-xx * Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] +* Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 +[#1953]: https://github.com/actix/actix-web/pull/1953 ## 0.6.0-beta.1 - 2021-01-07 * `HttpRange::parse` now has its own error type. diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 3371ab060..b4317596c 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -19,6 +19,8 @@ path = "src/lib.rs" [dependencies] actix-web = { version = "4.0.0-beta.1", default-features = false } actix-service = "2.0.0-beta.4" + +askama_escape = "0.10" bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } @@ -28,7 +30,6 @@ log = "0.4" mime = "0.3" mime_guess = "2.0.1" percent-encoding = "2.1" -v_htmlescape = "0.12" [dev-dependencies] actix-rt = "2" diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 3717985d3..1103dd6a7 100644 --- a/actix-files/src/directory.rs +++ b/actix-files/src/directory.rs @@ -1,8 +1,8 @@ use std::{fmt::Write, fs::DirEntry, io, path::Path, path::PathBuf}; use actix_web::{dev::ServiceResponse, HttpRequest, HttpResponse}; +use askama_escape::{escape as escape_html_entity, Html}; use percent_encoding::{utf8_percent_encode, CONTROLS}; -use v_htmlescape::escape as escape_html_entity; /// A directory; responds with the generated directory listing. #[derive(Debug)] @@ -50,7 +50,7 @@ macro_rules! encode_file_url { // " -- " & -- & ' -- ' < -- < > -- > / -- / macro_rules! encode_file_name { ($entry:ident) => { - escape_html_entity(&$entry.file_name().to_string_lossy()) + escape_html_entity(&$entry.file_name().to_string_lossy(), Html) }; } From deafb7c8b8038b3ab4bb80cf9b465a30453c7d44 Mon Sep 17 00:00:00 2001 From: Alexander Jackson Date: Sun, 7 Feb 2021 04:54:41 +0000 Subject: [PATCH 140/187] Improve `impl ResponseError` documentation (#1939) Co-authored-by: Rob Ede --- actix-http/src/error.rs | 35 +++++++++++++++++++++-------------- 1 file changed, 21 insertions(+), 14 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 28697cbbf..7beedc3ba 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -147,7 +147,10 @@ impl From for Error { } } -/// Return `GATEWAY_TIMEOUT` for `TimeoutError` +/// 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 { @@ -161,41 +164,44 @@ impl ResponseError for TimeoutError { #[display(fmt = "UnknownError")] struct UnitError; -/// `InternalServerError` for `UnitError` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`UnitError`]. impl ResponseError for UnitError {} -/// `InternalServerError` for `JsonError` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`JsonError`]. impl ResponseError for JsonError {} -/// `InternalServerError` for `FormError` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`FormError`]. impl ResponseError for FormError {} #[cfg(feature = "openssl")] -/// `InternalServerError` for `openssl::ssl::Error` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix_tls::accept::openssl::SslError`]. impl ResponseError for actix_tls::accept::openssl::SslError {} -/// Return `BAD_REQUEST` for `de::value::Error` +/// Returns [`StatusCode::BAD_REQUEST`] for [`DeError`]. impl ResponseError for DeError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } } -/// `InternalServerError` for `Canceled` +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`Canceled`]. impl ResponseError for Canceled {} -/// Return `BAD_REQUEST` for `Utf8Error` +/// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`]. impl ResponseError for Utf8Error { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } } -/// Return `InternalServerError` for `HttpError`, -/// Response generation can return `HttpError`, so it is internal error +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`HttpError`]. impl ResponseError for HttpError {} -/// Return `InternalServerError` for `io::Error` +/// Inspects the underlying [`io::ErrorKind`] and returns an appropriate status code. +/// +/// If the error is [`io::ErrorKind::NotFound`], [`StatusCode::NOT_FOUND`] is returned. If the +/// error is [`io::ErrorKind::PermissionDenied`], [`StatusCode::FORBIDDEN`] is returned. Otherwise, +/// [`StatusCode::INTERNAL_SERVER_ERROR`] is returned. impl ResponseError for io::Error { fn status_code(&self) -> StatusCode { match self.kind() { @@ -206,7 +212,7 @@ impl ResponseError for io::Error { } } -/// `BadRequest` for `InvalidHeaderValue` +/// Returns [`StatusCode::BAD_REQUEST`] for [`header::InvalidHeaderValue`]. impl ResponseError for header::InvalidHeaderValue { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST @@ -963,8 +969,9 @@ where } #[cfg(feature = "actors")] -/// `InternalServerError` for `actix::MailboxError` -/// This is supported on feature=`actors` only +/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix::MailboxError`]. +/// +/// This is only supported when the feature `actors` is enabled. impl ResponseError for actix::MailboxError {} #[cfg(test)] From 4c243cbf89e680772ed8ec7dcdde143e026e2a6c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 10:56:39 -0800 Subject: [PATCH 141/187] simplify methods of awc::connect::Connect trait (#1941) --- actix-http/src/h1/client.rs | 12 --- actix-http/src/h1/encoder.rs | 16 +--- awc/src/connect.rs | 143 +++++------------------------------ awc/src/sender.rs | 43 ++++++----- 4 files changed, 49 insertions(+), 165 deletions(-) diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 2e0103409..4a6104688 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -223,15 +223,3 @@ impl Encoder> for ClientCodec { Ok(()) } } - -pub struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index bb89905fb..bd8287b26 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -337,7 +337,7 @@ impl MessageType for RequestHeadType { let head = self.as_ref(); dst.reserve(256 + head.headers.len() * AVERAGE_HEADER_SIZE); write!( - Writer(dst), + helpers::Writer(dst), "{} {} {}", head.method, head.uri.path_and_query().map(|u| u.as_str()).unwrap_or("/"), @@ -470,7 +470,7 @@ impl TransferEncoding { *eof = true; buf.extend_from_slice(b"0\r\n\r\n"); } else { - writeln!(Writer(buf), "{:X}\r", msg.len()) + writeln!(helpers::Writer(buf), "{:X}\r", msg.len()) .map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; buf.reserve(msg.len() + 2); @@ -520,18 +520,6 @@ impl TransferEncoding { } } -struct Writer<'a>(pub &'a mut BytesMut); - -impl<'a> io::Write for Writer<'a> { - fn write(&mut self, buf: &[u8]) -> io::Result { - self.0.extend_from_slice(buf); - Ok(buf.len()) - } - fn flush(&mut self) -> io::Result<()> { - Ok(()) - } -} - /// # Safety /// Callers must ensure that the given length matches given value length. unsafe fn write_data(value: &[u8], buf: *mut u8, len: usize) { diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 2ffb8ec37..5450550a7 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -1,71 +1,39 @@ -use std::future::Future; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::{fmt, io, net}; +use std::{ + fmt, io, net, + pin::Pin, + task::{Context, Poll}, +}; use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; -use actix_http::body::Body; -use actix_http::client::{ - Connect as ClientConnect, ConnectError, Connection, SendRequestError, +use actix_http::{ + body::Body, + client::{Connect as ClientConnect, ConnectError, Connection, SendRequestError}, + h1::ClientCodec, + RequestHead, RequestHeadType, ResponseHead, }; -use actix_http::h1::ClientCodec; -use actix_http::http::HeaderMap; -use actix_http::{RequestHead, RequestHeadType, ResponseHead}; use actix_service::Service; +use futures_core::future::LocalBoxFuture; use crate::response::ClientResponse; pub(crate) struct ConnectorWrapper(pub T); +type TunnelResponse = (ResponseHead, Framed); + pub(crate) trait Connect { fn send_request( &self, - head: RequestHead, + head: RequestHeadType, body: Body, addr: Option, - ) -> Pin>>>; - - fn send_request_extra( - &self, - head: Rc, - extra_headers: Option, - body: Body, - addr: Option, - ) -> Pin>>>; + ) -> LocalBoxFuture<'static, Result>; /// Send request, returns Response and Framed fn open_tunnel( &self, head: RequestHead, addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - >; - - /// Send request and extra headers, returns Response and Framed - fn open_tunnel_extra( - &self, - head: Rc, - extra_headers: Option, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - >; + ) -> LocalBoxFuture<'static, Result>; } impl Connect for ConnectorWrapper @@ -79,13 +47,13 @@ where { fn send_request( &self, - head: RequestHead, + head: RequestHeadType, body: Body, addr: Option, - ) -> Pin>>> { + ) -> LocalBoxFuture<'static, Result> { // connect to the host let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), + uri: head.as_ref().uri.clone(), addr, }); @@ -93,33 +61,7 @@ where let connection = fut.await?; // send request - connection - .send_request(RequestHeadType::from(head), body) - .await - .map(|(head, payload)| ClientResponse::new(head, payload)) - }) - } - - fn send_request_extra( - &self, - head: Rc, - extra_headers: Option, - body: Body, - addr: Option, - ) -> Pin>>> { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, payload) = connection - .send_request(RequestHeadType::Rc(head, extra_headers), body) - .await?; + let (head, payload) = connection.send_request(head, body).await?; Ok(ClientResponse::new(head, payload)) }) @@ -129,16 +71,7 @@ where &self, head: RequestHead, addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - > { + ) -> LocalBoxFuture<'static, Result> { // connect to the host let fut = self.0.call(ClientConnect { uri: head.uri.clone(), @@ -156,40 +89,6 @@ where Ok((head, framed)) }) } - - fn open_tunnel_extra( - &self, - head: Rc, - extra_headers: Option, - addr: Option, - ) -> Pin< - Box< - dyn Future< - Output = Result< - (ResponseHead, Framed), - SendRequestError, - >, - >, - >, - > { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, framed) = connection - .open_tunnel(RequestHeadType::Rc(head, extra_headers)) - .await?; - - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok((head, framed)) - }) - } } trait AsyncSocket { diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 5f790a038..1cf863d96 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -1,21 +1,26 @@ -use std::future::Future; -use std::net; -use std::pin::Pin; -use std::rc::Rc; -use std::task::{Context, Poll}; -use std::time::Duration; +use std::{ + future::Future, + net, + pin::Pin, + rc::Rc, + task::{Context, Poll}, + time::Duration, +}; +use actix_http::{ + body::{Body, BodyStream}, + http::{ + header::{self, HeaderMap, HeaderName, IntoHeaderValue}, + Error as HttpError, + }, + Error, RequestHead, RequestHeadType, +}; use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; use futures_core::Stream; use serde::Serialize; -use actix_http::body::{Body, BodyStream}; -use actix_http::http::header::{self, IntoHeaderValue}; -use actix_http::http::{Error as HttpError, HeaderMap, HeaderName}; -use actix_http::{Error, RequestHead}; - #[cfg(feature = "compress")] use actix_http::encoding::Decoder; #[cfg(feature = "compress")] @@ -184,12 +189,16 @@ impl RequestSender { B: Into, { let fut = match self { - RequestSender::Owned(head) => { - config.connector.send_request(head, body.into(), addr) - } - RequestSender::Rc(head, extra_headers) => config - .connector - .send_request_extra(head, extra_headers, body.into(), addr), + RequestSender::Owned(head) => config.connector.send_request( + RequestHeadType::Owned(head), + body.into(), + addr, + ), + RequestSender::Rc(head, extra_headers) => config.connector.send_request( + RequestHeadType::Rc(head, extra_headers), + body.into(), + addr, + ), }; SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) From dbc47c9122b5731a11a5e1dc388efd0a13d65754 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 12:19:10 -0800 Subject: [PATCH 142/187] optimize actix-http messages (#1914) --- actix-http/src/message.rs | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index bccb4d53e..736f35ee1 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -343,6 +343,8 @@ impl ResponseHead { } pub struct Message { + // Rc here should not be cloned by anyone. + // It's used to reuse allocation of T and no shared ownership is allowed. head: Rc, } @@ -353,14 +355,6 @@ impl Message { } } -impl Clone for Message { - fn clone(&self) -> Self { - Message { - head: self.head.clone(), - } - } -} - impl std::ops::Deref for Message { type Target = T; @@ -377,9 +371,7 @@ impl std::ops::DerefMut for Message { impl Drop for Message { fn drop(&mut self) { - if Rc::strong_count(&self.head) == 1 { - T::with_pool(|p| p.release(self.head.clone())) - } + T::with_pool(|p| p.release(self.head.clone())) } } From 9604e249c9c840decd22ac6d66252063690e987c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 7 Feb 2021 20:33:53 +0000 Subject: [PATCH 143/187] use stable clippy (#1963) --- .github/workflows/bench.yml | 2 +- .github/workflows/clippy-fmt.yml | 21 ++++++++++++++------- .github/workflows/upload-doc.yml | 2 +- src/middleware/condition.rs | 1 - src/middleware/err_handlers.rs | 2 -- tests/test_httpserver.rs | 1 - 6 files changed, 16 insertions(+), 13 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index d19471a16..828d62561 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -1,4 +1,4 @@ -name: Benchmark (Linux) +name: Benchmark on: pull_request: diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index fb1ed7f32..e966fa4ab 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -1,32 +1,39 @@ +name: Lint + on: pull_request: types: [opened, synchronize, reopened] -name: Clippy and rustfmt Check jobs: - clippy_check: + fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - - uses: actions-rs/toolchain@v1 + - name: Install Rust + uses: actions-rs/toolchain@v1 with: toolchain: stable components: rustfmt - override: true - name: Check with rustfmt uses: actions-rs/cargo@v1 with: command: fmt args: --all -- --check - - uses: actions-rs/toolchain@v1 + clippy: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 with: - toolchain: nightly + toolchain: stable components: clippy override: true - name: Check with Clippy uses: actions-rs/clippy-check@v1 with: token: ${{ secrets.GITHUB_TOKEN }} - args: --all-features --all --tests + args: --workspace --tests --all-features diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index ba87a5637..c080dd8c3 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -24,7 +24,7 @@ jobs: uses: actions-rs/cargo@v1 with: command: doc - args: --no-deps --workspace --all-features + args: --workspace --all-features --no-deps - name: Tweak HTML run: echo "" > target/doc/index.html diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 85eba3fb8..9263292b9 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -106,7 +106,6 @@ mod tests { HttpResponse, }; - #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 70933241d..b55959460 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -182,7 +182,6 @@ mod tests { use crate::test::{self, TestRequest}; use crate::HttpResponse; - #[allow(clippy::unnecessary_wraps)] fn render_500(mut res: ServiceResponse) -> Result> { res.response_mut() .headers_mut() @@ -207,7 +206,6 @@ mod tests { assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } - #[allow(clippy::unnecessary_wraps)] fn render_500_async( mut res: ServiceResponse, ) -> Result> { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index cd5a75d49..07c171674 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -72,7 +72,6 @@ async fn test_start() { let _ = sys.stop(); } -#[allow(clippy::unnecessary_wraps)] #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; From 266cf0622c18fe569f6af3f0de19f0a226c98bc0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 14:48:27 -0800 Subject: [PATCH 144/187] reduce branch.remove deadcode for h1 dispatcher (#1962) --- actix-http/src/h1/dispatcher.rs | 202 +++++++++++++++----------------- 1 file changed, 95 insertions(+), 107 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e65a1bd56..f8fc95921 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -734,6 +734,78 @@ where } Ok(()) } + + /// Returns true when io stream can be disconnected after write to it. + /// + /// It covers these conditions: + /// + /// - `std::io::ErrorKind::ConnectionReset` after partial read. + /// - all data read done. + #[inline(always)] + fn read_available( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Result { + let this = self.project(); + + if this.flags.contains(Flags::READ_DISCONNECT) { + return Ok(false); + }; + + let mut io = Pin::new(this.io.as_mut().unwrap()); + + let mut read_some = false; + + loop { + // grow buffer if necessary. + let remaining = this.read_buf.capacity() - this.read_buf.len(); + if remaining < LW_BUFFER_SIZE { + this.read_buf.reserve(HW_BUFFER_SIZE - remaining); + } + + match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { + Poll::Pending => return Ok(false), + Poll::Ready(Ok(n)) => { + if n == 0 { + return Ok(true); + } else { + // Return early when read buf exceed decoder's max buffer size. + if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE { + // at this point it's not known io is still scheduled to + // be waked up. so force wake up dispatcher just in case. + // TODO: figure out the overhead. + cx.waker().wake_by_ref(); + return Ok(false); + } + + read_some = true; + } + } + Poll::Ready(Err(err)) => { + return if err.kind() == io::ErrorKind::WouldBlock { + Ok(false) + } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { + Ok(true) + } else { + Err(DispatchError::Io(err)) + } + } + } + } + } + + /// call upgrade service with request. + fn upgrade(self: Pin<&mut Self>, req: Request) -> U::Future { + let this = self.project(); + let mut parts = FramedParts::with_read_buf( + this.io.take().unwrap(), + mem::take(this.codec), + mem::take(this.read_buf), + ); + parts.write_buf = mem::take(this.write_buf); + let framed = Framed::from_parts(parts); + this.flow.upgrade.as_ref().unwrap().call((req, framed)) + } } impl Future for Dispatcher @@ -778,60 +850,36 @@ where } } } else { - // read socket into a buf - let should_disconnect = - if !inner.flags.contains(Flags::READ_DISCONNECT) { - let mut inner_p = inner.as_mut().project(); - read_available( - cx, - inner_p.io.as_mut().unwrap(), - &mut inner_p.read_buf, - )? - } else { - None - }; + // read from io stream and fill read buffer. + let should_disconnect = inner.as_mut().read_available(cx)?; inner.as_mut().poll_request(cx)?; - if let Some(true) = should_disconnect { - let inner_p = inner.as_mut().project(); - inner_p.flags.insert(Flags::READ_DISCONNECT); - if let Some(mut payload) = inner_p.payload.take() { + + // io stream should to be closed. + if should_disconnect { + let inner = inner.as_mut().project(); + inner.flags.insert(Flags::READ_DISCONNECT); + if let Some(mut payload) = inner.payload.take() { payload.feed_eof(); } }; loop { - let inner_p = inner.as_mut().project(); - let remaining = - inner_p.write_buf.capacity() - inner_p.write_buf.len(); - if remaining < LW_BUFFER_SIZE { - inner_p.write_buf.reserve(HW_BUFFER_SIZE - remaining); - } - let result = inner.as_mut().poll_response(cx)?; - let drain = result == PollResponse::DrainWriteBuf; - - // switch to upgrade handler - if let PollResponse::Upgrade(req) = result { - let inner_p = inner.as_mut().project(); - let mut parts = FramedParts::with_read_buf( - inner_p.io.take().unwrap(), - mem::take(inner_p.codec), - mem::take(inner_p.read_buf), - ); - parts.write_buf = mem::take(inner_p.write_buf); - let framed = Framed::from_parts(parts); - let upgrade = inner_p - .flow - .upgrade - .as_ref() - .unwrap() - .call((req, framed)); - self.as_mut() - .project() - .inner - .set(DispatcherState::Upgrade(upgrade)); - return self.poll(cx); - } + // poll_response and populate write buffer. + // drain indicate if write buffer should be emptied before next run. + let drain = match inner.as_mut().poll_response(cx)? { + PollResponse::DrainWriteBuf => true, + PollResponse::DoNothing => false, + // upgrade request and goes Upgrade variant of DispatcherState. + PollResponse::Upgrade(req) => { + let upgrade = inner.upgrade(req); + self.as_mut() + .project() + .inner + .set(DispatcherState::Upgrade(upgrade)); + return self.poll(cx); + } + }; // we didn't get WouldBlock from write operation, // so data get written to kernel completely (macOS) @@ -888,66 +936,6 @@ where } } -/// Returns either: -/// - `Ok(Some(true))` - data was read and done reading all data. -/// - `Ok(Some(false))` - data was read but there should be more to read. -/// - `Ok(None)` - no data was read but there should be more to read later. -/// - Unhandled Errors -fn read_available( - cx: &mut Context<'_>, - io: &mut T, - buf: &mut BytesMut, -) -> Result, io::Error> -where - T: AsyncRead + Unpin, -{ - let mut read_some = false; - - loop { - // reserve capacity for buffer - let remaining = buf.capacity() - buf.len(); - if remaining < LW_BUFFER_SIZE { - buf.reserve(HW_BUFFER_SIZE - remaining); - } - - match actix_codec::poll_read_buf(Pin::new(io), cx, buf) { - Poll::Pending => { - return if read_some { Ok(Some(false)) } else { Ok(None) }; - } - Poll::Ready(Ok(n)) => { - if n == 0 { - return Ok(Some(true)); - } else { - // If buf is full return but do not disconnect since - // there is more reading to be done - if buf.len() >= super::decoder::MAX_BUFFER_SIZE { - // at this point it's not known io is still scheduled to - // be waked up. so force wake up dispatcher just in case. - // TODO: figure out the overhead. - cx.waker().wake_by_ref(); - return Ok(Some(false)); - } - - read_some = true; - } - } - Poll::Ready(Err(err)) => { - return if err.kind() == io::ErrorKind::WouldBlock { - if read_some { - Ok(Some(false)) - } else { - Ok(None) - } - } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(Some(true)) - } else { - Err(err) - } - } - } - } -} - #[cfg(test)] mod tests { use std::str; From dddb623a11f23c96af45e295ac479d66374e3897 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 7 Feb 2021 15:47:51 -0800 Subject: [PATCH 145/187] add services register for tuple and vec of services (#1933) --- CHANGES.md | 3 ++ src/service.rs | 142 +++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 145 insertions(+) diff --git a/CHANGES.md b/CHANGES.md index e9a788950..9a3b7fe3e 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -4,6 +4,8 @@ ### Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] +* Add `services!` macro for helping register multiple services to `App`. [#1933] +* Enable registering vector of same type of `HttpServiceFactory` to `App` [#1933] ### Changed * Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. @@ -34,6 +36,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1905]: https://github.com/actix/actix-web/pull/1905 [#1906]: https://github.com/actix/actix-web/pull/1906 +[#1933]: https://github.com/actix/actix-web/pull/1933 [#1957]: https://github.com/actix/actix-web/pull/1957 diff --git a/src/service.rs b/src/service.rs index 86949dca6..db0ec602a 100644 --- a/src/service.rs +++ b/src/service.rs @@ -22,6 +22,13 @@ pub trait HttpServiceFactory { fn register(self, config: &mut AppService); } +impl HttpServiceFactory for Vec { + fn register(self, config: &mut AppService) { + self.into_iter() + .for_each(|factory| factory.register(config)); + } +} + pub(crate) trait AppServiceFactory { fn register(&mut self, config: &mut AppService); } @@ -532,6 +539,65 @@ where } } +/// Macro helping register different types of services at the sametime. +/// +/// The service type must be implementing [`HttpServiceFactory`](self::HttpServiceFactory) trait. +/// +/// The max number of services can be grouped together is 12. +/// +/// # Examples +/// +/// ``` +/// use actix_web::{services, web, App}; +/// +/// let services = services![ +/// web::resource("/test2").to(|| async { "test2" }), +/// web::scope("/test3").route("/", web::get().to(|| async { "test3" })) +/// ]; +/// +/// let app = App::new().service(services); +/// +/// // services macro just convert multiple services to a tuple. +/// // below would also work without importing the macro. +/// let app = App::new().service(( +/// web::resource("/test2").to(|| async { "test2" }), +/// web::scope("/test3").route("/", web::get().to(|| async { "test3" })) +/// )); +/// ``` +#[macro_export] +macro_rules! services { + ($($x:expr),+ $(,)?) => { + ($($x,)+) + } +} + +/// HttpServiceFactory trait impl for tuples +macro_rules! service_tuple ({ $(($n:tt, $T:ident)),+} => { + impl<$($T: HttpServiceFactory),+> HttpServiceFactory for ($($T,)+) { + fn register(self, config: &mut AppService) { + $(self.$n.register(config);)+ + } + } +}); + +#[rustfmt::skip] +mod m { + use super::*; + + service_tuple!((0, A)); + service_tuple!((0, A), (1, B)); + service_tuple!((0, A), (1, B), (2, C)); + service_tuple!((0, A), (1, B), (2, C), (3, D)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K)); + service_tuple!((0, A), (1, B), (2, C), (3, D), (4, E), (5, F), (6, G), (7, H), (8, I), (9, J), (10, K), (11, L)); +} + #[cfg(test)] mod tests { use super::*; @@ -606,4 +672,80 @@ mod tests { assert!(s.contains("ServiceResponse")); assert!(s.contains("x-test")); } + + #[actix_rt::test] + async fn test_services_macro() { + let scoped = services![ + web::service("/scoped_test1").name("scoped_test1").finish( + |req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::Ok().finish())) + } + ), + web::resource("/scoped_test2").to(|| async { "test2" }), + ]; + + let services = services![ + web::service("/test1") + .name("test") + .finish(|req: ServiceRequest| async { + Ok(req.into_response(HttpResponse::Ok().finish())) + }), + web::resource("/test2").to(|| async { "test2" }), + web::scope("/test3").service(scoped) + ]; + + let srv = init_service(App::new().service(services)).await; + + let req = TestRequest::with_uri("/test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + } + + #[actix_rt::test] + async fn test_services_vec() { + let services = vec![ + web::resource("/test1").to(|| async { "test1" }), + web::resource("/test2").to(|| async { "test2" }), + ]; + + let scoped = vec![ + web::resource("/scoped_test1").to(|| async { "test1" }), + web::resource("/scoped_test2").to(|| async { "test2" }), + ]; + + let srv = init_service( + App::new() + .service(services) + .service(web::scope("/test3").service(scoped)), + ) + .await; + + let req = TestRequest::with_uri("/test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test1").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + + let req = TestRequest::with_uri("/test3/scoped_test2").to_request(); + let resp = srv.call(req).await.unwrap(); + assert_eq!(resp.status(), http::StatusCode::OK); + } } From 519d7f2b8ab323b34071c8eedce03f89e4fde6d0 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 9 Feb 2021 02:41:20 -0800 Subject: [PATCH 146/187] add trust-dns optional feature for actix-http and awc (#1969) --- Cargo.toml | 1 - actix-http/CHANGES.md | 4 ++ actix-http/Cargo.toml | 7 +-- actix-http/src/client/connector.rs | 84 ++++++++++++++++++++++++++++-- actix-http/src/error.rs | 6 --- awc/CHANGES.md | 2 + awc/Cargo.toml | 3 ++ src/test.rs | 6 ++- 8 files changed, 98 insertions(+), 15 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 479a89f87..80187c8a5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -109,7 +109,6 @@ smallvec = "1.6" [dev-dependencies] actix = { version = "0.11.0-beta.2", default-features = false } -actix-http = { version = "3.0.0-beta.1", features = ["actors"] } rand = "0.8" env_logger = "0.8" serde_derive = "1.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 52980fb5d..ba8cc6e6d 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,7 @@ * `ResponseBuilder::append_header` method which allows using typed headers. [#1869] * `TestRequest::insert_header` method which allows using typed headers. [#1869] * `ContentEncoding` implements all necessary header traits. [#1912] +* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed * `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed @@ -26,6 +27,8 @@ * `ResponseBuilder::header`; use `ResponseBuilder::append_header`. [#1869] * `TestRequest::with_hdr`; use `TestRequest::default().insert_header()`. [#1869] * `TestRequest::with_header`; use `TestRequest::default().insert_header()`. [#1869] +* `actors` optional feature. [#1969] +* `ResponseError` impl for `actix::MailboxError`. [#1969] [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 @@ -34,6 +37,7 @@ [#1905]: https://github.com/actix/actix-web/pull/1905 [#1912]: https://github.com/actix/actix-web/pull/1912 [#1957]: https://github.com/actix/actix-web/pull/1957 +[#1969]: https://github.com/actix/actix-web/pull/1969 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 22a54f569..9f62639f8 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -36,8 +36,8 @@ compress = ["flate2", "brotli2"] # support for secure cookies secure-cookies = ["cookie/secure"] -# support for actix Actor messages -actors = ["actix"] +# trust-dns as client dns resolver +trust-dns = ["trust-dns-resolver"] [dependencies] actix-service = "2.0.0-beta.4" @@ -45,7 +45,6 @@ actix-codec = "0.4.0-beta.1" actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-tls = "3.0.0-beta.2" -actix = { version = "0.11.0-beta.2", default-features = false, optional = true } base64 = "0.13" bitflags = "1.2" @@ -84,6 +83,8 @@ time = { version = "0.2.23", default-features = false, features = ["std"] } brotli2 = { version="0.3.2", optional = true } flate2 = { version = "1.0.13", optional = true } +trust-dns-resolver = { version = "0.20.0", optional = true } + [dev-dependencies] actix-server = "2.0.0-beta.3" actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 425ee0f70..7c8e2b2a0 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -6,7 +6,7 @@ use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::net::TcpStream; use actix_service::{apply_fn, Service, ServiceExt}; use actix_tls::connect::{ - default_connector, Connect as TcpConnect, Connection as TcpConnection, + new_connector, Connect as TcpConnect, Connection as TcpConnection, Resolver, }; use actix_utils::timeout::{TimeoutError, TimeoutService}; use http::Uri; @@ -19,7 +19,6 @@ use super::Connect; #[cfg(feature = "openssl")] use actix_tls::connect::ssl::openssl::SslConnector as OpensslConnector; - #[cfg(feature = "rustls")] use actix_tls::connect::ssl::rustls::ClientConfig; #[cfg(feature = "rustls")] @@ -70,7 +69,7 @@ impl Connector<(), ()> { > { Connector { ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]), - connector: default_connector(), + connector: new_connector(resolver::resolver()), config: ConnectorConfig::default(), _phantom: PhantomData, } @@ -532,3 +531,82 @@ mod connect_impl { } } } + +#[cfg(not(feature = "trust-dns"))] +mod resolver { + use super::*; + + pub(super) fn resolver() -> Resolver { + Resolver::Default + } +} + +#[cfg(feature = "trust-dns")] +mod resolver { + use std::{cell::RefCell, net::SocketAddr}; + + use actix_tls::connect::Resolve; + use futures_core::future::LocalBoxFuture; + use trust_dns_resolver::{ + config::{ResolverConfig, ResolverOpts}, + system_conf::read_system_conf, + TokioAsyncResolver, + }; + + use super::*; + + pub(super) fn resolver() -> Resolver { + // new type for impl Resolve trait for TokioAsyncResolver. + struct TrustDnsResolver(TokioAsyncResolver); + + impl Resolve for TrustDnsResolver { + fn lookup<'a>( + &'a self, + host: &'a str, + port: u16, + ) -> LocalBoxFuture<'a, Result, Box>> + { + Box::pin(async move { + let res = self + .0 + .lookup_ip(host) + .await? + .iter() + .map(|ip| SocketAddr::new(ip, port)) + .collect(); + Ok(res) + }) + } + } + + // dns struct is cached in thread local. + // so new client constructor can reuse the existing dns resolver. + thread_local! { + static TRUST_DNS_RESOLVER: RefCell> = RefCell::new(None); + } + + // get from thread local or construct a new trust-dns resolver. + TRUST_DNS_RESOLVER.with(|local| { + let resolver = local.borrow().as_ref().map(Clone::clone); + match resolver { + Some(resolver) => resolver, + None => { + let (cfg, opts) = match read_system_conf() { + Ok((cfg, opts)) => (cfg, opts), + Err(e) => { + log::error!("TRust-DNS can not load system config: {}", e); + (ResolverConfig::default(), ResolverOpts::default()) + } + }; + + let resolver = TokioAsyncResolver::tokio(cfg, opts).unwrap(); + + // box trust dns resolver and put it in thread local. + let resolver = Resolver::new_custom(TrustDnsResolver(resolver)); + *local.borrow_mut() = Some(resolver.clone()); + resolver + } + } + }) + } +} diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 7beedc3ba..cea1f5d4f 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -968,12 +968,6 @@ where InternalError::new(err, StatusCode::NETWORK_AUTHENTICATION_REQUIRED).into() } -#[cfg(feature = "actors")] -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`actix::MailboxError`]. -/// -/// This is only supported when the feature `actors` is enabled. -impl ResponseError for actix::MailboxError {} - #[cfg(test)] mod tests { use super::*; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 16ec7ad1a..80895f46d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -4,6 +4,7 @@ ### Added * `ClientRequest::insert_header` method which allows using typed headers. [#1869] * `ClientRequest::append_header` method which allows using typed headers. [#1869] +* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed * Relax default timeout for `Connector` to 5 seconds(original 1 second). [#1905] @@ -16,6 +17,7 @@ [#1869]: https://github.com/actix/actix-web/pull/1869 [#1905]: https://github.com/actix/actix-web/pull/1905 +[#1969]: https://github.com/actix/actix-web/pull/1969 ## 3.0.0-beta.1 - 2021-01-07 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 7a9130780..483e04968 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -36,6 +36,9 @@ rustls = ["tls-rustls", "actix-http/rustls"] # content-encoding support compress = ["actix-http/compress"] +# trust-dns as dns resolver +trust-dns = ["actix-http/trust-dns"] + [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" diff --git a/src/test.rs b/src/test.rs index 62c329c91..5da100b81 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1277,8 +1277,10 @@ mod tests { async fn actor_handler( addr: Data>, ) -> Result { - // `?` operator tests "actors" feature flag on actix-http - let res = addr.send(Num(1)).await?; + let res = addr + .send(Num(1)) + .await + .map_err(crate::error::ErrorInternalServerError)?; if res == 1 { Ok(HttpResponse::Ok()) From a6ed4aee842858b3afb78a5d6830c77fa438d694 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 9 Feb 2021 14:32:46 -0800 Subject: [PATCH 147/187] add poll_flush after a non blocked write to h1 dispatcher (#1971) --- actix-http/src/h1/dispatcher.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index f8fc95921..8fbf3ede0 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -286,15 +286,12 @@ where self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - let len = self.write_buf.len(); - if len == 0 { - return Ok(false); - } - let InnerDispatcherProj { io, write_buf, .. } = self.project(); let mut io = Pin::new(io.as_mut().unwrap()); + let len = write_buf.len(); let mut written = 0; + while written < len { match io.as_mut().poll_write(cx, &write_buf[written..]) { Poll::Ready(Ok(0)) => { @@ -314,9 +311,14 @@ where // SAFETY: setting length to 0 is safe // skips one length check vs truncate - unsafe { write_buf.set_len(0) } + unsafe { + write_buf.set_len(0); + } - Ok(false) + // flush the io and check if get blocked. + let blocked = io.poll_flush(cx)?.is_pending(); + + Ok(blocked) } fn send_response( From 949d14ae2b78ca7d9d81af27d216b0cb4d3bcd7e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 9 Feb 2021 22:59:17 +0000 Subject: [PATCH 148/187] clean up header map (#1964) --- actix-http/CHANGES.md | 10 + actix-http/Cargo.toml | 1 - actix-http/src/client/h1proto.rs | 4 +- actix-http/src/h1/decoder.rs | 4 +- actix-http/src/h1/encoder.rs | 130 ++-- actix-http/src/header/as_name.rs | 46 ++ actix-http/src/header/map.rs | 1110 ++++++++++++++++++++++-------- actix-http/src/header/mod.rs | 18 +- actix-http/src/request.rs | 6 +- actix-http/src/response.rs | 22 +- awc/src/frozen.rs | 4 +- awc/src/request.rs | 8 +- awc/src/sender.rs | 4 +- src/request.rs | 8 +- 14 files changed, 982 insertions(+), 393 deletions(-) create mode 100644 actix-http/src/header/as_name.rs diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ba8cc6e6d..bef17fb08 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -7,6 +7,9 @@ * `ResponseBuilder::append_header` method which allows using typed headers. [#1869] * `TestRequest::insert_header` method which allows using typed headers. [#1869] * `ContentEncoding` implements all necessary header traits. [#1912] +* `HeaderMap::len_keys` has the behavior of the old `len` method. [#1964] +* `HeaderMap::drain` as an efficient draining iterator. [#1964] +* Implement `IntoIterator` for owned `HeaderMap`. [#1964] * `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] ### Changed @@ -20,6 +23,9 @@ * `client::error::ConnectError` Resolver variant contains `Box` type [#1905] * `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] * Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] +* `HeaderMap::len` now returns number of values instead of number of keys. [#1964] +* `HeaderMap::insert` now returns iterator of removed values. [#1964] +* `HeaderMap::remove` now returns iterator of removed values. [#1964] ### Removed * `ResponseBuilder::set`; use `ResponseBuilder::insert_header`. [#1869] @@ -30,6 +36,9 @@ * `actors` optional feature. [#1969] * `ResponseError` impl for `actix::MailboxError`. [#1969] +### Documentation +* Vastly improve docs and add examples for `HeaderMap`. [#1964] + [#1869]: https://github.com/actix/actix-web/pull/1869 [#1894]: https://github.com/actix/actix-web/pull/1894 [#1903]: https://github.com/actix/actix-web/pull/1903 @@ -37,6 +46,7 @@ [#1905]: https://github.com/actix/actix-web/pull/1905 [#1912]: https://github.com/actix/actix-web/pull/1912 [#1957]: https://github.com/actix/actix-web/pull/1957 +[#1964]: https://github.com/actix/actix-web/pull/1964 [#1969]: https://github.com/actix/actix-web/pull/1969 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 9f62639f8..e974e61df 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -52,7 +52,6 @@ bytes = "1" bytestring = "1" cookie = { version = "0.14.1", features = ["percent-encode"] } derive_more = "0.99.5" -either = "1.5.3" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 24f4207e8..92c3c0e1b 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -48,11 +48,11 @@ where match wrt.get_mut().split().freeze().try_into_value() { Ok(value) => match head { RequestHeadType::Owned(ref mut head) => { - head.headers.insert(HOST, value) + head.headers.insert(HOST, value); } RequestHeadType::Rc(_, ref mut extra_headers) => { let headers = extra_headers.get_or_insert(HeaderMap::new()); - headers.insert(HOST, value) + headers.insert(HOST, value); } }, Err(e) => log::error!("Can not set HOST header {}", e), diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 9da958563..122a815d5 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -830,8 +830,8 @@ mod tests { .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) .collect(); - assert_eq!(val[1], "c1=cookie1"); - assert_eq!(val[0], "c2=cookie2"); + assert_eq!(val[0], "c1=cookie1"); + assert_eq!(val[1], "c2=cookie2"); } #[test] diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index bd8287b26..2932a7dce 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -144,104 +144,54 @@ pub(crate) trait MessageType: Sized { let k = key.as_str().as_bytes(); let k_len = k.len(); - match value { - Value::One(ref val) => { - let v = val.as_ref(); - let v_len = v.len(); + // TODO: drain? + for val in value.iter() { + let v = val.as_ref(); + let v_len = v.len(); - // key length + value length + colon + space + \r\n - let len = k_len + v_len + 4; + // key length + value length + colon + space + \r\n + let len = k_len + v_len + 4; - if len > remaining { - // not enough room in buffer for this header; reserve more space - - // SAFETY: all the bytes written up to position "pos" are initialized - // the written byte count and pointer advancement are kept in sync - unsafe { - dst.advance_mut(pos); - } - - pos = 0; - dst.reserve(len * 2); - remaining = dst.capacity() - dst.len(); - - // re-assign buf raw pointer since it's possible that the buffer was - // reallocated and/or resized - buf = dst.chunk_mut().as_mut_ptr(); - } - - // SAFETY: on each write, it is enough to ensure that the advancement of the - // cursor matches the number of bytes written + if len > remaining { + // SAFETY: all the bytes written up to position "pos" are initialized + // the written byte count and pointer advancement are kept in sync unsafe { - // use upper Camel-Case - if camel_case { - write_camel_case(k, from_raw_parts_mut(buf, k_len)) - } else { - write_data(k, buf, k_len) - } - - buf = buf.add(k_len); - - write_data(b": ", buf, 2); - buf = buf.add(2); - - write_data(v, buf, v_len); - buf = buf.add(v_len); - - write_data(b"\r\n", buf, 2); - buf = buf.add(2); + dst.advance_mut(pos); } - pos += len; - remaining -= len; + pos = 0; + dst.reserve(len * 2); + remaining = dst.capacity() - dst.len(); + + // re-assign buf raw pointer since it's possible that the buffer was + // reallocated and/or resized + buf = dst.chunk_mut().as_mut_ptr(); } - Value::Multi(ref vec) => { - for val in vec { - let v = val.as_ref(); - let v_len = v.len(); - let len = k_len + v_len + 4; - - if len > remaining { - // SAFETY: all the bytes written up to position "pos" are initialized - // the written byte count and pointer advancement are kept in sync - unsafe { - dst.advance_mut(pos); - } - pos = 0; - dst.reserve(len * 2); - remaining = dst.capacity() - dst.len(); - - // re-assign buf raw pointer since it's possible that the buffer was - // reallocated and/or resized - buf = dst.chunk_mut().as_mut_ptr(); - } - - // SAFETY: on each write, it is enough to ensure that the advancement of - // the cursor matches the number of bytes written - unsafe { - if camel_case { - write_camel_case(k, from_raw_parts_mut(buf, k_len)); - } else { - write_data(k, buf, k_len); - } - - buf = buf.add(k_len); - - write_data(b": ", buf, 2); - buf = buf.add(2); - - write_data(v, buf, v_len); - buf = buf.add(v_len); - - write_data(b"\r\n", buf, 2); - buf = buf.add(2); - }; - - pos += len; - remaining -= len; + // SAFETY: on each write, it is enough to ensure that the advancement of + // the cursor matches the number of bytes written + unsafe { + if camel_case { + // use Camel-Case headers + write_camel_case(k, from_raw_parts_mut(buf, k_len)); + } else { + write_data(k, buf, k_len); } - } + + buf = buf.add(k_len); + + write_data(b": ", buf, 2); + buf = buf.add(2); + + write_data(v, buf, v_len); + buf = buf.add(v_len); + + write_data(b"\r\n", buf, 2); + buf = buf.add(2); + }; + + pos += len; + remaining -= len; } }); diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs new file mode 100644 index 000000000..68456e3f7 --- /dev/null +++ b/actix-http/src/header/as_name.rs @@ -0,0 +1,46 @@ +//! Helper trait for types that can be effectively borrowed as a [HeaderValue]. + +use std::{borrow::Cow, str::FromStr}; + +use http::header::{HeaderName, InvalidHeaderName}; + +pub trait AsHeaderName: Sealed {} + +pub trait Sealed { + fn try_as_name(&self) -> Result, InvalidHeaderName>; +} + +impl Sealed for HeaderName { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + Ok(Cow::Borrowed(self)) + } +} +impl AsHeaderName for HeaderName {} + +impl Sealed for &HeaderName { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + Ok(Cow::Borrowed(*self)) + } +} +impl AsHeaderName for &HeaderName {} + +impl Sealed for &str { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for &str {} + +impl Sealed for String { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for String {} + +impl Sealed for &String { + fn try_as_name(&self) -> Result, InvalidHeaderName> { + HeaderName::from_str(self).map(Cow::Owned) + } +} +impl AsHeaderName for &String {} diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs index 8f20f3e6f..106e44edb 100644 --- a/actix-http/src/header/map.rs +++ b/actix-http/src/header/map.rs @@ -1,332 +1,563 @@ -use std::{ - collections::hash_map::{self, Entry}, - convert::TryFrom, -}; +//! A multi-value [`HeaderMap`] and its iterators. + +use std::{borrow::Cow, collections::hash_map, ops}; use ahash::AHashMap; -use either::Either; use http::header::{HeaderName, HeaderValue}; use smallvec::{smallvec, SmallVec}; +use crate::header::AsHeaderName; + /// A multi-map of HTTP headers. /// -/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more values. +/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more [`HeaderValue`]s. +/// +/// # Examples +/// ``` +/// use actix_http::http::{header, HeaderMap, HeaderValue}; +/// +/// let mut map = HeaderMap::new(); +/// +/// map.insert(header::CONTENT_TYPE, HeaderValue::from_static("text/plain")); +/// map.insert(header::ORIGIN, HeaderValue::from_static("example.com")); +/// +/// assert!(map.contains_key(header::CONTENT_TYPE)); +/// assert!(map.contains_key(header::ORIGIN)); +/// +/// let mut removed = map.remove(header::ORIGIN); +/// assert_eq!(removed.next().unwrap(), "example.com"); +/// +/// assert!(!map.contains_key(header::ORIGIN)); +/// ``` #[derive(Debug, Clone, Default)] pub struct HeaderMap { pub(crate) inner: AHashMap, } +/// A bespoke non-empty list for HeaderMap values. #[derive(Debug, Clone)] -pub(crate) enum Value { - One(HeaderValue), - Multi(SmallVec<[HeaderValue; 4]>), +pub(crate) struct Value { + inner: SmallVec<[HeaderValue; 4]>, } impl Value { - fn first(&self) -> &HeaderValue { - match self { - Value::One(ref val) => val, - Value::Multi(ref val) => &val[0], + fn one(val: HeaderValue) -> Self { + Self { + inner: smallvec![val], } } + fn first(&self) -> &HeaderValue { + &self.inner[0] + } + fn first_mut(&mut self) -> &mut HeaderValue { - match self { - Value::One(ref mut val) => val, - Value::Multi(ref mut val) => &mut val[0], - } + &mut self.inner[0] } - fn append(&mut self, val: HeaderValue) { - match self { - Value::One(_) => { - let data = std::mem::replace(self, Value::Multi(smallvec![val])); - match data { - Value::One(val) => self.append(val), - Value::Multi(_) => unreachable!(), - } - } - Value::Multi(ref mut vec) => vec.push(val), - } + fn append(&mut self, new_val: HeaderValue) { + self.inner.push(new_val) + } +} + +impl ops::Deref for Value { + type Target = SmallVec<[HeaderValue; 4]>; + + fn deref(&self) -> &Self::Target { + &self.inner } } impl HeaderMap { /// Create an empty `HeaderMap`. /// - /// The map will be created without any capacity. This function will not - /// allocate. + /// The map will be created without any capacity; this function will not allocate. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::new(); + /// + /// assert!(map.is_empty()); + /// assert_eq!(0, map.capacity()); + /// ``` pub fn new() -> Self { - HeaderMap { - inner: AHashMap::default(), - } + HeaderMap::default() } /// Create an empty `HeaderMap` with the specified capacity. /// - /// The returned map will allocate internal storage in order to hold about - /// `capacity` elements without reallocating. However, this is a "best - /// effort" as there are usage patterns that could cause additional - /// allocations before `capacity` headers are stored in the map. + /// The map will be able to hold at least `capacity` elements without needing to reallocate. + /// If `capacity` is 0, the map will be created without allocating. /// - /// More capacity than requested may be allocated. - pub fn with_capacity(capacity: usize) -> HeaderMap { + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::with_capacity(16); + /// + /// assert!(map.is_empty()); + /// assert!(map.capacity() >= 16); + /// ``` + pub fn with_capacity(capacity: usize) -> Self { HeaderMap { - inner: AHashMap::with_capacity_and_hasher(capacity, Default::default()), + inner: AHashMap::with_capacity(capacity), } } - /// Returns the number of keys stored in the map. + /// Create new `HeaderMap` from a `http::HeaderMap`-like drain. + pub(crate) fn from_drain(mut drain: I) -> Self + where + I: Iterator, HeaderValue)>, + { + let (first_name, first_value) = match drain.next() { + None => return HeaderMap::new(), + Some((name, val)) => { + let name = name.expect("drained first item had no name"); + (name, val) + } + }; + + let (lb, ub) = drain.size_hint(); + let capacity = ub.unwrap_or(lb); + + let mut map = HeaderMap::with_capacity(capacity); + map.append(first_name.clone(), first_value); + + let (map, _) = + drain.fold((map, first_name), |(mut map, prev_name), (name, value)| { + let name = name.unwrap_or(prev_name); + map.append(name.clone(), value); + (map, name) + }); + + map + } + + /// Returns the number of values stored in the map. /// - /// This number could be be less than or equal to actual headers stored in - /// the map. + /// See also: [`len_keys`](Self::len_keys). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert_eq!(map.len(), 0); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len(), 2); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.len(), 3); + /// ``` pub fn len(&self) -> usize { + self.inner + .iter() + .fold(0, |acc, (_, values)| acc + values.len()) + } + + /// Returns the number of _keys_ stored in the map. + /// + /// The number of values stored will be at least this number. See also: [`Self::len`]. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert_eq!(map.len_keys(), 0); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len_keys(), 2); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.len_keys(), 2); + /// ``` + pub fn len_keys(&self) -> usize { self.inner.len() } /// Returns true if the map contains no elements. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert!(map.is_empty()); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(!map.is_empty()); + /// ``` pub fn is_empty(&self) -> bool { self.inner.len() == 0 } - /// Clears the map, removing all key-value pairs. Keeps the allocated memory - /// for reuse. + /// Clears the map, removing all name-value pairs. + /// + /// Keeps the allocated memory for reuse. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// assert_eq!(map.len(), 2); + /// + /// map.clear(); + /// assert!(map.is_empty()); + /// ``` pub fn clear(&mut self) { self.inner.clear(); } - /// Returns the number of headers the map can hold without reallocating. + fn get_value(&self, key: impl AsHeaderName) -> Option<&Value> { + match key.try_as_name().ok()? { + Cow::Borrowed(name) => self.inner.get(name), + Cow::Owned(name) => self.inner.get(&name), + } + } + + /// Returns a reference to the _first_ value associated with a header name. /// - /// This number is an approximation as certain usage patterns could cause - /// additional allocations before the returned capacity is filled. + /// Returns `None` if there is no value associated with the key. + /// + /// Even when multiple values are associated with the key, the "first" one is returned but is + /// not guaranteed to be chosen with any particular order; though, the returned item will be + /// consistent for each call to `get` if the map has not changed. + /// + /// See also: [`get_all`](Self::get_all). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// + /// let cookie = map.get(header::SET_COOKIE).unwrap(); + /// assert_eq!(cookie, "one=1"); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// assert_eq!(map.get(header::SET_COOKIE).unwrap(), "one=1"); + /// + /// assert_eq!(map.get(header::SET_COOKIE), map.get("set-cookie")); + /// assert_eq!(map.get(header::SET_COOKIE), map.get("Set-Cookie")); + /// + /// assert!(map.get(header::HOST).is_none()); + /// assert!(map.get("INVALID HEADER NAME").is_none()); + /// ``` + pub fn get(&self, key: impl AsHeaderName) -> Option<&HeaderValue> { + self.get_value(key).map(|val| val.first()) + } + + /// Returns a mutable reference to the _first_ value associated a header name. + /// + /// Returns `None` if there is no value associated with the key. + /// + /// Even when multiple values are associated with the key, the "first" one is returned but is + /// not guaranteed to be chosen with any particular order; though, the returned item will be + /// consistent for each call to `get_mut` if the map has not changed. + /// + /// See also: [`get_all`](Self::get_all). + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// + /// let mut cookie = map.get_mut(header::SET_COOKIE).unwrap(); + /// assert_eq!(cookie, "one=1"); + /// + /// *cookie = HeaderValue::from_static("three=3"); + /// assert_eq!(map.get(header::SET_COOKIE).unwrap(), "three=3"); + /// + /// assert!(map.get(header::HOST).is_none()); + /// assert!(map.get("INVALID HEADER NAME").is_none()); + /// ``` + pub fn get_mut(&mut self, key: impl AsHeaderName) -> Option<&mut HeaderValue> { + match key.try_as_name().ok()? { + Cow::Borrowed(name) => self.inner.get_mut(name).map(|v| v.first_mut()), + Cow::Owned(name) => self.inner.get_mut(&name).map(|v| v.first_mut()), + } + } + + /// Returns an iterator over all values associated with a header name. + /// + /// The returned iterator does not incur any allocations and will yield no items if there are no + /// values associated with the key. Iteration order is **not** guaranteed to be the same as + /// insertion order. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let mut none_iter = map.get_all(header::ORIGIN); + /// assert!(none_iter.next().is_none()); + /// + /// map.insert(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let mut set_cookies_iter = map.get_all(header::SET_COOKIE); + /// assert_eq!(set_cookies_iter.next().unwrap(), "one=1"); + /// assert_eq!(set_cookies_iter.next().unwrap(), "two=2"); + /// assert!(set_cookies_iter.next().is_none()); + /// ``` + pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> { + GetAll::new(self.get_value(key)) + } + + // TODO: get_all_mut ? + + /// Returns `true` if the map contains a value for the specified key. + /// + /// Invalid header names will simply return false. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// assert!(!map.contains_key(header::ACCEPT)); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(map.contains_key(header::ACCEPT)); + /// ``` + pub fn contains_key(&self, key: impl AsHeaderName) -> bool { + match key.try_as_name() { + Ok(Cow::Borrowed(name)) => self.inner.contains_key(name), + Ok(Cow::Owned(name)) => self.inner.contains_key(&name), + Err(_) => false, + } + } + + /// Inserts a name-value pair into the map. + /// + /// If the map already contained this key, the new value is associated with the key and all + /// previous values are removed and returned as a `Removed` iterator. The key is not updated; + /// this matters for types that can be `==` without being identical. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.insert(header::ACCEPT, HeaderValue::from_static("text/plain")); + /// assert!(map.contains_key(header::ACCEPT)); + /// assert_eq!(map.len(), 1); + /// + /// let mut removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/csv")); + /// assert_eq!(removed.next().unwrap(), "text/plain"); + /// assert!(removed.next().is_none()); + /// + /// assert_eq!(map.len(), 1); + /// ``` + pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed { + let value = self.inner.insert(key, Value::one(val)); + Removed::new(value) + } + + /// Inserts a name-value pair into the map. + /// + /// If the map already contained this key, the new value is added to the list of values + /// currently associated with the key. The key is not updated; this matters for types that can + /// be `==` without being identical. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.append(header::HOST, HeaderValue::from_static("example.com")); + /// assert_eq!(map.len(), 1); + /// + /// map.append(header::ACCEPT, HeaderValue::from_static("text/csv")); + /// assert_eq!(map.len(), 2); + /// + /// map.append(header::ACCEPT, HeaderValue::from_static("text/html")); + /// assert_eq!(map.len(), 3); + /// ``` + pub fn append(&mut self, key: HeaderName, value: HeaderValue) { + match self.inner.entry(key) { + hash_map::Entry::Occupied(mut entry) => { + entry.get_mut().append(value); + } + hash_map::Entry::Vacant(entry) => { + entry.insert(Value::one(value)); + } + }; + } + + /// Removes all headers for a particular header name from the map. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=2")); + /// + /// assert_eq!(map.len(), 2); + /// + /// let mut removed = map.remove(header::SET_COOKIE); + /// assert_eq!(removed.next().unwrap(), "one=1"); + /// assert_eq!(removed.next().unwrap(), "one=2"); + /// assert!(removed.next().is_none()); + /// + /// assert!(map.is_empty()); + pub fn remove(&mut self, key: impl AsHeaderName) -> Removed { + let value = match key.try_as_name() { + Ok(Cow::Borrowed(name)) => self.inner.remove(name), + Ok(Cow::Owned(name)) => self.inner.remove(&name), + Err(_) => None, + }; + + Removed::new(value) + } + + /// Returns the number of single-value headers the map can hold without needing to reallocate. + /// + /// Since this is a multi-value map, the actual capacity is much larger when considering + /// each header name can be associated with an arbitrary number of values. The effect is that + /// the size of `len` may be greater than `capacity` since it counts all the values. + /// Conversely, [`len_keys`](Self::len_keys) will never be larger than capacity. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let map = HeaderMap::with_capacity(16); + /// + /// assert!(map.is_empty()); + /// assert!(map.capacity() >= 16); + /// ``` pub fn capacity(&self) -> usize { self.inner.capacity() } - /// Reserves capacity for at least `additional` more headers to be inserted - /// into the `HeaderMap`. + /// Reserves capacity for at least `additional` more headers to be inserted in the map. /// - /// The header map may reserve more space to avoid frequent reallocations. - /// Like with `with_capacity`, this will be a "best effort" to avoid - /// allocations until `additional` more headers are inserted. Certain usage - /// patterns could cause additional allocations before the number is - /// reached. + /// The header map may reserve more space to avoid frequent reallocations. Additional capacity + /// only considers single-value headers. + /// + /// # Panics + /// Panics if the new allocation size overflows usize. + /// + /// # Examples + /// ``` + /// # use actix_http::http::HeaderMap; + /// let mut map = HeaderMap::with_capacity(2); + /// assert!(map.capacity() >= 2); + /// + /// map.reserve(100); + /// assert!(map.capacity() >= 102); + /// + /// assert!(map.is_empty()); + /// ``` pub fn reserve(&mut self, additional: usize) { self.inner.reserve(additional) } - /// Returns a reference to the value associated with the key. + /// An iterator over all name-value pairs. /// - /// If there are multiple values associated with the key, then the first one - /// is returned. Use `get_all` to get all values associated with a given - /// key. Returns `None` if there are no values associated with the key. - pub fn get(&self, name: N) -> Option<&HeaderValue> { - self.get2(name).map(|v| v.first()) - } - - fn get2(&self, name: N) -> Option<&Value> { - match name.as_name() { - Either::Left(name) => self.inner.get(name), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.get(&name) - } else { - None - } - } - } - } - - /// Returns an iterator of all values associated with a key. + /// Names will be yielded for each associated value. So, if a key has 3 associated values, it + /// will be yielded 3 times. The iteration order should be considered arbitrary. /// - /// The returned view does not incur any allocations and allows iterating the values associated - /// with the key. Returns `None` if there are no values associated with the key. Iteration order - /// is not guaranteed to be the same as insertion order. - pub fn get_all(&self, name: N) -> GetAll<'_> { - GetAll { - idx: 0, - item: self.get2(name), - } - } - - /// Returns a mutable reference to the value associated with the key. + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); /// - /// If there are multiple values associated with the key, then the first one - /// is returned. Use `entry` to get all values associated with a given - /// key. Returns `None` if there are no values associated with the key. - pub fn get_mut(&mut self, name: N) -> Option<&mut HeaderValue> { - match name.as_name() { - Either::Left(name) => self.inner.get_mut(name).map(|v| v.first_mut()), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.get_mut(&name).map(|v| v.first_mut()) - } else { - None - } - } - } - } - - /// Returns true if the map contains a value for the specified key. - pub fn contains_key(&self, key: N) -> bool { - match key.as_name() { - Either::Left(name) => self.inner.contains_key(name), - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - self.inner.contains_key(&name) - } else { - false - } - } - } - } - - /// An iterator visiting all key-value pairs. + /// let mut iter = map.iter(); + /// assert!(iter.next().is_none()); /// - /// The iteration order is arbitrary, but consistent across platforms for - /// the same crate version. Each key will be yielded once per associated - /// value. So, if a key has 3 associated values, it will be yielded 3 times. + /// map.append(header::HOST, HeaderValue::from_static("duck.com")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let mut iter = map.iter(); + /// assert!(iter.next().is_some()); + /// assert!(iter.next().is_some()); + /// assert!(iter.next().is_some()); + /// assert!(iter.next().is_none()); + /// + /// let pairs = map.iter().collect::>(); + /// assert!(pairs.contains(&(&header::HOST, &HeaderValue::from_static("duck.com")))); + /// assert!(pairs.contains(&(&header::SET_COOKIE, &HeaderValue::from_static("one=1")))); + /// assert!(pairs.contains(&(&header::SET_COOKIE, &HeaderValue::from_static("two=2")))); + /// ``` pub fn iter(&self) -> Iter<'_> { Iter::new(self.inner.iter()) } - /// An iterator visiting all keys. + /// An iterator over all contained header names. /// - /// The iteration order is arbitrary, but consistent across platforms for - /// the same crate version. Each key will be yielded only once even if it - /// has multiple associated values. + /// Each name will only be yielded once even if it has multiple associated values. The iteration + /// order should be considered arbitrary. + /// + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); + /// + /// let mut iter = map.keys(); + /// assert!(iter.next().is_none()); + /// + /// map.append(header::HOST, HeaderValue::from_static("duck.com")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); + /// + /// let keys = map.keys().cloned().collect::>(); + /// assert_eq!(keys.len(), 2); + /// assert!(keys.contains(&header::HOST)); + /// assert!(keys.contains(&header::SET_COOKIE)); + /// ``` pub fn keys(&self) -> Keys<'_> { Keys(self.inner.keys()) } - /// Inserts a key-value pair into the map. + /// Clears the map, returning all name-value sets as an iterator. /// - /// If the map did not previously have this key present, then `None` is - /// returned. + /// Header names will only be yielded for the first value in each set. All items that are + /// yielded without a name and after an item with a name are associated with that same name. + /// The first item will always contain a name. /// - /// If the map did have this key present, the new value is associated with - /// the key and all previous values are removed. **Note** that only a single - /// one of the previous values is returned. If there are multiple values - /// that have been previously associated with the key, then the first one is - /// returned. See `insert_mult` on `OccupiedEntry` for an API that returns - /// all values. + /// Keeps the allocated memory for reuse. + /// # Examples + /// ``` + /// # use actix_http::http::{header, HeaderMap, HeaderValue}; + /// let mut map = HeaderMap::new(); /// - /// The key is not updated, though; this matters for types that can be `==` - /// without being identical. - pub fn insert(&mut self, key: HeaderName, val: HeaderValue) { - let _ = self.inner.insert(key, Value::One(val)); - } - - /// Inserts a key-value pair into the map. + /// let mut iter = map.drain(); + /// assert!(iter.next().is_none()); + /// drop(iter); /// - /// If the map did not previously have this key present, then `false` is - /// returned. + /// map.append(header::SET_COOKIE, HeaderValue::from_static("one=1")); + /// map.append(header::SET_COOKIE, HeaderValue::from_static("two=2")); /// - /// If the map did have this key present, the new value is pushed to the end - /// of the list of values currently associated with the key. The key is not - /// updated, though; this matters for types that can be `==` without being - /// identical. - pub fn append(&mut self, key: HeaderName, value: HeaderValue) { - match self.inner.entry(key) { - Entry::Occupied(mut entry) => entry.get_mut().append(value), - Entry::Vacant(entry) => { - entry.insert(Value::One(value)); - } - } - } - - /// Removes all headers for a particular header name from the map. - pub fn remove(&mut self, key: N) { - match key.as_name() { - Either::Left(name) => { - let _ = self.inner.remove(name); - } - Either::Right(s) => { - if let Ok(name) = HeaderName::try_from(s) { - let _ = self.inner.remove(&name); - } - } - } + /// let mut iter = map.drain(); + /// assert_eq!(iter.next().unwrap(), (Some(header::SET_COOKIE), HeaderValue::from_static("one=1"))); + /// assert_eq!(iter.next().unwrap(), (None, HeaderValue::from_static("two=2"))); + /// drop(iter); + /// + /// assert!(map.is_empty()); + /// ``` + pub fn drain(&mut self) -> Drain<'_> { + Drain::new(self.inner.drain()) } } -#[doc(hidden)] -pub trait AsName { - fn as_name(&self) -> Either<&HeaderName, &str>; -} - -impl AsName for HeaderName { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Left(self) - } -} - -impl<'a> AsName for &'a HeaderName { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Left(self) - } -} - -impl<'a> AsName for &'a str { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self) - } -} - -impl AsName for String { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self.as_str()) - } -} - -impl<'a> AsName for &'a String { - fn as_name(&self) -> Either<&HeaderName, &str> { - Either::Right(self.as_str()) - } -} - -/// Iterator for all values in a `HeaderMap` with the same name. -pub struct GetAll<'a> { - idx: usize, - item: Option<&'a Value>, -} - -impl<'a> Iterator for GetAll<'a> { - type Item = &'a HeaderValue; +/// Note that this implementation will clone a [HeaderName] for each value. +impl IntoIterator for HeaderMap { + type Item = (HeaderName, HeaderValue); + type IntoIter = IntoIter; #[inline] - fn next(&mut self) -> Option<&'a HeaderValue> { - if let Some(ref val) = self.item { - match val { - Value::One(ref val) => { - self.item.take(); - Some(val) - } - Value::Multi(ref vec) => { - if self.idx < vec.len() { - let item = Some(&vec[self.idx]); - self.idx += 1; - item - } else { - self.item.take(); - None - } - } - } - } else { - None - } - } -} - -pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); - -impl<'a> Iterator for Keys<'a> { - type Item = &'a HeaderName; - - #[inline] - fn next(&mut self) -> Option<&'a HeaderName> { - self.0.next() + fn into_iter(self) -> Self::IntoIter { + IntoIter::new(self.inner.into_iter()) } } @@ -334,23 +565,116 @@ impl<'a> IntoIterator for &'a HeaderMap { type Item = (&'a HeaderName, &'a HeaderValue); type IntoIter = Iter<'a>; + #[inline] fn into_iter(self) -> Self::IntoIter { - self.iter() + Iter::new(self.inner.iter()) } } -pub struct Iter<'a> { +/// Iterator for all values with the same header name. +/// +/// See [`HeaderMap::get_all`]. +#[derive(Debug)] +pub struct GetAll<'a> { idx: usize, - current: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>, - iter: hash_map::Iter<'a, HeaderName, Value>, + value: Option<&'a Value>, +} + +impl<'a> GetAll<'a> { + fn new(value: Option<&'a Value>) -> Self { + Self { idx: 0, value } + } +} + +impl<'a> Iterator for GetAll<'a> { + type Item = &'a HeaderValue; + + fn next(&mut self) -> Option { + let val = self.value?; + + match val.get(self.idx) { + Some(val) => { + self.idx += 1; + Some(val) + } + None => { + // current index is none; remove value to fast-path future next calls + self.value = None; + None + } + } + } + + fn size_hint(&self) -> (usize, Option) { + match self.value { + Some(val) => (val.len(), Some(val.len())), + None => (0, Some(0)), + } + } +} + +/// Iterator for owned [`HeaderValue`]s with the same associated [`HeaderName`] returned from methods +/// on [`HeaderMap`] that remove or replace items. +#[derive(Debug)] +pub struct Removed { + inner: Option>, +} + +impl<'a> Removed { + fn new(value: Option) -> Self { + let inner = value.map(|value| value.inner.into_iter()); + Self { inner } + } +} + +impl Iterator for Removed { + type Item = HeaderValue; + + #[inline] + fn next(&mut self) -> Option { + self.inner.as_mut()?.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + match self.inner { + Some(ref iter) => iter.size_hint(), + None => (0, None), + } + } +} + +/// Iterator over all [`HeaderName`]s in the map. +#[derive(Debug)] +pub struct Keys<'a>(hash_map::Keys<'a, HeaderName, Value>); + +impl<'a> Iterator for Keys<'a> { + type Item = &'a HeaderName; + + #[inline] + fn next(&mut self) -> Option { + self.0.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + self.0.size_hint() + } +} + +#[derive(Debug)] +pub struct Iter<'a> { + inner: hash_map::Iter<'a, HeaderName, Value>, + multi_inner: Option<(&'a HeaderName, &'a SmallVec<[HeaderValue; 4]>)>, + multi_idx: usize, } impl<'a> Iter<'a> { fn new(iter: hash_map::Iter<'a, HeaderName, Value>) -> Self { Self { - iter, - idx: 0, - current: None, + inner: iter, + multi_idx: 0, + multi_inner: None, } } } @@ -358,28 +682,272 @@ impl<'a> Iter<'a> { impl<'a> Iterator for Iter<'a> { type Item = (&'a HeaderName, &'a HeaderValue); - #[inline] - fn next(&mut self) -> Option<(&'a HeaderName, &'a HeaderValue)> { - if let Some(ref mut item) = self.current { - if self.idx < item.1.len() { - let item = (item.0, &item.1[self.idx]); - self.idx += 1; - return Some(item); - } else { - self.idx = 0; - self.current.take(); - } - } - if let Some(item) = self.iter.next() { - match item.1 { - Value::One(ref value) => Some((item.0, value)), - Value::Multi(ref vec) => { - self.current = Some((item.0, vec)); - self.next() + fn next(&mut self) -> Option { + // handle in-progress multi value lists first + if let Some((ref name, ref mut vals)) = self.multi_inner { + match vals.get(self.multi_idx) { + Some(val) => { + self.multi_idx += 1; + return Some((name, val)); + } + None => { + // no more items in value list; reset state + self.multi_idx = 0; + self.multi_inner = None; } } - } else { - None + } + + let (name, value) = self.inner.next()?; + + // set up new inner iter and recurse into it + self.multi_inner = Some((name, &value.inner)); + self.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // take inner lower bound + // make no attempt at an upper bound + (self.inner.size_hint().0, None) + } +} + +/// Iterator over drained name-value pairs. +/// +/// Iterator items are `(Option, HeaderValue)` to avoid cloning. +#[derive(Debug)] +pub struct Drain<'a> { + inner: hash_map::Drain<'a, HeaderName, Value>, + multi_inner: Option<(Option, SmallVec<[HeaderValue; 4]>)>, + multi_idx: usize, +} + +impl<'a> Drain<'a> { + fn new(iter: hash_map::Drain<'a, HeaderName, Value>) -> Self { + Self { + inner: iter, + multi_inner: None, + multi_idx: 0, } } } + +impl<'a> Iterator for Drain<'a> { + type Item = (Option, HeaderValue); + + fn next(&mut self) -> Option { + // handle in-progress multi value iterators first + if let Some((ref mut name, ref mut vals)) = self.multi_inner { + if !vals.is_empty() { + // OPTIMIZE: array removals + return Some((name.take(), vals.remove(0))); + } else { + // no more items in value iterator; reset state + self.multi_inner = None; + self.multi_idx = 0; + } + } + + let (name, value) = self.inner.next()?; + + // set up new inner iter and recurse into it + self.multi_inner = Some((Some(name), value.inner)); + self.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // take inner lower bound + // make no attempt at an upper bound + (self.inner.size_hint().0, None) + } +} + +/// Iterator over owned name-value pairs. +/// +/// Implementation necessarily clones header names for each value. +#[derive(Debug)] +pub struct IntoIter { + inner: hash_map::IntoIter, + multi_inner: Option<(HeaderName, smallvec::IntoIter<[HeaderValue; 4]>)>, +} + +impl IntoIter { + fn new(inner: hash_map::IntoIter) -> Self { + Self { + inner, + multi_inner: None, + } + } +} + +impl Iterator for IntoIter { + type Item = (HeaderName, HeaderValue); + + fn next(&mut self) -> Option { + // handle in-progress multi value iterators first + if let Some((ref name, ref mut vals)) = self.multi_inner { + match vals.next() { + Some(val) => { + return Some((name.clone(), val)); + } + None => { + // no more items in value iterator; reset state + self.multi_inner = None; + } + } + } + + let (name, value) = self.inner.next()?; + + // set up new inner iter and recurse into it + self.multi_inner = Some((name, value.inner.into_iter())); + self.next() + } + + #[inline] + fn size_hint(&self) -> (usize, Option) { + // take inner lower bound + // make no attempt at an upper bound + (self.inner.size_hint().0, None) + } +} + +#[cfg(test)] +mod tests { + use http::header; + + use super::*; + + #[test] + fn create() { + let map = HeaderMap::new(); + assert_eq!(map.len(), 0); + assert_eq!(map.capacity(), 0); + + let map = HeaderMap::with_capacity(16); + assert_eq!(map.len(), 0); + assert!(map.capacity() >= 16); + } + + #[test] + fn insert() { + let mut map = HeaderMap::new(); + + map.insert(header::LOCATION, HeaderValue::from_static("/test")); + assert_eq!(map.len(), 1); + } + + #[test] + fn contains() { + let mut map = HeaderMap::new(); + assert!(!map.contains_key(header::LOCATION)); + + map.insert(header::LOCATION, HeaderValue::from_static("/test")); + assert!(map.contains_key(header::LOCATION)); + assert!(map.contains_key("Location")); + assert!(map.contains_key("Location".to_owned())); + assert!(map.contains_key("location")); + } + + #[test] + fn entries_iter() { + let mut map = HeaderMap::new(); + + map.append(header::HOST, HeaderValue::from_static("duck.com")); + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut iter = map.iter(); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_none()); + + let pairs = map.iter().collect::>(); + assert!(pairs.contains(&(&header::HOST, &HeaderValue::from_static("duck.com")))); + assert!(pairs.contains(&(&header::COOKIE, &HeaderValue::from_static("one=1")))); + assert!(pairs.contains(&(&header::COOKIE, &HeaderValue::from_static("two=2")))); + } + + #[test] + fn drain_iter() { + let mut map = HeaderMap::new(); + + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut vals = vec![]; + let mut iter = map.drain(); + + let (name, val) = iter.next().unwrap(); + assert_eq!(name, Some(header::COOKIE)); + vals.push(val); + + let (name, val) = iter.next().unwrap(); + assert!(name.is_none()); + vals.push(val); + + assert!(vals.contains(&HeaderValue::from_static("one=1"))); + assert!(vals.contains(&HeaderValue::from_static("two=2"))); + + assert!(iter.next().is_none()); + drop(iter); + + assert!(map.is_empty()); + } + + #[test] + fn entries_into_iter() { + let mut map = HeaderMap::new(); + + map.append(header::HOST, HeaderValue::from_static("duck.com")); + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut iter = map.into_iter(); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_some()); + assert!(iter.next().is_none()); + } + + #[test] + fn iter_and_into_iter_same_order() { + let mut map = HeaderMap::new(); + + map.append(header::HOST, HeaderValue::from_static("duck.com")); + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut iter = map.iter(); + let mut into_iter = map.clone().into_iter(); + + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + assert_eq!(iter.next().map(owned_pair), into_iter.next()); + } + + #[test] + fn get_all_and_remove_same_order() { + let mut map = HeaderMap::new(); + + map.append(header::COOKIE, HeaderValue::from_static("one=1")); + map.append(header::COOKIE, HeaderValue::from_static("two=2")); + + let mut vals = map.get_all(header::COOKIE); + let mut removed = map.clone().remove(header::COOKIE); + + assert_eq!(vals.next(), removed.next().as_ref()); + assert_eq!(vals.next(), removed.next().as_ref()); + assert_eq!(vals.next(), removed.next().as_ref()); + } + + fn owned_pair<'a>( + (name, val): (&'a HeaderName, &'a HeaderValue), + ) -> (HeaderName, HeaderValue) { + (name.clone(), val.clone()) + } +} diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index dc97bf5ff..9543d43b6 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -1,4 +1,4 @@ -//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing/conversion and other +//! Typed HTTP headers, pre-defined `HeaderName`s, traits for parsing and conversion, and other //! header utility methods. use std::fmt; @@ -11,6 +11,7 @@ pub use http::header::*; use crate::error::ParseError; use crate::httpmessage::HttpMessage; +mod as_name; mod into_pair; mod into_value; mod utils; @@ -23,6 +24,7 @@ pub use self::common::*; #[doc(hidden)] pub use self::shared::*; +pub use self::as_name::AsHeaderName; pub use self::into_pair::IntoHeaderPair; pub use self::into_value::IntoHeaderValue; #[doc(hidden)] @@ -39,16 +41,14 @@ pub trait Header: IntoHeaderValue { fn parse(msg: &T) -> Result; } -#[doc(hidden)] +#[derive(Debug, Default)] pub(crate) struct Writer { buf: BytesMut, } impl Writer { fn new() -> Writer { - Writer { - buf: BytesMut::new(), - } + Writer::default() } fn take(&mut self) -> Bytes { @@ -71,12 +71,8 @@ impl fmt::Write for Writer { /// Convert `http::HeaderMap` to our `HeaderMap`. impl From for HeaderMap { - fn from(map: http::HeaderMap) -> HeaderMap { - let mut new_map = HeaderMap::with_capacity(map.capacity()); - for (h, v) in map.iter() { - new_map.append(h.clone(), v.clone()); - } - new_map + fn from(mut map: http::HeaderMap) -> HeaderMap { + HeaderMap::from_drain(map.drain()) } } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 1031d7dce..fbf4d3c23 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -177,13 +177,17 @@ impl

fmt::Debug for Request

{ self.method(), self.path() )?; + if let Some(q) = self.uri().query().as_ref() { writeln!(f, " query: ?{:?}", q)?; } + writeln!(f, " headers:")?; - for (key, val) in self.headers() { + + for (key, val) in self.headers().iter() { writeln!(f, " {:?}: {:?}", key, val)?; } + Ok(()) } } diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 110514e05..113ace221 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -363,7 +363,9 @@ impl ResponseBuilder { { if let Some(parts) = parts(&mut self.head, &self.err) { match header.try_into_header_pair() { - Ok((key, value)) => parts.headers.insert(key, value), + Ok((key, value)) => { + parts.headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), }; } @@ -752,9 +754,11 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { let mut msg = BoxedResponseHead::new(head.status); msg.version = head.version; msg.reason = head.reason; - for (k, v) in &head.headers { + + for (k, v) in head.headers.iter() { msg.headers.append(k.clone(), v.clone()); } + msg.no_chunking(!head.chunked()); ResponseBuilder { @@ -893,16 +897,20 @@ mod tests { .max_age(time::Duration::days(1)) .finish(), ) - .del_cookie(&cookies[1]) + .del_cookie(&cookies[0]) .finish(); - let mut val: Vec<_> = resp + let mut val = resp .headers() .get_all(SET_COOKIE) .map(|v| v.to_str().unwrap().to_owned()) - .collect(); + .collect::>(); val.sort(); + + // the .del_cookie call assert!(val[0].starts_with("cookie1=; Max-Age=0;")); + + // the .cookie call assert_eq!( val[1], "name=value; HttpOnly; Path=/test; Domain=www.rust-lang.org; Max-Age=86400" @@ -927,9 +935,9 @@ mod tests { let mut iter = r.cookies(); let v = iter.next().unwrap(); - assert_eq!((v.name(), v.value()), ("cookie3", "val300")); - let v = iter.next().unwrap(); assert_eq!((v.name(), v.value()), ("original", "val100")); + let v = iter.next().unwrap(); + assert_eq!((v.name(), v.value()), ("cookie3", "val300")); } #[test] diff --git a/awc/src/frozen.rs b/awc/src/frozen.rs index 878f404c6..46b4063a0 100644 --- a/awc/src/frozen.rs +++ b/awc/src/frozen.rs @@ -145,7 +145,9 @@ impl FrozenSendBuilder { { match HeaderName::try_from(key) { Ok(key) => match value.try_into_value() { - Ok(value) => self.extra_headers.insert(key, value), + Ok(value) => { + self.extra_headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), }, Err(e) => self.err = Some(e.into()), diff --git a/awc/src/request.rs b/awc/src/request.rs index b9a333b18..c87df9b3b 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -159,7 +159,9 @@ impl ClientRequest { H: IntoHeaderPair, { match header.try_into_header_pair() { - Ok((key, value)) => self.head.headers.insert(key, value), + Ok((key, value)) => { + self.head.headers.insert(key, value); + } Err(e) => self.err = Some(e.into()), }; @@ -232,7 +234,9 @@ impl ClientRequest { >::Error: Into, { match HeaderValue::try_from(value) { - Ok(value) => self.head.headers.insert(header::CONTENT_TYPE, value), + Ok(value) => { + self.head.headers.insert(header::CONTENT_TYPE, value); + } Err(e) => self.err = Some(e.into()), } self diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 1cf863d96..ca220be71 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -304,7 +304,9 @@ impl RequestSender { RequestSender::Owned(head) => { if !head.headers.contains_key(&key) { match value.try_into_value() { - Ok(value) => head.headers.insert(key, value), + Ok(value) => { + head.headers.insert(key, value); + } Err(e) => return Err(e.into()), } } diff --git a/src/request.rs b/src/request.rs index a563518e0..9a5e43185 100644 --- a/src/request.rs +++ b/src/request.rs @@ -445,10 +445,10 @@ mod tests { { let cookies = req.cookies().unwrap(); assert_eq!(cookies.len(), 2); - assert_eq!(cookies[0].name(), "cookie2"); - assert_eq!(cookies[0].value(), "value2"); - assert_eq!(cookies[1].name(), "cookie1"); - assert_eq!(cookies[1].value(), "value1"); + assert_eq!(cookies[0].name(), "cookie1"); + assert_eq!(cookies[0].value(), "value1"); + assert_eq!(cookies[1].name(), "cookie2"); + assert_eq!(cookies[1].value(), "value2"); } let cookie = req.cookie("cookie1"); From dcad9724bc1a056417818f89359476889438074c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 10 Feb 2021 02:11:53 -0800 Subject: [PATCH 149/187] ensure poll_flush on h1 connection disconnect (#1974) --- actix-http/src/h1/dispatcher.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8fbf3ede0..1d28a0a39 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -841,9 +841,8 @@ where if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer - inner.as_mut().poll_flush(cx)?; - if !inner.write_buf.is_empty() { + // flush buffer and wait on block. + if inner.as_mut().poll_flush(cx)? { Poll::Pending } else { Pin::new(inner.project().io.as_mut().unwrap()) From a290e589823163d24880fc37f678b34b6dcbf38e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 12:10:03 +0000 Subject: [PATCH 150/187] prepare beta 2 release set (#1975) --- CHANGES.md | 23 ++-- Cargo.toml | 6 +- README.md | 6 +- actix-files/CHANGES.md | 4 + actix-files/Cargo.toml | 6 +- actix-http-test/CHANGES.md | 4 + actix-http-test/Cargo.toml | 8 +- actix-http-test/README.md | 2 +- actix-http/CHANGES.md | 219 +++++++++--------------------- actix-http/Cargo.toml | 4 +- actix-http/README.md | 11 +- actix-multipart/CHANGES.md | 4 + actix-multipart/Cargo.toml | 8 +- actix-multipart/README.md | 22 ++- actix-multipart/src/lib.rs | 2 +- actix-web-actors/CHANGES.md | 4 + actix-web-actors/Cargo.toml | 8 +- actix-web-actors/README.md | 22 ++- actix-web-actors/src/lib.rs | 2 +- actix-web-codegen/Cargo.toml | 2 +- actix-web-codegen/src/lib.rs | 2 +- awc/CHANGES.md | 3 + awc/Cargo.toml | 10 +- awc/README.md | 6 +- docs/graphs/web-focus.dot | 7 +- docs/graphs/web-only.dot | 1 - src/middleware/compat.rs | 2 +- src/middleware/compress.rs | 2 +- src/middleware/condition.rs | 2 +- src/middleware/default_headers.rs | 2 +- src/middleware/err_handlers.rs | 2 +- src/middleware/logger.rs | 2 +- src/middleware/normalize.rs | 2 +- src/types/json.rs | 2 +- src/types/path.rs | 2 +- src/types/payload.rs | 6 +- src/types/query.rs | 4 +- src/web.rs | 1 + 38 files changed, 191 insertions(+), 234 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 9a3b7fe3e..5eac57fe6 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,26 +1,29 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 4.0.0-beta.2 - 2021-xx-xx ### Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] * Add `services!` macro for helping register multiple services to `App`. [#1933] -* Enable registering vector of same type of `HttpServiceFactory` to `App` [#1933] +* Enable registering a vec of services of the same type to `App` [#1933] ### Changed * Rework `Responder` trait to be sync and returns `Response`/`HttpResponse` directly. - Making it more simple and performant. [#1891] -* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` would not fail. - `ServiceRequest::from_request` would not fail and no payload would be generated [#1893] + Making it simpler and more performant. [#1891] +* `ServiceRequest::into_parts` and `ServiceRequest::from_parts` can no longer fail. [#1893] +* `ServiceRequest::from_request` can no longer fail. [#1893] * Our `Either` type now uses `Left`/`Right` variants (instead of `A`/`B`) [#1894] -* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` +* `test::{call_service, read_response, read_response_json, send_request}` take `&Service` in argument [#1905] -* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` would give `&Service` in closure - argument [#1905] -* `web::block` accept any closure that has an output bound to `Send` and `'static`. [#1957] - +* `App::wrap_fn`, `Resource::wrap_fn` and `Scope::wrap_fn` provide `&Service` in closure + argument. [#1905] +* `web::block` no longer requires the output is a Result. [#1957] + ### Fixed -* Multiple calls `App::data` with the same type now keeps the latest call's data. [#1906] +* Multiple calls to `App::data` with the same type now keeps the latest call's data. [#1906] ### Removed * Public field of `web::Path` has been made private. [#1894] diff --git a/Cargo.toml b/Cargo.toml index 80187c8a5..217594a48 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.1" +version = "4.0.0-beta.2" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" @@ -83,8 +83,8 @@ actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } actix-web-codegen = "0.4.0" -actix-http = "3.0.0-beta.1" -awc = { version = "3.0.0-beta.1", default-features = false } +actix-http = "3.0.0-beta.2" +awc = { version = "3.0.0-beta.2", default-features = false } ahash = "0.7" bytes = "1" diff --git a/README.md b/README.md index 8a703117f..684310961 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=3.3.2)](https://docs.rs/actix-web/3.3.2) +[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.2)](https://docs.rs/actix-web/4.0.0-beta.2) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) -![License](https://img.shields.io/crates/l/actix-web.svg) -[![Dependency Status](https://deps.rs/crate/actix-web/3.3.2/status.svg)](https://deps.rs/crate/actix-web/3.3.2) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) +[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.2)
[![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 8e5566b61..6e2a241ac 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,12 +1,16 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 0.6.0-beta.2 - 2021-02-10 * Fix If-Modified-Since and If-Unmodified-Since to not compare using sub-second timestamps. [#1887] * Replace `v_htmlescape` with `askama_escape`. [#1953] [#1887]: https://github.com/actix/actix-web/pull/1887 [#1953]: https://github.com/actix/actix-web/pull/1953 + ## 0.6.0-beta.1 - 2021-01-07 * `HttpRange::parse` now has its own error type. * Update `bytes` to `1.0`. [#1813] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index b4317596c..f7e1a6c0d 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.0-beta.1" +version = "0.6.0-beta.2" authors = ["Nikolay Kim "] description = "Static file serving for Actix Web" readme = "README.md" @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.1", default-features = false } +actix-web = { version = "4.0.0-beta.2", default-features = false } actix-service = "2.0.0-beta.4" askama_escape = "0.10" @@ -33,4 +33,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2" -actix-web = "4.0.0-beta.1" +actix-web = "4.0.0-beta.2" diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index ed6d87a60..2f47d700d 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 3.0.0-beta.2 - 2021-02-10 +* No notable changes. + + ## 3.0.0-beta.1 - 2021-01-07 * Update `bytes` to `1.0`. [#1813] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 25fd74a10..3eef4e727 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.1" +version = "3.0.0-beta.2" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" readme = "README.md" @@ -35,7 +35,7 @@ actix-tls = "3.0.0-beta.3" actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-server = "2.0.0-beta.3" -awc = "3.0.0-beta.1" +awc = "3.0.0-beta.2" base64 = "0.13" bytes = "1" @@ -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 = "4.0.0-beta.1" -actix-http = "3.0.0-beta.1" +actix-web = "4.0.0-beta.2" +actix-http = "3.0.0-beta.2" diff --git a/actix-http-test/README.md b/actix-http-test/README.md index bca9a7976..66f15979d 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -4,7 +4,7 @@ [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) [![Documentation](https://docs.rs/actix-http-test/badge.svg?version=2.1.0)](https://docs.rs/actix-http-test/2.1.0) -![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http-test) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test) [![Dependency Status](https://deps.rs/crate/actix-http-test/2.1.0/status.svg)](https://deps.rs/crate/actix-http-test/2.1.0) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index bef17fb08..07a64701e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.2 - 2021-02-19 ### Added * `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] * `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] @@ -18,11 +21,12 @@ * Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std `TryInto` trait. [#1894] * `Extensions::insert` returns Option of replaced item. [#1904] -* Remove `HttpResponseBuilder::json2()` and make `HttpResponseBuilder::json()` take a value by - reference. [#1903] -* `client::error::ConnectError` Resolver variant contains `Box` type [#1905] +* Remove `HttpResponseBuilder::json2()`. [#1903] +* Enable `HttpResponseBuilder::json()` to receive data by value and reference. [#1903] +* `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] * `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] -* Simplify `BlockingError` type to a struct. It's only triggered with blocking thread pool is dead. [#1957] +* Simplify `BlockingError` type to a unit struct. It's now only triggered when blocking thread pool + is dead. [#1957] * `HeaderMap::len` now returns number of values instead of number of keys. [#1964] * `HeaderMap::insert` now returns iterator of removed values. [#1964] * `HeaderMap::remove` now returns iterator of removed values. [#1964] @@ -123,15 +127,14 @@ * Update actix-connect and actix-tls dependencies. -## [2.0.0-beta.3] - 2020-08-14 - +## 2.0.0-beta.3 - 2020-08-14 ### Fixed * Memory leak of `client::pool::ConnectorPoolSupport`. [#1626] [#1626]: https://github.com/actix/actix-web/pull/1626 -## [2.0.0-beta.2] - 2020-07-21 +## 2.0.0-beta.2 - 2020-07-21 ### Fixed * Potential UB in h1 decoder using uninitialized memory. [#1614] @@ -142,10 +145,8 @@ [#1615]: https://github.com/actix/actix-web/pull/1615 -## [2.0.0-beta.1] - 2020-07-11 - +## 2.0.0-beta.1 - 2020-07-11 ### Changed - * Migrate cookie handling to `cookie` crate. [#1558] * Update `sha-1` to 0.9. [#1586] * Fix leak in client pool. [#1580] @@ -155,33 +156,30 @@ [#1586]: https://github.com/actix/actix-web/pull/1586 [#1580]: https://github.com/actix/actix-web/pull/1580 -## [2.0.0-alpha.4] - 2020-05-21 +## 2.0.0-alpha.4 - 2020-05-21 ### Changed - * Bump minimum supported Rust version to 1.40 -* content_length function is removed, and you can set Content-Length by calling no_chunking function [#1439] +* content_length function is removed, and you can set Content-Length by calling + no_chunking function [#1439] * `BodySize::Sized64` variant has been removed. `BodySize::Sized` now receives a `u64` instead of a `usize`. * Update `base64` dependency to 0.12 ### Fixed - * Support parsing of `SameSite=None` [#1503] [#1439]: https://github.com/actix/actix-web/pull/1439 [#1503]: https://github.com/actix/actix-web/pull/1503 -## [2.0.0-alpha.3] - 2020-05-08 +## 2.0.0-alpha.3 - 2020-05-08 ### Fixed - * Correct spelling of ConnectError::Unresolved [#1487] * Fix a mistake in the encoding of websocket continuation messages wherein Item::FirstText and Item::FirstBinary are each encoded as the other. ### Changed - * Implement `std::error::Error` for our custom errors [#1422] * Remove `failure` support for `ResponseError` since that crate will be deprecated in the near future. @@ -189,338 +187,247 @@ [#1422]: https://github.com/actix/actix-web/pull/1422 [#1487]: https://github.com/actix/actix-web/pull/1487 -## [2.0.0-alpha.2] - 2020-03-07 +## 2.0.0-alpha.2 - 2020-03-07 ### Changed - * Update `actix-connect` and `actix-tls` dependency to 2.0.0-alpha.1. [#1395] - -* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB respectively - to improve download speed for awc when downloading large objects. [#1394] - -* client::Connector accepts initial_window_size and initial_connection_window_size HTTP2 configuration. [#1394] - +* Change default initial window size and connection window size for HTTP2 to 2MB and 1MB + respectively to improve download speed for awc when downloading large objects. [#1394] +* client::Connector accepts initial_window_size and initial_connection_window_size + HTTP2 configuration. [#1394] * client::Connector allowing to set max_http_version to limit HTTP version to be used. [#1394] [#1394]: https://github.com/actix/actix-web/pull/1394 [#1395]: https://github.com/actix/actix-web/pull/1395 -## [2.0.0-alpha.1] - 2020-02-27 +## 2.0.0-alpha.1 - 2020-02-27 ### Changed - * Update the `time` dependency to 0.2.7. * Moved actors messages support from actix crate, enabled with feature `actors`. -* Breaking change: trait MessageBody requires Unpin and accepting Pin<&mut Self> instead of &mut self in the poll_next(). +* Breaking change: trait MessageBody requires Unpin and accepting `Pin<&mut Self>` instead of + `&mut self` in the poll_next(). * MessageBody is not implemented for &'static [u8] anymore. ### Fixed - * Allow `SameSite=None` cookies to be sent in a response. -## [1.0.1] - 2019-12-20 +## 1.0.1 - 2019-12-20 ### Fixed - * Poll upgrade service's readiness from HTTP service handlers - * Replace brotli with brotli2 #1224 -## [1.0.0] - 2019-12-13 +## 1.0.0 - 2019-12-13 ### Added - * Add websockets continuation frame support ### Changed - * Replace `flate2-xxx` features with `compress` -## [1.0.0-alpha.5] - 2019-12-09 +## 1.0.0-alpha.5 - 2019-12-09 ### Fixed - * Check `Upgrade` service readiness before calling it - -* Fix buffer remaining capacity calcualtion +* Fix buffer remaining capacity calculation ### Changed - * Websockets: Ping and Pong should have binary data #1049 -## [1.0.0-alpha.4] - 2019-12-08 +## 1.0.0-alpha.4 - 2019-12-08 ### Added - * Add impl ResponseBuilder for Error ### Changed - * Use rust based brotli compression library -## [1.0.0-alpha.3] - 2019-12-07 - +## 1.0.0-alpha.3 - 2019-12-07 ### Changed - * Migrate to tokio 0.2 - * Migrate to `std::future` -## [0.2.11] - 2019-11-06 - +## 0.2.11 - 2019-11-06 ### Added - * Add support for serde_json::Value to be passed as argument to ResponseBuilder.body() - -* Add an additional `filename*` param in the `Content-Disposition` header of `actix_files::NamedFile` to be more compatible. (#1151) - +* Add an additional `filename*` param in the `Content-Disposition` header of + `actix_files::NamedFile` to be more compatible. (#1151) * Allow to use `std::convert::Infallible` as `actix_http::error::Error` ### Fixed +* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; + charset=utf-8` header [#1118] -* To be compatible with non-English error responses, `ResponseError` rendered with `text/plain; charset=utf-8` header #1118 +[#1878]: https://github.com/actix/actix-web/pull/1878 -## [0.2.10] - 2019-09-11 - +## 0.2.10 - 2019-09-11 ### Added - -* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests with `RequestHead` +* Add support for sending HTTP requests with `Rc` in addition to sending HTTP requests + with `RequestHead` ### Fixed - * h2 will use error response #1080 - * on_connect result isn't added to request extensions for http2 requests #1009 -## [0.2.9] - 2019-08-13 - +## 0.2.9 - 2019-08-13 ### Changed - * Dropped the `byteorder`-dependency in favor of `stdlib`-implementation - * Update percent-encoding to 2.1 - * Update serde_urlencoded to 0.6.1 ### Fixed - * Fixed a panic in the HTTP2 handshake in client HTTP requests (#1031) -## [0.2.8] - 2019-08-01 - +## 0.2.8 - 2019-08-01 ### Added - * Add `rustls` support - * Add `Clone` impl for `HeaderMap` ### Fixed - * awc client panic #1016 - -* Invalid response with compression middleware enabled, but compression-related features disabled #997 +* Invalid response with compression middleware enabled, but compression-related features + disabled #997 -## [0.2.7] - 2019-07-18 - +## 0.2.7 - 2019-07-18 ### Added - * Add support for downcasting response errors #986 -## [0.2.6] - 2019-07-17 - +## 0.2.6 - 2019-07-17 ### Changed - * Replace `ClonableService` with local copy - * Upgrade `rand` dependency version to 0.7 -## [0.2.5] - 2019-06-28 - +## 0.2.5 - 2019-06-28 ### Added - * Add `on-connect` callback, `HttpServiceBuilder::on_connect()` #946 ### Changed - * Use `encoding_rs` crate instead of unmaintained `encoding` crate - * Add `Copy` and `Clone` impls for `ws::Codec` -## [0.2.4] - 2019-06-16 - +## 0.2.4 - 2019-06-16 ### Fixed - * Do not compress NoContent (204) responses #918 -## [0.2.3] - 2019-06-02 - +## 0.2.3 - 2019-06-02 ### Added - * Debug impl for ResponseBuilder - * From SizedStream and BodyStream for Body ### Changed - * SizedStream uses u64 -## [0.2.2] - 2019-05-29 - +## 0.2.2 - 2019-05-29 ### Fixed - * Parse incoming stream before closing stream on disconnect #868 -## [0.2.1] - 2019-05-25 - +## 0.2.1 - 2019-05-25 ### Fixed - * Handle socket read disconnect -## [0.2.0] - 2019-05-12 - +## 0.2.0 - 2019-05-12 ### Changed - * Update actix-service to 0.4 - * Expect and upgrade services accept `ServerConfig` config. ### Deleted - * `OneRequest` service -## [0.1.5] - 2019-05-04 - +## 0.1.5 - 2019-05-04 ### Fixed - * Clean up response extensions in response pool #817 -## [0.1.4] - 2019-04-24 - +## 0.1.4 - 2019-04-24 ### Added - * Allow to render h1 request headers in `Camel-Case` ### Fixed - * Read until eof for http/1.0 responses #771 -## [0.1.3] - 2019-04-23 - +## 0.1.3 - 2019-04-23 ### Fixed - * Fix http client pool management - * Fix http client wait queue management #794 -## [0.1.2] - 2019-04-23 - +## 0.1.2 - 2019-04-23 ### Fixed - * Fix BorrowMutError panic in client connector #793 -## [0.1.1] - 2019-04-19 - +## 0.1.1 - 2019-04-19 ### Changed - * Cookie::max_age() accepts value in seconds - * Cookie::max_age_time() accepts value in time::Duration - * Allow to specify server address for client connector -## [0.1.0] - 2019-04-16 - +## 0.1.0 - 2019-04-16 ### Added - * Expose peer addr via `Request::peer_addr()` and `RequestHead::peer_addr` ### Changed - * `actix_http::encoding` always available - * use trust-dns-resolver 0.11.0 -## [0.1.0-alpha.5] - 2019-04-12 - +## 0.1.0-alpha.5 - 2019-04-12 ### Added - * Allow to use custom service for upgrade requests - * Added `h1::SendResponse` future. ### Changed - * MessageBody::length() renamed to MessageBody::size() for consistency - * ws handshake verification functions take RequestHead instead of Request -## [0.1.0-alpha.4] - 2019-04-08 - +## 0.1.0-alpha.4 - 2019-04-08 ### Added - * Allow to use custom `Expect` handler - * Add minimal `std::error::Error` impl for `Error` ### Changed - * Export IntoHeaderValue - * Render error and return as response body - -* Use thread pool for response body comression +* Use thread pool for response body compression ### Deleted - * Removed PayloadBuffer -## [0.1.0-alpha.3] - 2019-04-02 - +## 0.1.0-alpha.3 - 2019-04-02 ### Added - * Warn when an unsealed private cookie isn't valid UTF-8 ### Fixed - * Rust 1.31.0 compatibility - * Preallocate read buffer for h1 codec - * Detect socket disconnection during protocol selection -## [0.1.0-alpha.2] - 2019-03-29 - +## 0.1.0-alpha.2 - 2019-03-29 ### Added - * Added ws::Message::Nop, no-op websockets message ### Changed - -* Do not use thread pool for decomression if chunk size is smaller than 2048. +* Do not use thread pool for decompression if chunk size is smaller than 2048. -## [0.1.0-alpha.1] - 2019-03-28 - +## 0.1.0-alpha.1 - 2019-03-28 * Initial impl diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index e974e61df..67ea0a12d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" @@ -86,7 +86,7 @@ trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] actix-server = "2.0.0-beta.3" -actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" diff --git a/actix-http/README.md b/actix-http/README.md index 9dfb85e24..4ec5b97af 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -3,10 +3,13 @@ > HTTP primitives for the Actix ecosystem. [![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) -[![Documentation](https://docs.rs/actix-http/badge.svg?version=2.2.0)](https://docs.rs/actix-http/2.2.0) -![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/actix-http) -[![Dependency Status](https://deps.rs/crate/actix-http/2.2.0/status.svg)](https://deps.rs/crate/actix-http/2.2.0) -[![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.2)](https://docs.rs/actix-http/3.0.0-beta.2) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +![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.2/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.2) +[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) +[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index e1fe9c4af..2142ebf4b 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.4.0-beta.2 - 2021-02-10 +* No notable changes. + + ## 0.4.0-beta.1 - 2021-01-07 * Fix multipart consuming payload before header checks. [#1513] * Update `bytes` to `1.0`. [#1813] diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index beecd2e9d..6a6b4ac14 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-multipart" -version = "0.4.0-beta.1" +version = "0.4.0-beta.2" authors = ["Nikolay Kim "] -description = "Multipart support for actix web framework." +description = "Multipart form support for Actix Web" readme = "README.md" keywords = ["http", "web", "framework", "async", "futures"] homepage = "https://actix.rs" @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.1", default-features = false } +actix-web = { version = "4.0.0-beta.2", default-features = false } actix-utils = "3.0.0-beta.2" bytes = "1" @@ -29,4 +29,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2" -actix-http = "3.0.0-beta.1" +actix-http = "3.0.0-beta.2" diff --git a/actix-multipart/README.md b/actix-multipart/README.md index edb2e0020..defcf7828 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -1,8 +1,18 @@ -# Multipart support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-multipart)](https://crates.io/crates/actix-multipart) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# actix-multipart -## Documentation & community resources +> Multipart form support for Actix Web. -* [API Documentation](https://docs.rs/actix-multipart/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-multipart](https://crates.io/crates/actix-multipart) -* Minimum supported Rust version: 1.40 or later +[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.2)](https://docs.rs/actix-multipart/0.4.0-beta.2) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg) +
+[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.2/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.2) +[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) +[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-multipart) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum Supported Rust Version (MSRV): 1.46.0 diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index 46dd0ee9b..38a24e28f 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -1,4 +1,4 @@ -//! Multipart form support for Actix web. +//! Multipart form support for Actix Web. #![deny(rust_2018_idioms)] #![allow(clippy::borrow_interior_mutable_const)] diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index f75c6805f..acd9ceada 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.2 - 2021-02-10 +* No notable changes. + + ## 4.0.0-beta.1 - 2021-01-07 * Update `pin-project` to `1.0`. * Update `bytes` to `1.0`. [#1813] diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 522279fd0..5f689ddb7 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,8 +1,8 @@ [package] name = "actix-web-actors" -version = "4.0.0-beta.1" +version = "4.0.0-beta.2" authors = ["Nikolay Kim "] -description = "Actix actors support for actix web framework." +description = "Actix actors support for Actix Web" readme = "README.md" keywords = ["actix", "http", "web", "framework", "async"] homepage = "https://actix.rs" @@ -18,8 +18,8 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.1" -actix-web = { version = "4.0.0-beta.1", default-features = false } +actix-http = "3.0.0-beta.2" +actix-web = { version = "4.0.0-beta.2", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index fb8c3a621..c9b588153 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -1,8 +1,18 @@ -Actix actors support for actix web framework [![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) [![crates.io](https://meritbadge.herokuapp.com/actix-web-actors)](https://crates.io/crates/actix-web-actors) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) +# actix-web-actors -## Documentation & community resources +> Actix actors support for Actix Web. -* [API Documentation](https://docs.rs/actix-web-actors/) -* [Chat on gitter](https://gitter.im/actix/actix) -* Cargo package: [actix-web-actors](https://crates.io/crates/actix-web-actors) -* Minimum supported Rust version: 1.40 or later +[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=0.5.0)](https://docs.rs/actix-web-actors/0.5.0) +[![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) +![License](https://img.shields.io/crates/l/actix-web-actors.svg) +
+[![dependency status](https://deps.rs/crate/actix-web-actors/0.5.0/status.svg)](https://deps.rs/crate/actix-web-actors/0.5.0) +[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) +[![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) + +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-web-actors) +- [Chat on Gitter](https://gitter.im/actix/actix-web) +- Minimum supported Rust version: 1.46 or later diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 0421f05fb..7a4823d91 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -1,4 +1,4 @@ -//! Actix actors integration for Actix web framework +//! Actix actors support for Actix Web. #![deny(rust_2018_idioms)] #![allow(clippy::borrow_interior_mutable_const)] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 04bd10421..377427fd1 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2" -actix-web = "4.0.0-beta.1" +actix-web = "4.0.0-beta.2" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index ede1e0005..1248fe757 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -159,7 +159,7 @@ method_macro! { /// # Actix Web Re-export /// This macro can be applied with `#[actix_web::main]` when used in Actix Web applications. /// -/// # Usage +/// # Examples /// ```rust /// #[actix_web_codegen::main] /// async fn main() { diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 80895f46d..20e8af6df 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.0-beta.2 - 2021-02-10 ### Added * `ClientRequest::insert_header` method which allows using typed headers. [#1869] * `ClientRequest::append_header` method which allows using typed headers. [#1869] diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 483e04968..e4178d45e 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.1" +version = "3.0.0-beta.2" authors = ["Nikolay Kim "] description = "Async HTTP and WebSocket client library built on the Actix ecosystem" readme = "README.md" @@ -42,7 +42,7 @@ trust-dns = ["actix-http/trust-dns"] [dependencies] actix-codec = "0.4.0-beta.1" actix-service = "2.0.0-beta.4" -actix-http = "3.0.0-beta.1" +actix-http = "3.0.0-beta.2" actix-rt = "2" base64 = "0.13" @@ -61,9 +61,9 @@ 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.1", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.1", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.1", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.2", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" actix-tls = { version = "3.0.0-beta.3", features = ["openssl", "rustls"] } diff --git a/awc/README.md b/awc/README.md index 3d18a07c5..043ae6a41 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=2.0.3)](https://docs.rs/awc/2.0.3) -![Apache 2.0 or MIT licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/2.0.3/status.svg)](https://deps.rs/crate/awc/2.0.3) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.2)](https://docs.rs/awc/3.0.0-beta.2) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.2/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.2) [![Join the chat at https://gitter.im/actix/actix-web](https://badges.gitter.im/actix/actix-web.svg)](https://gitter.im/actix/actix-web?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources diff --git a/docs/graphs/web-focus.dot b/docs/graphs/web-focus.dot index 55a82bb41..ec0f7a946 100644 --- a/docs/graphs/web-focus.dot +++ b/docs/graphs/web-focus.dot @@ -1,6 +1,7 @@ digraph { subgraph cluster_web { label="actix/actix-web" + "awc" "actix-web" "actix-files" @@ -16,7 +17,7 @@ digraph { "actix-web-actors" -> { "actix" "actix-web" "actix-http" "actix-codec" } "actix-multipart" -> { "actix-web" "actix-service" "actix-utils" } "actix-http" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "threadpool" } - "actix-http" -> { "actix" "actix-tls" }[color=blue] // optional + "actix-http" -> { "actix-tls" }[color=blue] // optional "actix-files" -> { "actix-web" } "actix-http-test" -> { "actix-service" "actix-codec" "actix-tls" "actix-utils" "actix-rt" "actix-server" "awc" } @@ -27,4 +28,8 @@ digraph { "actix-tls" -> { "actix-service" "actix-codec" "actix-utils" } "actix-server" -> { "actix-service" "actix-rt" "actix-codec" "actix-utils" } "actix-rt" -> { "macros" "threadpool" } + + // actix + + "actix" -> { "actix-rt" } } diff --git a/docs/graphs/web-only.dot b/docs/graphs/web-only.dot index 9e1bb2805..6f8292a3a 100644 --- a/docs/graphs/web-only.dot +++ b/docs/graphs/web-only.dot @@ -15,7 +15,6 @@ digraph { "awc" -> { "actix-http" } "actix-web-actors" -> { "actix" "actix-web" "actix-http" } "actix-multipart" -> { "actix-web" } - "actix-http" -> { "actix" }[color=blue] // optional "actix-files" -> { "actix-web" } "actix-http-test" -> { "awc" } } diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 2df535280..7326c5824 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -15,7 +15,7 @@ use crate::{error::Error, service::ServiceResponse}; /// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap), /// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition). /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::middleware::{Logger, Compat}; /// use actix_web::{App, web}; diff --git a/src/middleware/compress.rs b/src/middleware/compress.rs index 7a45e4c8d..698ba768e 100644 --- a/src/middleware/compress.rs +++ b/src/middleware/compress.rs @@ -30,7 +30,7 @@ use crate::{ /// Use `BodyEncoding` trait for overriding response compression. To disable compression set /// encoding to `ContentEncoding::Identity`. /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::{web, middleware, App, HttpResponse}; /// diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index 9263292b9..be6f6764d 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -11,7 +11,7 @@ use futures_util::future::{Either, FutureExt, LocalBoxFuture}; /// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat) /// middleware for a workaround. /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::middleware::{Condition, NormalizePath}; /// use actix_web::App; diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index a9a50ec2c..d98403502 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -28,7 +28,7 @@ use crate::{ /// /// Headers with the same key that are already set in a response will *not* be overwritten. /// -/// # Usage +/// # Examples /// ```rust /// use actix_web::{web, http, middleware, App, HttpResponse}; /// diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index b55959460..07150f905 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -33,7 +33,7 @@ type ErrorHandler = dyn Fn(ServiceResponse) -> Result Date: Wed, 10 Feb 2021 12:12:03 +0000 Subject: [PATCH 151/187] consistent case s/web/Web --- README.md | 2 +- src/app.rs | 2 +- src/lib.rs | 4 ++-- src/scope.rs | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 684310961..ff3755062 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@

-

Actix web

+

Actix Web

Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust

diff --git a/src/app.rs b/src/app.rs index 1660c4b94..40362e0d3 100644 --- a/src/app.rs +++ b/src/app.rs @@ -211,7 +211,7 @@ where /// /// Http service is any type that implements `HttpServiceFactory` trait. /// - /// Actix web provides several services implementations: + /// Actix Web provides several services implementations: /// /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. diff --git a/src/lib.rs b/src/lib.rs index 46fbc2937..7f096584a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -30,7 +30,7 @@ //! //! To get started navigating the API docs, you may consider looking at the following pages first: //! -//! * [App]: This struct represents an Actix web application and is used to +//! * [App]: This struct represents an Actix Web application and is used to //! configure routes and other common application settings. //! //! * [HttpServer]: This struct represents an HTTP server instance and is @@ -205,7 +205,7 @@ pub mod dev { } pub mod client { - //! Actix web async HTTP client. + //! Actix Web async HTTP client. //! //! ```rust //! use actix_web::client::Client; diff --git a/src/scope.rs b/src/scope.rs index d17acd843..d08c78133 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -206,11 +206,11 @@ where self } - /// Register http service. + /// Register HTTP service. /// /// This is similar to `App's` service registration. /// - /// Actix web provides several services implementations: + /// Actix Web provides several services implementations: /// /// * *Resource* is an entry in resource table which corresponds to requested URL. /// * *Scope* is a set of resources with common root path. From bd26083f333ecf63e3eb444748250364ce124f5e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 12:45:46 +0000 Subject: [PATCH 152/187] prepare codegen 0.5.0-beta.1 release --- CHANGES.md | 6 +++++- Cargo.toml | 4 ++-- actix-web-codegen/CHANGES.md | 4 ++++ actix-web-codegen/Cargo.toml | 4 ++-- actix-web-codegen/README.md | 14 ++++++++------ actix-web-codegen/src/lib.rs | 10 +++++----- 6 files changed, 26 insertions(+), 16 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 5eac57fe6..954410b30 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,7 +3,11 @@ ## Unreleased - 2021-xx-xx -## 4.0.0-beta.2 - 2021-xx-xx +## 4.0.0-beta.3 - 2021-02-10 +* Update `actix-web-codegen` to `0.5.0-beta.1`. + + +## 4.0.0-beta.2 - 2021-02-10 ### Added * The method `Either, web::Form>::into_inner()` which returns the inner type for whichever variant was created. Also works for `Either, web::Json>`. [#1894] diff --git a/Cargo.toml b/Cargo.toml index 217594a48..708075b90 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web" -version = "4.0.0-beta.2" +version = "4.0.0-beta.3" authors = ["Nikolay Kim "] description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" readme = "README.md" @@ -82,7 +82,7 @@ actix-service = "2.0.0-beta.4" actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } -actix-web-codegen = "0.4.0" +actix-web-codegen = "0.5.0-beta.1" actix-http = "3.0.0-beta.2" awc = { version = "3.0.0-beta.2", default-features = false } diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index a7675d9dd..2ce728aad 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -3,6 +3,10 @@ ## Unreleased - 2021-xx-xx +## 0.5.0-beta.1 - 2021-02-10 +* Use new call signature for `System::new`. + + ## 0.4.0 - 2020-09-20 * Added compile success and failure testing. [#1677] * Add `route` macro for supporting multiple HTTP methods guards. [#1674] diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index 377427fd1..a54c684a2 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "actix-web-codegen" -version = "0.4.0" -description = "Actix web proc macros" +version = "0.5.0-beta.1" +description = "Routing and runtime macros for Actix Web" readme = "README.md" homepage = "https://actix.rs" repository = "https://github.com/actix/actix-web" diff --git a/actix-web-codegen/README.md b/actix-web-codegen/README.md index 887502075..5820bb443 100644 --- a/actix-web-codegen/README.md +++ b/actix-web-codegen/README.md @@ -1,22 +1,24 @@ # actix-web-codegen -> Helper and convenience macros for Actix Web +> Routing and runtime macros for Actix Web. -[![crates.io](https://meritbadge.herokuapp.com/actix-web-codegen)](https://crates.io/crates/actix-web-codegen) -[![Documentation](https://docs.rs/actix-web-codegen/badge.svg)](https://docs.rs/actix-web-codegen/0.4.0/actix_web_codegen/) +[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen) +[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.1)](https://docs.rs/actix-web-codegen/0.5.0-beta.1) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) -[![Build Status](https://travis-ci.org/actix/actix-web.svg?branch=master)](https://travis-ci.org/actix/actix-web) -[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web) +![License](https://img.shields.io/crates/l/actix-web-codegen.svg) +
+[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.1) +[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) ## Documentation & Resources - [API Documentation](https://docs.rs/actix-web-codegen) - [Chat on Gitter](https://gitter.im/actix/actix-web) -- Cargo package: [actix-web-codegen](https://crates.io/crates/actix-web-codegen) - Minimum supported Rust version: 1.46 or later. ## Compile Testing + Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this. [`trybuild`]: https://github.com/dtolnay/trybuild diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 1248fe757..670d82ce9 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -1,6 +1,6 @@ -//! Macros for reducing boilerplate code in Actix Web applications. +//! Routing and runtime macros for Actix Web. //! -//! ## Actix Web Re-exports +//! # Actix Web Re-exports //! Actix Web re-exports a version of this crate in it's entirety so you usually don't have to //! specify a dependency on this crate explicitly. Sometimes, however, updates are made to this //! crate before the actix-web dependency is updated. Therefore, code examples here will show @@ -10,7 +10,7 @@ //! # Runtime Setup //! Used for setting up the actix async runtime. See [macro@main] macro docs. //! -//! ```rust +//! ``` //! #[actix_web_codegen::main] // or `#[actix_web::main]` in Actix Web apps //! async fn main() { //! async { println!("Hello world"); }.await @@ -23,7 +23,7 @@ //! //! See docs for: [GET], [POST], [PATCH], [PUT], [DELETE], [HEAD], [CONNECT], [OPTIONS], [TRACE] //! -//! ```rust +//! ``` //! # use actix_web::HttpResponse; //! # use actix_web_codegen::get; //! #[get("/test")] @@ -36,7 +36,7 @@ //! Similar to the single method handler macro but takes one or more arguments for the HTTP methods //! it should respond to. See [macro@route] macro docs. //! -//! ```rust +//! ``` //! # use actix_web::HttpResponse; //! # use actix_web_codegen::route; //! #[route("/test", method="GET", method="HEAD")] From e18464b274b7d80b756b317f9cac5773dc33bf14 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 12:57:13 +0000 Subject: [PATCH 153/187] bump actix web versions in deps --- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 2 +- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- actix-web-codegen/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 6 files changed, 7 insertions(+), 7 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index f7e1a6c0d..45fa18a69 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -17,7 +17,7 @@ name = "actix_files" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.2", default-features = false } +actix-web = { version = "4.0.0-beta.3", default-features = false } actix-service = "2.0.0-beta.4" askama_escape = "0.10" @@ -33,4 +33,4 @@ percent-encoding = "2.1" [dev-dependencies] actix-rt = "2" -actix-web = "4.0.0-beta.2" +actix-web = "4.0.0-beta.3" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 3eef4e727..cb3b82eda 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -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 = "4.0.0-beta.2" +actix-web = "4.0.0-beta.3" actix-http = "3.0.0-beta.2" diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 6a6b4ac14..fc59f30c7 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -16,7 +16,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-web = { version = "4.0.0-beta.2", default-features = false } +actix-web = { version = "4.0.0-beta.3", default-features = false } actix-utils = "3.0.0-beta.2" bytes = "1" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 5f689ddb7..c96e0465f 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -19,7 +19,7 @@ path = "src/lib.rs" actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" actix-http = "3.0.0-beta.2" -actix-web = { version = "4.0.0-beta.2", default-features = false } +actix-web = { version = "4.0.0-beta.3", default-features = false } bytes = "1" bytestring = "1" diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a54c684a2..886d9ac3e 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -20,7 +20,7 @@ proc-macro2 = "1" [dev-dependencies] actix-rt = "2" -actix-web = "4.0.0-beta.2" +actix-web = "4.0.0-beta.3" futures-util = { version = "0.3.7", default-features = false } trybuild = "1" rustversion = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index e4178d45e..8cab8e3ba 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -61,7 +61,7 @@ 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.2", features = ["openssl"] } +actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } actix-http = { version = "3.0.0-beta.2", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" From ea5ce3befb806aac28df3019320d0b4619d96198 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 10 Feb 2021 18:36:14 +0000 Subject: [PATCH 154/187] prepare actix-http 3.0.0-beta.3 release --- Cargo.toml | 2 +- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 6 +++++- actix-http/Cargo.toml | 4 ++-- actix-http/README.md | 4 ++-- actix-multipart/Cargo.toml | 2 +- actix-web-actors/Cargo.toml | 2 +- awc/Cargo.toml | 4 ++-- 8 files changed, 15 insertions(+), 11 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 708075b90..3cbd1f358 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -83,7 +83,7 @@ actix-utils = "3.0.0-beta.2" actix-tls = { version = "3.0.0-beta.3", default-features = false, optional = true } actix-web-codegen = "0.5.0-beta.1" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" awc = { version = "3.0.0-beta.2", default-features = false } ahash = "0.7" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index cb3b82eda..6622e04e0 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -52,4 +52,4 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } [dev-dependencies] actix-web = "4.0.0-beta.3" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 07a64701e..b781fe50e 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,7 +3,11 @@ ## Unreleased - 2021-xx-xx -## 3.0.0-beta.2 - 2021-02-19 +## 3.0.0-beta.3 - 2021-02-10 +* No notable changes. + + +## 3.0.0-beta.2 - 2021-02-10 ### Added * `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] * `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 67ea0a12d..c789c3ae5 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0-beta.2" +version = "3.0.0-beta.3" authors = ["Nikolay Kim "] description = "HTTP primitives for the Actix ecosystem" readme = "README.md" @@ -15,7 +15,7 @@ license = "MIT OR Apache-2.0" edition = "2018" [package.metadata.docs.rs] -features = ["openssl", "rustls", "compress", "secure-cookies", "actors"] +features = ["openssl", "rustls", "compress", "secure-cookies"] [lib] name = "actix_http" diff --git a/actix-http/README.md b/actix-http/README.md index 4ec5b97af..881fbc8c5 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.2)](https://docs.rs/actix-http/3.0.0-beta.2) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.3)](https://docs.rs/actix-http/3.0.0-beta.3) [![Version](https://img.shields.io/badge/rustc-1.46+-ab6000.svg)](https://blog.rust-lang.org/2020/03/12/Rust-1.46.html) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.2/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.2) +[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.3/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.3) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Join the chat at https://gitter.im/actix/actix](https://badges.gitter.im/actix/actix.svg)](https://gitter.im/actix/actix?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index fc59f30c7..67b2698bd 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -29,4 +29,4 @@ twoway = "0.2" [dev-dependencies] actix-rt = "2" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index c96e0465f..698ff9420 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -18,7 +18,7 @@ path = "src/lib.rs" [dependencies] actix = { version = "0.11.0-beta.2", default-features = false } actix-codec = "0.4.0-beta.1" -actix-http = "3.0.0-beta.2" +actix-http = "3.0.0-beta.3" actix-web = { version = "4.0.0-beta.3", default-features = false } bytes = "1" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8cab8e3ba..a6da53664 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -42,7 +42,7 @@ 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.2" +actix-http = "3.0.0-beta.3" actix-rt = "2" base64 = "0.13" @@ -62,7 +62,7 @@ tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features [dev-dependencies] actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } -actix-http = { version = "3.0.0-beta.2", features = ["openssl"] } +actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-utils = "3.0.0-beta.1" actix-server = "2.0.0-beta.3" From d9d0d1d1a2e7b1380cb5ea05268f9c1cf6e2b13d Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 10 Feb 2021 15:11:12 -0800 Subject: [PATCH 155/187] reduce unsafe (#1972) --- actix-http/src/h1/dispatcher.rs | 7 +- actix-http/src/ws/frame.rs | 12 +- actix-http/src/ws/mask.rs | 189 ++++++++++++-------------------- 3 files changed, 77 insertions(+), 131 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 1d28a0a39..3882c2d52 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -309,11 +309,8 @@ where } } - // SAFETY: setting length to 0 is safe - // skips one length check vs truncate - unsafe { - write_buf.set_len(0); - } + // everything has written to io. clear buffer. + write_buf.clear(); // flush the io and check if get blocked. let blocked = io.poll_flush(cx)?.is_pending(); diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 8c49524e6..78d487dd2 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -16,7 +16,8 @@ impl Parser { src: &[u8], server: bool, max_size: usize, - ) -> Result)>, ProtocolError> { + ) -> Result)>, ProtocolError> + { let chunk_len = src.len(); let mut idx = 2; @@ -77,9 +78,10 @@ impl Parser { return Ok(None); } - let mask = - u32::from_le_bytes(TryFrom::try_from(&src[idx..idx + 4]).unwrap()); + let mask = TryFrom::try_from(&src[idx..idx + 4]).unwrap(); + idx += 4; + Some(mask) } else { None @@ -187,8 +189,8 @@ impl Parser { }; if mask { - let mask = rand::random::(); - dst.put_u32_le(mask); + let mask = rand::random::<[u8; 4]>(); + dst.put_slice(mask.as_ref()); dst.put_slice(payload.as_ref()); let pos = dst.len() - payload_len; apply_mask(&mut dst[pos..], mask); diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs index 79e015f79..276ca4a85 100644 --- a/actix-http/src/ws/mask.rs +++ b/actix-http/src/ws/mask.rs @@ -1,136 +1,57 @@ //! This is code from [Tungstenite project](https://github.com/snapview/tungstenite-rs) -#![allow(clippy::cast_ptr_alignment)] -use std::ptr::copy_nonoverlapping; -use std::slice; -/// Holds a slice guaranteed to be shorter than 8 bytes. -struct ShortSlice<'a> { - inner: &'a mut [u8], -} - -impl<'a> ShortSlice<'a> { - /// # Safety - /// Given slice must be shorter than 8 bytes. - unsafe fn new(slice: &'a mut [u8]) -> Self { - // Sanity check for debug builds - debug_assert!(slice.len() < 8); - ShortSlice { inner: slice } - } - - fn len(&self) -> usize { - self.inner.len() - } -} - -/// Faster version of `apply_mask()` which operates on 8-byte blocks. +/// Mask/unmask a frame. #[inline] -#[allow(clippy::cast_lossless)] -pub(crate) fn apply_mask(buf: &mut [u8], mask_u32: u32) { - // Extend the mask to 64 bits - let mut mask_u64 = ((mask_u32 as u64) << 32) | (mask_u32 as u64); - // Split the buffer into three segments - let (head, mid, tail) = align_buf(buf); +pub fn apply_mask(buf: &mut [u8], mask: [u8; 4]) { + apply_mask_fast32(buf, mask) +} - // Initial unaligned segment - let head_len = head.len(); - if head_len > 0 { - xor_short(head, mask_u64); +/// A safe unoptimized mask application. +#[inline] +fn apply_mask_fallback(buf: &mut [u8], mask: [u8; 4]) { + for (i, byte) in buf.iter_mut().enumerate() { + *byte ^= mask[i & 3]; + } +} + +/// Faster version of `apply_mask()` which operates on 4-byte blocks. +#[inline] +pub fn apply_mask_fast32(buf: &mut [u8], mask: [u8; 4]) { + let mask_u32 = u32::from_ne_bytes(mask); + + // SAFETY: + // + // buf is a valid slice borrowed mutably from bytes::BytesMut. + // + // un aligned prefix and suffix would be mask/unmask per byte. + // proper aligned middle slice goes into fast path and operates on 4-byte blocks. + let (mut prefix, words, mut suffix) = unsafe { buf.align_to_mut::() }; + apply_mask_fallback(&mut prefix, mask); + let head = prefix.len() & 3; + let mask_u32 = if head > 0 { if cfg!(target_endian = "big") { - mask_u64 = mask_u64.rotate_left(8 * head_len as u32); + mask_u32.rotate_left(8 * head as u32) } else { - mask_u64 = mask_u64.rotate_right(8 * head_len as u32); - } - } - // Aligned segment - for v in mid { - *v ^= mask_u64; - } - // Final unaligned segment - if tail.len() > 0 { - xor_short(tail, mask_u64); - } -} - -// TODO: copy_nonoverlapping here compiles to call memcpy. While it is not so -// inefficient, it could be done better. The compiler does not understand that -// a `ShortSlice` must be smaller than a u64. -#[inline] -#[allow(clippy::needless_pass_by_value)] -fn xor_short(buf: ShortSlice<'_>, mask: u64) { - // SAFETY: we know that a `ShortSlice` fits in a u64 - unsafe { - let (ptr, len) = (buf.inner.as_mut_ptr(), buf.len()); - let mut b: u64 = 0; - #[allow(trivial_casts)] - copy_nonoverlapping(ptr, &mut b as *mut _ as *mut u8, len); - b ^= mask; - #[allow(trivial_casts)] - copy_nonoverlapping(&b as *const _ as *const u8, ptr, len); - } -} - -/// # Safety -/// Caller must ensure the buffer has the correct size and alignment. -#[inline] -unsafe fn cast_slice(buf: &mut [u8]) -> &mut [u64] { - // Assert correct size and alignment in debug builds - debug_assert!(buf.len().trailing_zeros() >= 3); - debug_assert!((buf.as_ptr() as usize).trailing_zeros() >= 3); - - slice::from_raw_parts_mut(buf.as_mut_ptr() as *mut u64, buf.len() >> 3) -} - -/// Splits a slice into three parts: -/// - an unaligned short head -/// - an aligned `u64` slice mid section -/// - an unaligned short tail -#[inline] -fn align_buf(buf: &mut [u8]) -> (ShortSlice<'_>, &mut [u64], ShortSlice<'_>) { - let start_ptr = buf.as_ptr() as usize; - let end_ptr = start_ptr + buf.len(); - - // Round *up* to next aligned boundary for start - let start_aligned = (start_ptr + 7) & !0x7; - // Round *down* to last aligned boundary for end - let end_aligned = end_ptr & !0x7; - - if end_aligned >= start_aligned { - // We have our three segments (head, mid, tail) - let (tmp, tail) = buf.split_at_mut(end_aligned - start_ptr); - let (head, mid) = tmp.split_at_mut(start_aligned - start_ptr); - - // SAFETY: we know the middle section is correctly aligned, and the outer - // sections are smaller than 8 bytes - unsafe { - ( - ShortSlice::new(head), - cast_slice(mid), - ShortSlice::new(tail), - ) + mask_u32.rotate_right(8 * head as u32) } } else { - // We didn't cross even one aligned boundary! - - // SAFETY: The outer sections are smaller than 8 bytes - unsafe { (ShortSlice::new(buf), &mut [], ShortSlice::new(&mut [])) } + mask_u32 + }; + for word in words.iter_mut() { + *word ^= mask_u32; } + apply_mask_fallback(&mut suffix, mask_u32.to_ne_bytes()); } #[cfg(test)] mod tests { - use super::apply_mask; - - /// A safe unoptimized mask application. - fn apply_mask_fallback(buf: &mut [u8], mask: &[u8; 4]) { - for (i, byte) in buf.iter_mut().enumerate() { - *byte ^= mask[i & 3]; - } - } + use super::*; + // legacy test from old apply mask test. kept for now for back compat test. + // TODO: remove it and favor the other test. #[test] - fn test_apply_mask() { + fn test_apply_mask_legacy() { let mask = [0x6d, 0xb6, 0xb2, 0x80]; - let mask_u32 = u32::from_le_bytes(mask); let unmasked = vec![ 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, @@ -140,10 +61,10 @@ mod tests { // Check masking with proper alignment. { let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked, &mask); + apply_mask_fallback(&mut masked, mask); let mut masked_fast = unmasked.clone(); - apply_mask(&mut masked_fast, mask_u32); + apply_mask(&mut masked_fast, mask); assert_eq!(masked, masked_fast); } @@ -151,12 +72,38 @@ mod tests { // Check masking without alignment. { let mut masked = unmasked.clone(); - apply_mask_fallback(&mut masked[1..], &mask); + apply_mask_fallback(&mut masked[1..], mask); let mut masked_fast = unmasked; - apply_mask(&mut masked_fast[1..], mask_u32); + apply_mask(&mut masked_fast[1..], mask); assert_eq!(masked, masked_fast); } } + + #[test] + fn test_apply_mask() { + let mask = [0x6d, 0xb6, 0xb2, 0x80]; + let unmasked = vec![ + 0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, + 0x74, 0xf9, 0x12, 0x03, + ]; + + for data_len in 0..=unmasked.len() { + let unmasked = &unmasked[0..data_len]; + // Check masking with different alignment. + for off in 0..=3 { + if unmasked.len() < off { + continue; + } + let mut masked = unmasked.to_vec(); + apply_mask_fallback(&mut masked[off..], mask); + + let mut masked_fast = unmasked.to_vec(); + apply_mask_fast32(&mut masked_fast[off..], mask); + + assert_eq!(masked, masked_fast); + } + } + } } From 75a9a72e78b5dad1cf599c05c3ada50525b8284e Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 11 Feb 2021 06:54:42 -0800 Subject: [PATCH 156/187] clean up poll_response. add comments (#1978) --- actix-http/src/h1/dispatcher.rs | 166 +++++++++++++++----------------- 1 file changed, 78 insertions(+), 88 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 3882c2d52..0389d1f8e 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -139,27 +139,14 @@ where fn is_empty(&self) -> bool { matches!(self, State::None) } - - fn is_call(&self) -> bool { - matches!(self, State::ServiceCall(_)) - } } + enum PollResponse { Upgrade(Request), DoNothing, DrainWriteBuf, } -impl PartialEq for PollResponse { - fn eq(&self, other: &PollResponse) -> bool { - match self { - PollResponse::DrainWriteBuf => matches!(other, PollResponse::DrainWriteBuf), - PollResponse::DoNothing => matches!(other, PollResponse::DoNothing), - _ => false, - } - } -} - impl Dispatcher where T: AsyncRead + AsyncWrite + Unpin, @@ -323,9 +310,10 @@ where message: Response<()>, body: ResponseBody, ) -> Result<(), DispatchError> { + let size = body.size(); let mut this = self.project(); this.codec - .encode(Message::Item((message, body.size())), &mut this.write_buf) + .encode(Message::Item((message, size)), &mut this.write_buf) .map_err(|err| { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Incomplete(None)); @@ -334,7 +322,7 @@ where })?; this.flags.set(Flags::KEEPALIVE, this.codec.keepalive()); - match body.size() { + match size { BodySize::None | BodySize::Empty => this.state.set(State::None), _ => this.state.set(State::SendPayload(body)), }; @@ -351,109 +339,111 @@ where mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { - loop { + 'res: loop { let mut this = self.as_mut().project(); - // state is not changed on Poll::Pending. - // other variant and conditions always trigger a state change(or an error). - let state_change = match this.state.project() { + match this.state.as_mut().project() { + // no future is in InnerDispatcher state. pop next message. StateProj::None => match this.messages.pop_front() { + // handle request message. Some(DispatcherMessage::Item(req)) => { - self.as_mut().handle_request(req, cx)?; - true + // Handle `EXPECT: 100-Continue` header + if req.head().expect() { + // set InnerDispatcher state and continue loop to poll it. + let task = this.flow.expect.call(req); + this.state.set(State::ExpectCall(task)); + } else { + // the same as expect call. + let task = this.flow.service.call(req); + this.state.set(State::ServiceCall(task)); + }; } + // handle error message. Some(DispatcherMessage::Error(res)) => { + // send_response would update InnerDispatcher state to SendPayload or + // None(If response body is empty). + // continue loop to poll it. self.as_mut() .send_response(res, ResponseBody::Other(Body::Empty))?; - true } + // return with upgrade request and poll it exclusively. Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } - None => false, - }, - StateProj::ExpectCall(fut) => match fut.poll(cx) { - Poll::Ready(Ok(req)) => { - self.as_mut().send_continue(); - this = self.as_mut().project(); - let fut = this.flow.service.call(req); - this.state.set(State::ServiceCall(fut)); - continue; - } - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); - let (res, body) = res.replace_body(()); - self.as_mut().send_response(res, body.into_body())?; - true - } - Poll::Pending => false, + // all messages are dealt with. + None => return Ok(PollResponse::DoNothing), }, StateProj::ServiceCall(fut) => match fut.poll(cx) { + // service call resolved. send response. Poll::Ready(Ok(res)) => { let (res, body) = res.into().replace_body(()); self.as_mut().send_response(res, body)?; - continue; } + // send service call error as response Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; - true } - Poll::Pending => false, + // service call pending and could be waiting for more chunk messages. + // (pipeline message limit and/or payload can_read limit) + Poll::Pending => { + // no new message is decoded and no new payload is feed. + // nothing to do except waiting for new incoming data from client. + if !self.as_mut().poll_request(cx)? { + return Ok(PollResponse::DoNothing); + } + // otherwise keep loop. + } }, StateProj::SendPayload(mut stream) => { - loop { - if this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { - match stream.as_mut().poll_next(cx) { - Poll::Ready(Some(Ok(item))) => { - this.codec.encode( - Message::Chunk(Some(item)), - &mut this.write_buf, - )?; - continue; - } - Poll::Ready(None) => { - this.codec.encode( - Message::Chunk(None), - &mut this.write_buf, - )?; - this = self.as_mut().project(); - this.state.set(State::None); - } - Poll::Ready(Some(Err(_))) => { - return Err(DispatchError::Unknown) - } - Poll::Pending => return Ok(PollResponse::DoNothing), + // keep populate writer buffer until buffer size limit hit, + // get blocked or finished. + while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { + match stream.as_mut().poll_next(cx) { + Poll::Ready(Some(Ok(item))) => { + this.codec.encode( + Message::Chunk(Some(item)), + &mut this.write_buf, + )?; } - } else { - return Ok(PollResponse::DrainWriteBuf); + Poll::Ready(None) => { + this.codec + .encode(Message::Chunk(None), &mut this.write_buf)?; + // payload stream finished. + // set state to None and handle next message + this.state.set(State::None); + continue 'res; + } + Poll::Ready(Some(Err(e))) => { + return Err(DispatchError::Service(e)) + } + Poll::Pending => return Ok(PollResponse::DoNothing), } - break; } - continue; + // buffer is beyond max size. + // return and try to write the whole buffer to io stream. + return Ok(PollResponse::DrainWriteBuf); } - }; - - // state is changed and continue when the state is not Empty - if state_change { - if !self.state.is_empty() { - continue; - } - } else { - // if read-backpressure is enabled and we consumed some data. - // we may read more data and retry - if self.state.is_call() { - if self.as_mut().poll_request(cx)? { - continue; + StateProj::ExpectCall(fut) => match fut.poll(cx) { + // expect resolved. write continue to buffer and set InnerDispatcher state + // to service call. + Poll::Ready(Ok(req)) => { + this.write_buf + .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); + let fut = this.flow.service.call(req); + this.state.set(State::ServiceCall(fut)); } - } else if !self.messages.is_empty() { - continue; - } + // send expect error as response + Poll::Ready(Err(e)) => { + let res: Response = e.into().into(); + let (res, body) = res.replace_body(()); + self.as_mut().send_response(res, body.into_body())?; + } + // expect must be solved before progress can be made. + Poll::Pending => return Ok(PollResponse::DoNothing), + }, } - break; } - - Ok(PollResponse::DoNothing) } fn handle_request( From ceace26ed4cf1fd753178fbe7726e6f18d8070d1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 11 Feb 2021 14:19:14 -0800 Subject: [PATCH 157/187] remove unused flag POLLED (#1980) --- actix-http/src/h1/dispatcher.rs | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 0389d1f8e..c4ccf72c3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -37,15 +37,14 @@ bitflags! { pub struct Flags: u8 { const STARTED = 0b0000_0001; const KEEPALIVE = 0b0000_0010; - const POLLED = 0b0000_0100; - const SHUTDOWN = 0b0000_1000; - const READ_DISCONNECT = 0b0001_0000; - const WRITE_DISCONNECT = 0b0010_0000; - const UPGRADE = 0b0100_0000; + const SHUTDOWN = 0b0000_0100; + const READ_DISCONNECT = 0b0000_1000; + const WRITE_DISCONNECT = 0b0001_0000; + const UPGRADE = 0b0010_0000; } } -#[pin_project::pin_project] +#[pin_project] /// Dispatcher for HTTP/1.1 protocol pub struct Dispatcher where From 871ca5e4ae2bdc22d1ea02701c2992fa8d04aed7 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 21:44:22 +0000 Subject: [PATCH 158/187] stop claiming actor support --- README.md | 1 - actix-http/src/h1/dispatcher.rs | 10 +++++++ src/lib.rs | 27 ----------------- src/test.rs | 53 --------------------------------- 4 files changed, 10 insertions(+), 81 deletions(-) diff --git a/README.md b/README.md index ff3755062..cc7c4cd52 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,6 @@ * SSL support using OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -* Supports [Actix actor framework](https://github.com/actix/actix) * Runs on stable Rust 1.46+ ## Documentation diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index c4ccf72c3..94bc5c442 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -356,6 +356,7 @@ where this.state.set(State::ServiceCall(task)); }; } + // handle error message. Some(DispatcherMessage::Error(res)) => { // send_response would update InnerDispatcher state to SendPayload or @@ -364,10 +365,12 @@ where self.as_mut() .send_response(res, ResponseBody::Other(Body::Empty))?; } + // return with upgrade request and poll it exclusively. Some(DispatcherMessage::Upgrade(req)) => { return Ok(PollResponse::Upgrade(req)); } + // all messages are dealt with. None => return Ok(PollResponse::DoNothing), }, @@ -377,12 +380,14 @@ where let (res, body) = res.into().replace_body(()); self.as_mut().send_response(res, body)?; } + // send service call error as response Poll::Ready(Err(e)) => { let res: Response = e.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } + // service call pending and could be waiting for more chunk messages. // (pipeline message limit and/or payload can_read limit) Poll::Pending => { @@ -394,6 +399,7 @@ where // otherwise keep loop. } }, + StateProj::SendPayload(mut stream) => { // keep populate writer buffer until buffer size limit hit, // get blocked or finished. @@ -405,6 +411,7 @@ where &mut this.write_buf, )?; } + Poll::Ready(None) => { this.codec .encode(Message::Chunk(None), &mut this.write_buf)?; @@ -413,9 +420,11 @@ where this.state.set(State::None); continue 'res; } + Poll::Ready(Some(Err(e))) => { return Err(DispatchError::Service(e)) } + Poll::Pending => return Ok(PollResponse::DoNothing), } } @@ -423,6 +432,7 @@ where // return and try to write the whole buffer to io stream. return Ok(PollResponse::DrainWriteBuf); } + StateProj::ExpectCall(fut) => match fut.poll(cx) { // expect resolved. write continue to buffer and set InnerDispatcher state // to service call. diff --git a/src/lib.rs b/src/lib.rs index 7f096584a..271efa0de 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -56,7 +56,6 @@ //! * SSL support using OpenSSL or Rustls //! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) //! * Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) -//! * Supports [Actix actor framework](https://github.com/actix/actix) //! * Runs on stable Rust 1.46+ //! //! ## Crate Features @@ -203,29 +202,3 @@ pub mod dev { } } } - -pub mod client { - //! Actix Web async HTTP client. - //! - //! ```rust - //! use actix_web::client::Client; - //! - //! #[actix_web::main] - //! async fn main() { - //! let mut client = Client::default(); - //! - //! // Create request builder and send request - //! let response = client.get("http://www.rust-lang.org") - //! .insert_header(("User-Agent", "actix-web/3.0")) - //! .send() // <- Send request - //! .await; // <- Wait for response - //! - //! println!("Response: {:?}", response); - //! } - //! ``` - - pub use awc::error::*; - pub use awc::{ - test, Client, ClientBuilder, ClientRequest, ClientResponse, Connector, - }; -} diff --git a/src/test.rs b/src/test.rs index 5da100b81..03113bd4a 100644 --- a/src/test.rs +++ b/src/test.rs @@ -1246,57 +1246,4 @@ mod tests { let res = app.call(req).await.unwrap(); assert!(res.status().is_success()); } - - #[actix_rt::test] - async fn test_actor() { - use crate::Error; - use actix::prelude::*; - - struct MyActor; - - impl Actor for MyActor { - type Context = Context; - } - - struct Num(usize); - - impl Message for Num { - type Result = usize; - } - - impl Handler for MyActor { - type Result = usize; - - fn handle(&mut self, msg: Num, _: &mut Self::Context) -> Self::Result { - msg.0 - } - } - - let addr = MyActor.start(); - - async fn actor_handler( - addr: Data>, - ) -> Result { - let res = addr - .send(Num(1)) - .await - .map_err(crate::error::ErrorInternalServerError)?; - - if res == 1 { - Ok(HttpResponse::Ok()) - } else { - Ok(HttpResponse::BadRequest()) - } - } - - let srv = App::new() - .data(addr.clone()) - .service(web::resource("/").to(actor_handler)); - - let app = init_service(srv).await; - - let req = TestRequest::post().uri("/").to_request(); - let res = app.call(req).await.unwrap(); - assert!(res.status().is_success()); - } } From 77efc09362a2eb7517bd47c29dea4a92a3de3d97 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 22:39:54 +0000 Subject: [PATCH 159/187] hide httpmessage mod --- actix-http-test/src/lib.rs | 20 ++++++++--------- actix-http/src/body.rs | 18 +++++++++------ actix-http/src/client/connector.rs | 8 ++++--- actix-http/src/client/error.rs | 22 ++++++++++++++----- actix-http/src/client/mod.rs | 3 ++- actix-http/src/clinu/mod.rs | 0 actix-http/src/config.rs | 10 ++++----- actix-http/src/encoding/decoder.rs | 6 ++--- actix-http/src/encoding/encoder.rs | 7 +++--- actix-http/src/encoding/mod.rs | 3 ++- actix-http/src/error.rs | 4 ++-- actix-http/src/h1/dispatcher.rs | 2 +- actix-http/src/h1/mod.rs | 2 +- actix-http/src/h2/mod.rs | 2 +- actix-http/src/h2/service.rs | 2 +- actix-http/src/header/as_name.rs | 2 ++ .../src/{httpcodes.rs => http_codes.rs} | 0 actix-http/src/lib.rs | 4 ++-- actix-http/src/request.rs | 10 +++++---- actix-http/src/response.rs | 6 ++--- actix-http/src/service.rs | 2 +- actix-http/tests/test_openssl.rs | 2 +- actix-http/tests/test_server.rs | 2 +- actix-web-actors/src/context.rs | 2 +- awc/src/builder.rs | 5 +++-- awc/src/lib.rs | 2 +- awc/src/request.rs | 6 ++--- awc/src/response.rs | 4 ++-- src/app.rs | 4 ++-- src/data.rs | 2 +- src/guard.rs | 22 +++++++++---------- src/request.rs | 10 +++++---- src/server.rs | 11 +++++----- src/service.rs | 8 ++++--- src/test.rs | 10 ++++----- 35 files changed, 125 insertions(+), 98 deletions(-) delete mode 100644 actix-http/src/clinu/mod.rs rename actix-http/src/{httpcodes.rs => http_codes.rs} (100%) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index fcf8b3476..f5c97262a 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -150,7 +150,7 @@ impl TestServer { } } - /// Construct test https server url + /// Construct test HTTPS server URL. pub fn surl(&self, uri: &str) -> String { if uri.starts_with('/') { format!("https://localhost:{}{}", self.addr.port(), uri) @@ -164,7 +164,7 @@ impl TestServer { self.client.get(self.url(path.as_ref()).as_str()) } - /// Create https `GET` request + /// Create HTTPS `GET` request pub fn sget>(&self, path: S) -> ClientRequest { self.client.get(self.surl(path.as_ref()).as_str()) } @@ -174,7 +174,7 @@ impl TestServer { self.client.post(self.url(path.as_ref()).as_str()) } - /// Create https `POST` request + /// Create HTTPS `POST` request pub fn spost>(&self, path: S) -> ClientRequest { self.client.post(self.surl(path.as_ref()).as_str()) } @@ -184,7 +184,7 @@ impl TestServer { self.client.head(self.url(path.as_ref()).as_str()) } - /// Create https `HEAD` request + /// Create HTTPS `HEAD` request pub fn shead>(&self, path: S) -> ClientRequest { self.client.head(self.surl(path.as_ref()).as_str()) } @@ -194,7 +194,7 @@ impl TestServer { self.client.put(self.url(path.as_ref()).as_str()) } - /// Create https `PUT` request + /// Create HTTPS `PUT` request pub fn sput>(&self, path: S) -> ClientRequest { self.client.put(self.surl(path.as_ref()).as_str()) } @@ -204,7 +204,7 @@ impl TestServer { self.client.patch(self.url(path.as_ref()).as_str()) } - /// Create https `PATCH` request + /// Create HTTPS `PATCH` request pub fn spatch>(&self, path: S) -> ClientRequest { self.client.patch(self.surl(path.as_ref()).as_str()) } @@ -214,7 +214,7 @@ impl TestServer { self.client.delete(self.url(path.as_ref()).as_str()) } - /// Create https `DELETE` request + /// Create HTTPS `DELETE` request pub fn sdelete>(&self, path: S) -> ClientRequest { self.client.delete(self.surl(path.as_ref()).as_str()) } @@ -224,12 +224,12 @@ impl TestServer { self.client.options(self.url(path.as_ref()).as_str()) } - /// Create https `OPTIONS` request + /// Create HTTPS `OPTIONS` request pub fn soptions>(&self, path: S) -> ClientRequest { self.client.options(self.surl(path.as_ref()).as_str()) } - /// Connect to test http server + /// Connect to test HTTP server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) } @@ -263,7 +263,7 @@ impl TestServer { self.ws_at("/").await } - /// Stop http server + /// Stop HTTP server fn stop(&mut self) { self.system.stop(); } diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index d5d1989d4..0dbe93a4a 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -1,6 +1,10 @@ -use std::pin::Pin; -use std::task::{Context, Poll}; -use std::{fmt, mem}; +//! Traits and structures to aid consuming and writing HTTP payloads. + +use std::{ + fmt, mem, + pin::Pin, + task::{Context, Poll}, +}; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; @@ -8,8 +12,8 @@ use pin_project::pin_project; use crate::error::Error; +/// Body size hint. #[derive(Debug, PartialEq, Copy, Clone)] -/// Body size hint pub enum BodySize { None, Empty, @@ -23,7 +27,7 @@ impl BodySize { } } -/// Type that provides this trait can be streamed to a peer. +/// Type that implement this trait can be streamed to a peer. pub trait MessageBody { fn size(&self) -> BodySize; @@ -80,7 +84,7 @@ impl ResponseBody { impl ResponseBody { pub fn take_body(&mut self) -> ResponseBody { - std::mem::replace(self, ResponseBody::Other(Body::None)) + mem::replace(self, ResponseBody::Other(Body::None)) } } @@ -127,7 +131,7 @@ impl Stream for ResponseBody { } } -/// Represents various types of http message body. +/// Represents various types of HTTP message body. pub enum Body { /// Empty response. `Content-Length` header is not set. None, diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 7c8e2b2a0..3bf424d49 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -34,7 +34,8 @@ enum SslConnector { #[cfg(not(any(feature = "openssl", feature = "rustls")))] type SslConnector = (); -/// Manages http client network connectivity +/// Manages HTTP client network connectivity. +/// /// The `Connector` type uses a builder-like combinator pattern for service /// construction that finishes by calling the `.finish()` method. /// @@ -160,8 +161,9 @@ where self } - /// Maximum supported http major version - /// Supported versions http/1.1, http/2 + /// Maximum supported HTTP major version. + /// + /// Supported versions are HTTP/1.1 and HTTP/2. pub fn max_http_version(mut self, val: http::Version) -> Self { let versions = match val { http::Version::HTTP_11 => vec![b"http/1.1".to_vec()], diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 8d609f546..7768462b8 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -65,13 +65,16 @@ impl From for ConnectError { #[derive(Debug, Display, From)] pub enum InvalidUrl { - #[display(fmt = "Missing url scheme")] + #[display(fmt = "Missing URL scheme")] MissingScheme, - #[display(fmt = "Unknown url scheme")] + + #[display(fmt = "Unknown URL scheme")] UnknownScheme, + #[display(fmt = "Missing host name")] MissingHost, - #[display(fmt = "Url parse error: {}", _0)] + + #[display(fmt = "URL parse error: {}", _0)] HttpError(http::Error), } @@ -83,25 +86,33 @@ pub enum SendRequestError { /// Invalid URL #[display(fmt = "Invalid URL: {}", _0)] Url(InvalidUrl), + /// Failed to connect to host #[display(fmt = "Failed to connect to host: {}", _0)] Connect(ConnectError), + /// Error sending request Send(io::Error), + /// Error parsing response Response(ParseError), + /// Http error #[display(fmt = "{}", _0)] Http(HttpError), + /// Http2 error #[display(fmt = "{}", _0)] H2(h2::Error), + /// Response took too long #[display(fmt = "Timeout while waiting for response")] Timeout, - /// Tunnels are not supported for http2 connection + + /// Tunnels are not supported for HTTP/2 connection #[display(fmt = "Tunnels are not supported for http2 connection")] TunnelNotSupported, + /// Error sending request body Body(Error), } @@ -127,7 +138,8 @@ pub enum FreezeRequestError { /// Invalid URL #[display(fmt = "Invalid URL: {}", _0)] Url(InvalidUrl), - /// Http error + + /// HTTP error #[display(fmt = "{}", _0)] Http(HttpError), } diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index dd1e9b25a..9c7f632ea 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -1,4 +1,5 @@ -//! Http client api +//! HTTP client. + use http::Uri; mod config; diff --git a/actix-http/src/clinu/mod.rs b/actix-http/src/clinu/mod.rs deleted file mode 100644 index e69de29bb..000000000 diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index f178db274..61708cfc3 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -95,32 +95,32 @@ impl ServiceConfig { })) } + /// Returns true if connection is secure (HTTPS) #[inline] - /// Returns true if connection is secure(https) pub fn secure(&self) -> bool { self.0.secure } - #[inline] /// Returns the local address that this server is bound to. + #[inline] pub fn local_addr(&self) -> Option { self.0.local_addr } - #[inline] /// Keep alive duration if configured. + #[inline] pub fn keep_alive(&self) -> Option { self.0.keep_alive } - #[inline] /// Return state of connection keep-alive functionality + #[inline] pub fn keep_alive_enabled(&self) -> bool { self.0.ka_enabled } - #[inline] /// Client timeout for first request. + #[inline] pub fn client_timer(&self) -> Option { let delay_time = self.0.client_timeout; if delay_time != 0 { diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index 2cf2f6e03..e16d42288 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,7 +1,7 @@ -use std::future::Future; -use std::io::{self, Write}; -use std::pin::Pin; +//! Stream decoders. + use std::task::{Context, Poll}; +use std::{future::Future, io, io::Write as _, pin::Pin}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliDecoder; diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 1d4a8e933..3a413a150 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,8 +1,7 @@ -//! Stream encoder -use std::future::Future; -use std::io::{self, Write}; -use std::pin::Pin; +//! Stream encoders. + use std::task::{Context, Poll}; +use std::{future::Future, io, io::Write as _, pin::Pin}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliEncoder; diff --git a/actix-http/src/encoding/mod.rs b/actix-http/src/encoding/mod.rs index 9eaf4104e..cb271c638 100644 --- a/actix-http/src/encoding/mod.rs +++ b/actix-http/src/encoding/mod.rs @@ -1,4 +1,5 @@ -//! Content-Encoding support +//! Content-Encoding support. + use std::io; use bytes::{Bytes, BytesMut}; diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index cea1f5d4f..5eb3c157a 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -38,7 +38,7 @@ pub type Result = result::Result; /// converting errors with `into()`. /// /// Whenever it is created from an external object a response error is created -/// for it that can be used to create an http response from it this means that +/// for it that can be used to create an HTTP response from it this means that /// if you have access to an actix `Error` you can always get a /// `ResponseError` reference from it. pub struct Error { @@ -404,7 +404,7 @@ impl ResponseError for crate::cookie::ParseError { } #[derive(Debug, Display, From)] -/// A set of errors that can occur during dispatching http requests +/// A set of errors that can occur during dispatching HTTP requests pub enum DispatchError { /// Service error Service(Error), diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 94bc5c442..8d451fdad 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -178,7 +178,7 @@ where ) } - /// Create http/1 dispatcher with slow request timeout. + /// Create HTTP/1 dispatcher with slow request timeout. pub(crate) fn with_timeout( io: T, codec: Codec, diff --git a/actix-http/src/h1/mod.rs b/actix-http/src/h1/mod.rs index 3d5dea5d6..7e6df6ceb 100644 --- a/actix-http/src/h1/mod.rs +++ b/actix-http/src/h1/mod.rs @@ -1,4 +1,4 @@ -//! HTTP/1 implementation +//! HTTP/1 protocol implementation. use bytes::{Bytes, BytesMut}; mod client; diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index c05ee609d..7eff44ac1 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -1,4 +1,4 @@ -//! HTTP/2 implementation. +//! HTTP/2 protocol. use std::{ pin::Pin, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 36c76b17c..e00c8d968 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -243,7 +243,7 @@ where } } -/// `Service` implementation for http/2 transport +/// `Service` implementation for HTTP/2 transport pub struct H2ServiceHandler where S: Service, diff --git a/actix-http/src/header/as_name.rs b/actix-http/src/header/as_name.rs index 68456e3f7..af81ff7f2 100644 --- a/actix-http/src/header/as_name.rs +++ b/actix-http/src/header/as_name.rs @@ -1,4 +1,6 @@ //! Helper trait for types that can be effectively borrowed as a [HeaderValue]. +//! +//! [HeaderValue]: crate::http::HeaderValue use std::{borrow::Cow, str::FromStr}; diff --git a/actix-http/src/httpcodes.rs b/actix-http/src/http_codes.rs similarity index 100% rename from actix-http/src/httpcodes.rs rename to actix-http/src/http_codes.rs diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index cc99130eb..15866a3d8 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -25,8 +25,8 @@ pub mod encoding; mod extensions; mod header; mod helpers; -mod httpcodes; -pub mod httpmessage; +mod http_codes; +mod httpmessage; mod message; mod payload; mod request; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index fbf4d3c23..1ac2f33d6 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -107,7 +107,7 @@ impl

Request

{ #[inline] #[doc(hidden)] - /// Mutable reference to a http message part of the request + /// Mutable reference to a HTTP message part of the request pub fn head_mut(&mut self) -> &mut RequestHead { &mut *self.head } @@ -158,10 +158,12 @@ impl

Request

{ diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs index 2cde5e8cf..8cd1c8e0c 100644 --- a/actix-multipart/src/server.rs +++ b/actix-multipart/src/server.rs @@ -13,9 +13,7 @@ use futures_util::stream::{LocalBoxStream, Stream, StreamExt}; use actix_utils::task::LocalWaker; use actix_web::error::{ParseError, PayloadError}; -use actix_web::http::header::{ - self, ContentDisposition, HeaderMap, HeaderName, HeaderValue, -}; +use actix_web::http::header::{self, ContentDisposition, HeaderMap, HeaderName, HeaderValue}; use crate::error::MultipartError; @@ -120,10 +118,7 @@ impl Multipart { impl Stream for Multipart { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if let Some(err) = self.error.take() { Poll::Ready(Some(Err(err))) } else if self.safety.current() { @@ -142,9 +137,7 @@ impl Stream for Multipart { } impl InnerMultipart { - fn read_headers( - payload: &mut PayloadBuffer, - ) -> Result, MultipartError> { + fn read_headers(payload: &mut PayloadBuffer) -> Result, MultipartError> { match payload.read_until(b"\r\n\r\n")? { None => { if payload.eof { @@ -226,8 +219,7 @@ impl InnerMultipart { if chunk.len() < boundary.len() { continue; } - if &chunk[..2] == b"--" - && &chunk[2..chunk.len() - 2] == boundary.as_bytes() + if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() { break; } else { @@ -273,9 +265,7 @@ impl InnerMultipart { match field.borrow_mut().poll(safety) { Poll::Pending => return Poll::Pending, Poll::Ready(Some(Ok(_))) => continue, - Poll::Ready(Some(Err(e))) => { - return Poll::Ready(Some(Err(e))) - } + Poll::Ready(Some(Err(e))) => return Poll::Ready(Some(Err(e))), Poll::Ready(None) => true, } } @@ -311,10 +301,7 @@ impl InnerMultipart { } // read boundary InnerState::Boundary => { - match InnerMultipart::read_boundary( - &mut *payload, - &self.boundary, - )? { + match InnerMultipart::read_boundary(&mut *payload, &self.boundary)? { None => return Poll::Pending, Some(eof) => { if eof { @@ -418,8 +405,7 @@ impl Field { pub fn content_disposition(&self) -> Option { // RFC 7578: 'Each part MUST contain a Content-Disposition header field // where the disposition type is "form-data".' - if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) - { + if let Some(content_disposition) = self.headers.get(&header::CONTENT_DISPOSITION) { ContentDisposition::from_raw(content_disposition).ok() } else { None @@ -430,15 +416,10 @@ impl Field { impl Stream for Field { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.safety.current() { let mut inner = self.inner.borrow_mut(); - if let Some(mut payload) = - inner.payload.as_ref().unwrap().get_mut(&self.safety) - { + if let Some(mut payload) = inner.payload.as_ref().unwrap().get_mut(&self.safety) { payload.poll_stream(cx)?; } inner.poll(&self.safety) @@ -607,8 +588,7 @@ impl InnerField { return Poll::Ready(None); } - let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) - { + let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) { if !self.eof { let res = if let Some(ref mut len) = self.length { InnerField::read_len(&mut *payload, len) @@ -628,7 +608,9 @@ impl InnerField { Ok(None) => Poll::Pending, Ok(Some(line)) => { if line.as_ref() != b"\r\n" { - log::warn!("multipart field did not read all the data or it is malformed"); + log::warn!( + "multipart field did not read all the data or it is malformed" + ); } Poll::Ready(None) } @@ -804,9 +786,7 @@ impl PayloadBuffer { /// Read bytes until new line delimiter or eof pub fn readline_or_eof(&mut self) -> Result, MultipartError> { match self.readline() { - Err(MultipartError::Incomplete) if self.eof => { - Ok(Some(self.buf.split().freeze())) - } + Err(MultipartError::Incomplete) if self.eof => Ok(Some(self.buf.split().freeze())), line => line, } } @@ -902,10 +882,7 @@ mod tests { impl Stream for SlowStream { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); if !this.ready { this.ready = true; diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index c452025af..bd6635cba 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -3,9 +3,7 @@ use std::future::Future; use std::pin::Pin; use std::task::{Context, Poll}; -use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope, -}; +use actix::dev::{AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, ToEnvelope}; use actix::fut::ActorFuture; use actix::{ Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message, SpawnHandle, @@ -165,10 +163,7 @@ where { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { if self.fut.alive() { let _ = Pin::new(&mut self.fut).poll(cx); } @@ -233,10 +228,11 @@ mod tests { #[actix_rt::test] async fn test_default_resource() { - let srv = init_service(App::new().service(web::resource("/test").to(|| { - HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) - }))) - .await; + let srv = + init_service(App::new().service(web::resource("/test").to(|| { + HttpResponse::Ok().streaming(HttpContext::create(MyActor { count: 0 })) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&srv, req).await; diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 10113665b..3eb8cc1e7 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -7,13 +7,12 @@ use std::task::{Context, Poll}; use std::{collections::VecDeque, convert::TryFrom}; use actix::dev::{ - AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, - ToEnvelope, + AsyncContextParts, ContextFut, ContextParts, Envelope, Mailbox, StreamHandler, ToEnvelope, }; use actix::fut::ActorFuture; use actix::{ - Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, - Message as ActixMessage, SpawnHandle, + Actor, ActorContext, ActorState, Addr, AsyncContext, Handler, Message as ActixMessage, + SpawnHandle, }; use actix_codec::{Decoder, Encoder}; use actix_http::ws::{hash_key, Codec}; @@ -32,8 +31,7 @@ use tokio::sync::oneshot::Sender; /// Perform WebSocket handshake and start actor. pub fn start(actor: A, req: &HttpRequest, stream: T) -> Result where - A: Actor> - + StreamHandler>, + A: Actor> + StreamHandler>, T: Stream> + 'static, { let mut res = handshake(req)?; @@ -57,8 +55,7 @@ pub fn start_with_addr( stream: T, ) -> Result<(Addr, HttpResponse), Error> where - A: Actor> - + StreamHandler>, + A: Actor> + StreamHandler>, T: Stream> + 'static, { let mut res = handshake(req)?; @@ -76,8 +73,7 @@ pub fn start_with_protocols( stream: T, ) -> Result where - A: Actor> - + StreamHandler>, + A: Actor> + StreamHandler>, T: Stream> + 'static, { let mut res = handshake_with_protocols(req, protocols)?; @@ -301,10 +297,7 @@ where } /// Create a new Websocket context - pub fn with_factory( - stream: S, - f: F, - ) -> impl Stream> + pub fn with_factory(stream: S, f: F) -> impl Stream> where F: FnOnce(&mut Self) -> A + 'static, A: StreamHandler>, @@ -423,10 +416,7 @@ where { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); if this.fut.alive() { @@ -493,10 +483,7 @@ where { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut this = self.as_mut().project(); if !*this.closed { @@ -512,9 +499,10 @@ where } Poll::Pending => break, Poll::Ready(Some(Err(e))) => { - return Poll::Ready(Some(Err(ProtocolError::Io( - io::Error::new(io::ErrorKind::Other, format!("{}", e)), - )))); + return Poll::Ready(Some(Err(ProtocolError::Io(io::Error::new( + io::ErrorKind::Other, + format!("{}", e), + ))))); } } } diff --git a/actix-web-actors/tests/test_ws.rs b/actix-web-actors/tests/test_ws.rs index 7fd59a4a7..912480ae4 100644 --- a/actix-web-actors/tests/test_ws.rs +++ b/actix-web-actors/tests/test_ws.rs @@ -11,11 +11,7 @@ impl Actor for Ws { } impl StreamHandler> for Ws { - fn handle( - &mut self, - msg: Result, - ctx: &mut Self::Context, - ) { + fn handle(&mut self, msg: Result, ctx: &mut Self::Context) { match msg.unwrap() { ws::Message::Ping(msg) => ctx.pong(&msg), ws::Message::Text(text) => ctx.text(text), @@ -30,9 +26,7 @@ impl StreamHandler> for Ws { async fn test_simple() { let mut srv = test::start(|| { App::new().service(web::resource("/").to( - |req: HttpRequest, stream: web::Payload| async move { - ws::start(Ws, &req, stream) - }, + |req: HttpRequest, stream: web::Payload| async move { ws::start(Ws, &req, stream) }, )) }); diff --git a/actix-web-codegen/tests/test_macro.rs b/actix-web-codegen/tests/test_macro.rs index 252be1b95..34cdad649 100644 --- a/actix-web-codegen/tests/test_macro.rs +++ b/actix-web-codegen/tests/test_macro.rs @@ -4,9 +4,7 @@ use std::task::{Context, Poll}; use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform}; use actix_web::http::header::{HeaderName, HeaderValue}; use actix_web::{http, test, web::Path, App, Error, HttpResponse, Responder}; -use actix_web_codegen::{ - connect, delete, get, head, options, patch, post, put, route, trace, -}; +use actix_web_codegen::{connect, delete, get, head, options, patch, post, put, route, trace}; use futures_util::future::{self, LocalBoxFuture}; // Make sure that we can name function as 'config' diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 5450550a7..a9b8f9f83 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -82,8 +82,7 @@ where let connection = fut.await?; // send request - let (head, framed) = - connection.open_tunnel(RequestHeadType::from(head)).await?; + let (head, framed) = connection.open_tunnel(RequestHeadType::from(head)).await?; let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); Ok((head, framed)) @@ -142,10 +141,7 @@ impl AsyncWrite for BoxedSocket { Pin::new(self.get_mut().0.as_write()).poll_flush(cx) } - fn poll_shutdown( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(self.get_mut().0.as_write()).poll_shutdown(cx) } } diff --git a/awc/src/error.rs b/awc/src/error.rs index d008166d9..c60339f76 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,7 +1,5 @@ //! Http client errors -pub use actix_http::client::{ - ConnectError, FreezeRequestError, InvalidUrl, SendRequestError, -}; +pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use actix_http::error::PayloadError; pub use actix_http::http::Error as HttpError; pub use actix_http::ws::HandshakeError as WsHandshakeError; diff --git a/awc/src/request.rs b/awc/src/request.rs index 3ec43f3e5..db8196c5b 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -11,8 +11,7 @@ use actix_http::body::Body; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ - uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, - Version, + uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version, }; use actix_http::{Error, RequestHead}; @@ -520,15 +519,11 @@ impl ClientRequest { .unwrap_or(true); if https { - slf = - slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING)) } else { #[cfg(any(feature = "flate2-zlib", feature = "flate2-rust"))] { - slf = slf.insert_header_if_none(( - header::ACCEPT_ENCODING, - "gzip, deflate", - )) + slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate")) } }; } diff --git a/awc/src/response.rs b/awc/src/response.rs index f5161f9c1..1c887ee4e 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -50,8 +50,7 @@ impl HttpMessage for ClientResponse { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); for hdr in self.headers().get_all(&SET_COOKIE) { - let s = std::str::from_utf8(hdr.as_bytes()) - .map_err(CookieParseError::from)?; + let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; cookies.push(Cookie::parse_encoded(s)?.into_owned()); } self.extensions_mut().insert(Cookies(cookies)); @@ -132,10 +131,7 @@ where { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.get_mut().payload).poll_next(cx) } } @@ -306,9 +302,7 @@ where if let Some(len) = self.length.take() { if len > self.fut.as_ref().unwrap().limit { - return Poll::Ready(Err(JsonPayloadError::Payload( - PayloadError::Overflow, - ))); + return Poll::Ready(Err(JsonPayloadError::Payload(PayloadError::Overflow))); } } @@ -374,8 +368,7 @@ mod tests { _ => unreachable!("error"), } - let mut req = - TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); + let mut req = TestResponse::with_header(header::CONTENT_LENGTH, "1000000").finish(); match req.body().await.err().unwrap() { PayloadError::Overflow => {} _ => unreachable!("error"), diff --git a/awc/src/sender.rs b/awc/src/sender.rs index ca220be71..a72b129f8 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -81,8 +81,7 @@ impl SendClientRequest { #[cfg(feature = "compress")] impl Future for SendClientRequest { - type Output = - Result>>, SendRequestError>; + type Output = Result>>, SendRequestError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let this = self.get_mut(); @@ -99,15 +98,9 @@ impl Future for SendClientRequest { let res = futures_core::ready!(Pin::new(send).poll(cx)).map(|res| { res.map_body(|head, payload| { if *response_decompress { - Payload::Stream(Decoder::from_headers( - payload, - &head.headers, - )) + Payload::Stream(Decoder::from_headers(payload, &head.headers)) } else { - Payload::Stream(Decoder::new( - payload, - ContentEncoding::Identity, - )) + Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) } }) }); @@ -189,11 +182,11 @@ impl RequestSender { B: Into, { let fut = match self { - RequestSender::Owned(head) => config.connector.send_request( - RequestHeadType::Owned(head), - body.into(), - addr, - ), + RequestSender::Owned(head) => { + config + .connector + .send_request(RequestHeadType::Owned(head), body.into(), addr) + } RequestSender::Rc(head, extra_headers) => config.connector.send_request( RequestHeadType::Rc(head, extra_headers), body.into(), @@ -217,8 +210,7 @@ impl RequestSender { Err(e) => return Error::from(e).into(), }; - if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") - { + if let Err(e) = self.set_header_if_none(header::CONTENT_TYPE, "application/json") { return e.into(); } @@ -245,10 +237,9 @@ impl RequestSender { }; // set content-type - if let Err(e) = self.set_header_if_none( - header::CONTENT_TYPE, - "application/x-www-form-urlencoded", - ) { + if let Err(e) = + self.set_header_if_none(header::CONTENT_TYPE, "application/x-www-form-urlencoded") + { return e.into(); } @@ -292,11 +283,7 @@ impl RequestSender { self.send_body(addr, response_decompress, timeout, config, Body::Empty) } - fn set_header_if_none( - &mut self, - key: HeaderName, - value: V, - ) -> Result<(), HttpError> + fn set_header_if_none(&mut self, key: HeaderName, value: V) -> Result<(), HttpError> where V: IntoHeaderValue, { diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 7c795226b..8994af7ed 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -40,12 +40,8 @@ pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; use crate::connect::BoxedSocket; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; -use crate::http::header::{ - self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION, -}; -use crate::http::{ - ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version, -}; +use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}; +use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version}; use crate::response::ClientResponse; use crate::ClientConfig; diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index a9f5dc370..7e74d226e 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -51,8 +51,7 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ #[actix_rt::test] async fn test_simple() { let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let request = srv.get("/").insert_header(("x-test", "111")).send(); @@ -612,9 +611,8 @@ async fn test_client_streaming_explicit() { })) }); - let body = stream::once(async { - Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) - }); + let body = + stream::once(async { Ok::<_, actix_http::Error>(Bytes::from_static(STR.as_bytes())) }); let req = srv.post("/").send_stream(Box::pin(body)); let mut res = req.await.unwrap(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index b0f3b71be..a928715a8 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -64,8 +64,7 @@ async fn test_connection_reuse_h2() { .and_then( HttpService::build() .h2(map_config( - App::new() - .service(web::resource("/").route(web::to(HttpResponse::Ok))), + App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) .rustls(tls_config()) diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 064824d59..b93c729e5 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -48,8 +48,7 @@ async fn test_connection_reuse_h2() { .and_then( HttpService::build() .h2(map_config( - App::new() - .service(web::resource("/").route(web::to(HttpResponse::Ok))), + App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) .openssl(ssl_acceptor()) diff --git a/benches/server.rs b/benches/server.rs index ce79f077d..2471dd74c 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -33,9 +33,8 @@ fn bench_async_burst(c: &mut Criterion) { let srv = rt.block_on(async { test::start(|| { - App::new().service( - web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR))), - ) + App::new() + .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }) }); diff --git a/examples/basic.rs b/examples/basic.rs index e8ad5fcdb..99eef3ee1 100644 --- a/examples/basic.rs +++ b/examples/basic.rs @@ -29,12 +29,8 @@ async fn main() -> std::io::Result<()> { .service(no_params) .service( web::resource("/resource2/index.html") - .wrap( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), - ) + .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) + .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/examples/uds.rs b/examples/uds.rs index c0a5d76a6..096781984 100644 --- a/examples/uds.rs +++ b/examples/uds.rs @@ -33,12 +33,8 @@ async fn main() -> std::io::Result<()> { .service(no_params) .service( web::resource("/resource2/index.html") - .wrap( - middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"), - ) - .default_service( - web::route().to(|| HttpResponse::MethodNotAllowed()), - ) + .wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3")) + .default_service(web::route().to(|| HttpResponse::MethodNotAllowed())) .route(web::get().to(index_async)), ) .service(web::resource("/test1.html").to(|| async { "Test\r\n" })) diff --git a/rustfmt.toml b/rustfmt.toml index 94bd11d51..973e002c0 100644 --- a/rustfmt.toml +++ b/rustfmt.toml @@ -1,2 +1,2 @@ -max_width = 89 +max_width = 96 reorder_imports = true diff --git a/src/app.rs b/src/app.rs index 123076fc8..7a26a3a89 100644 --- a/src/app.rs +++ b/src/app.rs @@ -8,8 +8,7 @@ use actix_http::body::{Body, MessageBody}; use actix_http::{Extensions, Request}; use actix_service::boxed::{self, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, - Transform, + apply, apply_fn_factory, IntoServiceFactory, ServiceFactory, ServiceFactoryExt, Transform, }; use futures_util::future::FutureExt; @@ -473,17 +472,13 @@ mod tests { use crate::http::{header, HeaderValue, Method, StatusCode}; use crate::middleware::DefaultHeaders; use crate::service::ServiceRequest; - use crate::test::{ - call_service, init_service, read_body, try_init_service, TestRequest, - }; + use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest}; use crate::{web, HttpRequest, HttpResponse}; #[actix_rt::test] async fn test_default_resource() { - let srv = init_service( - App::new().service(web::resource("/test").to(HttpResponse::Ok)), - ) - .await; + let srv = + init_service(App::new().service(web::resource("/test").to(HttpResponse::Ok))).await; let req = TestRequest::with_uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); @@ -525,20 +520,22 @@ mod tests { #[actix_rt::test] async fn test_data_factory() { - let srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .data_factory(|| ok::<_, ()>(10usize)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let srv = - init_service(App::new().data_factory(|| ok::<_, ()>(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .data_factory(|| ok::<_, ()>(10u32)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -546,23 +543,24 @@ mod tests { #[actix_rt::test] async fn test_data_factory_errors() { - let srv = - try_init_service(App::new().data_factory(|| err::(())).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = try_init_service( + App::new() + .data_factory(|| err::(())) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; assert!(srv.is_err()); } #[actix_rt::test] async fn test_extension() { - let srv = init_service(App::new().app_data(10usize).service( - web::resource("/").to(|req: HttpRequest| { + let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to( + |req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 10); HttpResponse::Ok() - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); @@ -617,10 +615,8 @@ mod tests { let fut = srv.call(req); async move { let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); Ok(res) } }) @@ -645,10 +641,8 @@ mod tests { let fut = srv.call(req); async { let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); Ok(res) } }), @@ -671,9 +665,8 @@ mod tests { .route( "/test", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["12345"]).unwrap().to_string(), - ) + HttpResponse::Ok() + .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) }), ), ) diff --git a/src/app_service.rs b/src/app_service.rs index a38f1d652..9b4ae3354 100644 --- a/src/app_service.rs +++ b/src/app_service.rs @@ -241,16 +241,15 @@ impl ServiceFactory for AppRoutingFactory { fn new_service(&self, _: ()) -> Self::Future { // construct all services factory future with it's resource def and guards. - let factory_fut = - join_all(self.services.iter().map(|(path, factory, guards)| { - let path = path.clone(); - let guards = guards.borrow_mut().take(); - let factory_fut = factory.new_service(()); - async move { - let service = factory_fut.await?; - Ok((path, guards, service)) - } - })); + let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { + let path = path.clone(); + let guards = guards.borrow_mut().take(); + let factory_fut = factory.new_service(()); + async move { + let service = factory_fut.await?; + Ok((path, guards, service)) + } + })); // construct default service factory future let default_fut = self.default.new_service(()); diff --git a/src/config.rs b/src/config.rs index 24afca295..bd9a25c6f 100644 --- a/src/config.rs +++ b/src/config.rs @@ -17,8 +17,7 @@ use crate::service::{ }; type Guards = Vec>; -type HttpNewService = - boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; +type HttpNewService = boxed::BoxServiceFactory<(), ServiceRequest, ServiceResponse, Error, ()>; /// Application configuration pub struct AppService { @@ -99,12 +98,8 @@ impl AppService { InitError = (), > + 'static, { - self.services.push(( - rdef, - boxed::factory(factory.into_factory()), - guards, - nested, - )); + self.services + .push((rdef, boxed::factory(factory.into_factory()), guards, nested)); } } @@ -263,12 +258,12 @@ mod tests { cfg.app_data(15u8); }; - let srv = init_service(App::new().configure(cfg).service( - web::resource("/").to(|_: web::Data, req: HttpRequest| { + let srv = init_service(App::new().configure(cfg).service(web::resource("/").to( + |_: web::Data, req: HttpRequest| { assert_eq!(*req.app_data::().unwrap(), 15u8); HttpResponse::Ok() - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); @@ -312,17 +307,13 @@ mod tests { let srv = init_service( App::new() .configure(|cfg| { - cfg.external_resource( - "youtube", - "https://youtube.com/watch/{video_id}", - ); + cfg.external_resource("youtube", "https://youtube.com/watch/{video_id}"); }) .route( "/test", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["12345"]).unwrap().to_string(), - ) + HttpResponse::Ok() + .body(req.url_for("youtube", &["12345"]).unwrap().to_string()) }), ), ) @@ -337,10 +328,8 @@ mod tests { #[actix_rt::test] async fn test_service() { let srv = init_service(App::new().configure(|cfg| { - cfg.service( - web::resource("/test").route(web::get().to(HttpResponse::Created)), - ) - .route("/index.html", web::get().to(HttpResponse::Ok)); + cfg.service(web::resource("/test").route(web::get().to(HttpResponse::Created))) + .route("/index.html", web::get().to(HttpResponse::Ok)); })) .await; diff --git a/src/data.rs b/src/data.rs index b3f3d643c..133248212 100644 --- a/src/data.rs +++ b/src/data.rs @@ -156,11 +156,12 @@ mod tests { let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let srv = - init_service(App::new().data(10u32).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .data(10u32) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -186,21 +187,23 @@ mod tests { #[actix_rt::test] async fn test_app_data_extractor() { - let srv = - init_service(App::new().app_data(Data::new(10usize)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .app_data(Data::new(10usize)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::OK); - let srv = - init_service(App::new().app_data(Data::new(10u32)).service( - web::resource("/").to(|_: web::Data| HttpResponse::Ok()), - )) - .await; + let srv = init_service( + App::new() + .app_data(Data::new(10u32)) + .service(web::resource("/").to(|_: web::Data| HttpResponse::Ok())), + ) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR); @@ -237,15 +240,16 @@ mod tests { #[actix_rt::test] async fn test_override_data() { - let srv = init_service(App::new().data(1usize).service( - web::resource("/").data(10usize).route(web::get().to( - |data: web::Data| { - assert_eq!(**data, 10); - HttpResponse::Ok() - }, - )), - )) - .await; + let srv = + init_service(App::new().data(1usize).service( + web::resource("/").data(10usize).route(web::get().to( + |data: web::Data| { + assert_eq!(**data, 10); + HttpResponse::Ok() + }, + )), + )) + .await; let req = TestRequest::default().to_request(); let resp = srv.call(req).await.unwrap(); diff --git a/src/error.rs b/src/error.rs index c0d6f8af9..1d7c781d8 100644 --- a/src/error.rs +++ b/src/error.rs @@ -92,9 +92,7 @@ impl std::error::Error for JsonPayloadError {} impl ResponseError for JsonPayloadError { fn error_response(&self) -> HttpResponse { match *self { - JsonPayloadError::Overflow => { - HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE) - } + JsonPayloadError::Overflow => HttpResponse::new(StatusCode::PAYLOAD_TOO_LARGE), _ => HttpResponse::new(StatusCode::BAD_REQUEST), } } diff --git a/src/lib.rs b/src/lib.rs index 271efa0de..39cfaf197 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -130,9 +130,7 @@ pub mod dev { pub use crate::handler::Handler; pub use crate::info::ConnectionInfo; pub use crate::rmap::ResourceMap; - pub use crate::service::{ - HttpServiceFactory, ServiceRequest, ServiceResponse, WebService, - }; + pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService}; pub use crate::types::form::UrlEncoded; pub use crate::types::json::JsonBody; @@ -142,9 +140,7 @@ pub mod dev { #[cfg(feature = "compress")] pub use actix_http::encoding::Decoder as Decompress; pub use actix_http::ResponseBuilder as HttpResponseBuilder; - pub use actix_http::{ - Extensions, Payload, PayloadStream, RequestHead, ResponseHead, - }; + pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead}; pub use actix_router::{Path, ResourceDef, ResourcePath, Url}; pub use actix_server::Server; pub use actix_service::{Service, Transform}; diff --git a/src/middleware/compat.rs b/src/middleware/compat.rs index 7326c5824..97742e92c 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -143,9 +143,7 @@ mod tests { web::scope("app") .wrap(Compat::new(logger)) .wrap(Compat::new(compress)) - .service( - web::resource("/test").route(web::get().to(HttpResponse::Ok)), - ), + .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), ) .await; diff --git a/src/middleware/condition.rs b/src/middleware/condition.rs index be6f6764d..f0c344062 100644 --- a/src/middleware/condition.rs +++ b/src/middleware/condition.rs @@ -119,15 +119,13 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mw = Condition::new(true, mw) .new_transform(srv.into_service()) .await .unwrap(); - let resp = - test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -137,16 +135,14 @@ mod tests { ok(req.into_response(HttpResponse::InternalServerError().finish())) }; - let mw = - ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); + let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500); let mw = Condition::new(false, mw) .new_transform(srv.into_service()) .await .unwrap(); - let resp = - test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE), None); } } diff --git a/src/middleware/default_headers.rs b/src/middleware/default_headers.rs index d98403502..a36cc2f29 100644 --- a/src/middleware/default_headers.rs +++ b/src/middleware/default_headers.rs @@ -229,8 +229,7 @@ mod tests { #[actix_rt::test] async fn test_content_type() { - let srv = - |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); + let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())); let mw = DefaultHeaders::new() .add_content_type() .new_transform(srv.into_service()) diff --git a/src/middleware/err_handlers.rs b/src/middleware/err_handlers.rs index 07150f905..4673ed4ce 100644 --- a/src/middleware/err_handlers.rs +++ b/src/middleware/err_handlers.rs @@ -201,8 +201,7 @@ mod tests { .await .unwrap(); - let resp = - test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } @@ -227,8 +226,7 @@ mod tests { .await .unwrap(); - let resp = - test::call_service(&mw, TestRequest::default().to_srv_request()).await; + let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await; assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001"); } } diff --git a/src/middleware/logger.rs b/src/middleware/logger.rs index d4880b562..5b5b5577c 100644 --- a/src/middleware/logger.rs +++ b/src/middleware/logger.rs @@ -137,9 +137,9 @@ impl Logger { ) -> Self { let inner = Rc::get_mut(&mut self.0).unwrap(); - let ft = inner.format.0.iter_mut().find(|ft| { - matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label) - }); + let ft = inner.format.0.iter_mut().find( + |ft| matches!(ft, FormatText::CustomRequest(unit_label, _) if label == unit_label), + ); if let Some(FormatText::CustomRequest(_, request_fn)) = ft { // replace into None or previously registered fn using same label @@ -363,8 +363,7 @@ impl Format { /// Returns `None` if the format string syntax is incorrect. pub fn new(s: &str) -> Format { log::trace!("Access log format: {}", s); - let fmt = - Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[atPrUsbTD]?)").unwrap(); + let fmt = Regex::new(r"%(\{([A-Za-z0-9\-_]+)\}([aioe]|xi)|[atPrUsbTD]?)").unwrap(); let mut idx = 0; let mut results = Vec::new(); @@ -385,12 +384,12 @@ impl Format { unreachable!() } } - "i" => FormatText::RequestHeader( - HeaderName::try_from(key.as_str()).unwrap(), - ), - "o" => FormatText::ResponseHeader( - HeaderName::try_from(key.as_str()).unwrap(), - ), + "i" => { + FormatText::RequestHeader(HeaderName::try_from(key.as_str()).unwrap()) + } + "o" => { + FormatText::ResponseHeader(HeaderName::try_from(key.as_str()).unwrap()) + } "e" => FormatText::EnvironHeader(key.as_str().to_owned()), "xi" => FormatText::CustomRequest(key.as_str().to_owned(), None), _ => unreachable!(), @@ -533,9 +532,7 @@ impl FormatText { }; } FormatText::UrlPath => *self = FormatText::Str(req.path().to_string()), - FormatText::RequestTime => { - *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")) - } + FormatText::RequestTime => *self = FormatText::Str(now.format("%Y-%m-%dT%H:%M:%S")), FormatText::RequestHeader(ref name) => { let s = if let Some(val) = req.headers().get(name) { if let Ok(s) = val.to_str() { @@ -557,8 +554,7 @@ impl FormatText { *self = s; } FormatText::RealIPRemoteAddr => { - let s = if let Some(remote) = req.connection_info().realip_remote_addr() - { + let s = if let Some(remote) = req.connection_info().realip_remote_addr() { FormatText::Str(remote.to_string()) } else { FormatText::Str("-".to_string()) @@ -629,8 +625,8 @@ mod tests { .finish(), )) }; - let logger = Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test") - .exclude_regex("\\w"); + let logger = + Logger::new("%% %{User-Agent}i %{X-Test}o %{HOME}e %D test").exclude_regex("\\w"); let srv = logger.new_transform(srv.into_service()).await.unwrap(); @@ -743,9 +739,7 @@ mod tests { let req = TestRequest::default() .insert_header(( header::FORWARDED, - header::HeaderValue::from_static( - "for=192.0.2.60;proto=http;by=203.0.113.43", - ), + header::HeaderValue::from_static("for=192.0.2.60;proto=http;by=203.0.113.43"), )) .to_srv_request(); diff --git a/src/request.rs b/src/request.rs index 3f4eb1038..d62fca300 100644 --- a/src/request.rs +++ b/src/request.rs @@ -175,11 +175,7 @@ impl HttpRequest { /// ); /// } /// ``` - pub fn url_for( - &self, - name: &str, - elements: U, - ) -> Result + pub fn url_for(&self, name: &str, elements: U) -> Result where U: IntoIterator, I: AsRef, @@ -577,30 +573,30 @@ mod tests { #[actix_rt::test] async fn test_data() { - let srv = init_service(App::new().app_data(10usize).service( - web::resource("/").to(|req: HttpRequest| { + let srv = init_service(App::new().app_data(10usize).service(web::resource("/").to( + |req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() } else { HttpResponse::BadRequest() } - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); - let srv = init_service(App::new().app_data(10u32).service( - web::resource("/").to(|req: HttpRequest| { + let srv = init_service(App::new().app_data(10u32).service(web::resource("/").to( + |req: HttpRequest| { if req.app_data::().is_some() { HttpResponse::Ok() } else { HttpResponse::BadRequest() } - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); @@ -687,14 +683,14 @@ mod tests { let tracker = Rc::new(RefCell::new(Tracker { dropped: false })); { let tracker2 = Rc::clone(&tracker); - let srv = init_service(App::new().data(10u32).service( - web::resource("/").to(move |req: HttpRequest| { + let srv = init_service(App::new().data(10u32).service(web::resource("/").to( + move |req: HttpRequest| { req.extensions_mut().insert(Foo { tracker: Rc::clone(&tracker2), }); HttpResponse::Ok() - }), - )) + }, + ))) .await; let req = TestRequest::default().to_request(); diff --git a/src/resource.rs b/src/resource.rs index 188e6fa43..944beeefa 100644 --- a/src/resource.rs +++ b/src/resource.rs @@ -358,9 +358,10 @@ where U::InitError: fmt::Debug, { // create and configure default resource - self.default = boxed::factory(f.into_factory().map_init_err(|e| { - log::error!("Can not construct default service: {:?}", e) - })); + self.default = boxed::factory( + f.into_factory() + .map_init_err(|e| log::error!("Can not construct default service: {:?}", e)), + ); self } @@ -437,8 +438,7 @@ impl ServiceFactory for ResourceFactory { let default_fut = self.default.new_service(()); // construct route service factory futures - let factory_fut = - join_all(self.routes.iter().map(|route| route.new_service(()))); + let factory_fut = join_all(self.routes.iter().map(|route| route.new_service(()))); let app_data = self.app_data.clone(); @@ -530,19 +530,18 @@ mod tests { #[actix_rt::test] async fn test_middleware() { - let srv = - init_service( - App::new().service( - web::resource("/test") - .name("test") - .wrap(DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - )) - .route(web::get().to(HttpResponse::Ok)), - ), - ) - .await; + let srv = init_service( + App::new().service( + web::resource("/test") + .name("test") + .wrap( + DefaultHeaders::new() + .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), + ) + .route(web::get().to(HttpResponse::Ok)), + ), + ) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); @@ -584,12 +583,11 @@ mod tests { #[actix_rt::test] async fn test_to() { - let srv = - init_service(App::new().service(web::resource("/test").to(|| async { - sleep(Duration::from_millis(100)).await; - Ok::<_, Error>(HttpResponse::Ok()) - }))) - .await; + let srv = init_service(App::new().service(web::resource("/test").to(|| async { + sleep(Duration::from_millis(100)).await; + Ok::<_, Error>(HttpResponse::Ok()) + }))) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::OK); diff --git a/src/responder.rs b/src/responder.rs index dcad45e0f..92945cdaa 100644 --- a/src/responder.rs +++ b/src/responder.rs @@ -262,9 +262,7 @@ pub(crate) mod tests { async fn test_option_responder() { let srv = init_service( App::new() - .service( - web::resource("/none").to(|| async { Option::<&'static str>::None }), - ) + .service(web::resource("/none").to(|| async { Option::<&'static str>::None })) .service(web::resource("/some").to(|| async { Some("some") })), ) .await; @@ -364,8 +362,7 @@ pub(crate) mod tests { ); // InternalError - let resp = - error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); + let resp = error::InternalError::new("err", StatusCode::BAD_REQUEST).respond_to(&req); assert_eq!(resp.status(), StatusCode::BAD_REQUEST); } @@ -382,9 +379,8 @@ pub(crate) mod tests { HeaderValue::from_static("text/plain; charset=utf-8") ); - let res = - Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) - .respond_to(&req); + let res = Err::(error::InternalError::new("err", StatusCode::BAD_REQUEST)) + .respond_to(&req); assert_eq!(res.status(), StatusCode::BAD_REQUEST); } diff --git a/src/route.rs b/src/route.rs index c5e297411..b6b2482cd 100644 --- a/src/route.rs +++ b/src/route.rs @@ -238,12 +238,7 @@ where impl RouteNewService where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - >, + T: ServiceFactory, T::Future: 'static, T::Service: 'static, >::Future: 'static, @@ -255,12 +250,7 @@ where impl ServiceFactory for RouteNewService where - T: ServiceFactory< - ServiceRequest, - Config = (), - Response = ServiceResponse, - Error = Error, - >, + T: ServiceFactory, T::Future: 'static, T::Service: 'static, >::Future: 'static, diff --git a/src/scope.rs b/src/scope.rs index d08c78133..dd02501b0 100644 --- a/src/scope.rs +++ b/src/scope.rs @@ -8,8 +8,8 @@ use actix_http::Extensions; use actix_router::{ResourceDef, Router}; use actix_service::boxed::{self, BoxService, BoxServiceFactory}; use actix_service::{ - apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, - ServiceFactoryExt, Transform, + apply, apply_fn_factory, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt, + Transform, }; use futures_core::future::LocalBoxFuture; use futures_util::future::join_all; @@ -476,16 +476,15 @@ impl ServiceFactory for ScopeFactory { let default_fut = self.default.new_service(()); // construct all services factory future with it's resource def and guards. - let factory_fut = - join_all(self.services.iter().map(|(path, factory, guards)| { - let path = path.clone(); - let guards = guards.borrow_mut().take(); - let factory_fut = factory.new_service(()); - async move { - let service = factory_fut.await?; - Ok((path, guards, service)) - } - })); + let factory_fut = join_all(self.services.iter().map(|(path, factory, guards)| { + let path = path.clone(); + let guards = guards.borrow_mut().take(); + let factory_fut = factory.new_service(()); + async move { + let service = factory_fut.await?; + Ok((path, guards, service)) + } + })); let app_data = self.app_data.clone(); @@ -589,10 +588,11 @@ mod tests { #[actix_rt::test] async fn test_scope() { - let srv = init_service(App::new().service( - web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), - )) - .await; + let srv = + init_service(App::new().service( + web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)), + )) + .await; let req = TestRequest::with_uri("/app/path1").to_request(); let resp = srv.call(req).await.unwrap(); @@ -621,9 +621,10 @@ mod tests { #[actix_rt::test] async fn test_scope_root2() { - let srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("").to(HttpResponse::Ok)), - )) + let srv = init_service( + App::new() + .service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))), + ) .await; let req = TestRequest::with_uri("/app").to_request(); @@ -637,9 +638,10 @@ mod tests { #[actix_rt::test] async fn test_scope_root3() { - let srv = init_service(App::new().service( - web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok)), - )) + let srv = init_service( + App::new() + .service(web::scope("/app/").service(web::resource("/").to(HttpResponse::Ok))), + ) .await; let req = TestRequest::with_uri("/app").to_request(); @@ -737,8 +739,7 @@ mod tests { async fn test_scope_variable_segment() { let srv = init_service(App::new().service(web::scope("/ab-{project}").service( web::resource("/path1").to(|r: HttpRequest| { - HttpResponse::Ok() - .body(format!("project: {}", &r.match_info()["project"])) + HttpResponse::Ok().body(format!("project: {}", &r.match_info()["project"])) }), ))) .await; @@ -945,14 +946,10 @@ mod tests { App::new().service( web::scope("app") .wrap( - DefaultHeaders::new().header( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ), + DefaultHeaders::new() + .header(header::CONTENT_TYPE, HeaderValue::from_static("0001")), ) - .service( - web::resource("/test").route(web::get().to(HttpResponse::Ok)), - ), + .service(web::resource("/test").route(web::get().to(HttpResponse::Ok))), ), ) .await; @@ -975,10 +972,8 @@ mod tests { let fut = srv.call(req); async move { let mut res = fut.await?; - res.headers_mut().insert( - header::CONTENT_TYPE, - HeaderValue::from_static("0001"), - ); + res.headers_mut() + .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001")); Ok(res) } }) @@ -1083,9 +1078,8 @@ mod tests { s.route( "/", web::get().to(|req: HttpRequest| { - HttpResponse::Ok().body( - req.url_for("youtube", &["xxxxxx"]).unwrap().to_string(), - ) + HttpResponse::Ok() + .body(req.url_for("youtube", &["xxxxxx"]).unwrap().to_string()) }), ); })); diff --git a/src/server.rs b/src/server.rs index 69318e08d..d69d6570d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -277,32 +277,28 @@ where }); let on_connect_fn = self.on_connect_fn.clone(); - self.builder = self.builder.listen( - format!("actix-web-service-{}", addr), - lst, - move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .local_addr(addr); + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .local_addr(addr); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (handler)(io as &dyn Any, ext) - }) - } else { - svc - }; + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| { - AppConfig::new(false, addr, host.clone()) - })) - .tcp() - }, - )?; + svc.finish(map_config(factory(), move |_| { + AppConfig::new(false, addr, host.clone()) + })) + .tcp() + })?; Ok(self) } @@ -334,32 +330,30 @@ where let on_connect_fn = self.on_connect_fn.clone(); - self.builder = self.builder.listen( - format!("actix-web-service-{}", addr), - lst, - move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (&*handler)(io as &dyn Any, ext) - }) - } else { - svc - }; + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| { + (&*handler)(io as &dyn Any, ext) + }) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) - })) - .openssl(acceptor.clone()) - }, - )?; + svc.finish(map_config(factory(), move |_| { + AppConfig::new(true, addr, host.clone()) + })) + .openssl(acceptor.clone()) + })?; Ok(self) } @@ -391,32 +385,28 @@ where let on_connect_fn = self.on_connect_fn.clone(); - self.builder = self.builder.listen( - format!("actix-web-service-{}", addr), - lst, - move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .client_disconnect(c.client_shutdown); + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .client_disconnect(c.client_shutdown); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (handler)(io as &dyn Any, ext) - }) - } else { - svc - }; + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| { - AppConfig::new(true, addr, host.clone()) - })) - .rustls(config.clone()) - }, - )?; + svc.finish(map_config(factory(), move |_| { + AppConfig::new(true, addr, host.clone()) + })) + .rustls(config.clone()) + })?; Ok(self) } @@ -433,10 +423,7 @@ where Ok(self) } - fn bind2( - &self, - addr: A, - ) -> io::Result> { + fn bind2(&self, addr: A) -> io::Result> { let mut err = None; let mut success = false; let mut sockets = Vec::new(); @@ -469,11 +456,7 @@ where /// Start listening for incoming tls connections. /// /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_openssl( - mut self, - addr: A, - builder: SslAcceptorBuilder, - ) -> io::Result + pub fn bind_openssl(mut self, addr: A, builder: SslAcceptorBuilder) -> io::Result where A: net::ToSocketAddrs, { @@ -505,18 +488,13 @@ where #[cfg(unix)] /// Start listening for unix domain (UDS) connections on existing listener. - pub fn listen_uds( - mut self, - lst: std::os::unix::net::UnixListener, - ) -> io::Result { + pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_rt::net::UnixStream; let cfg = self.config.clone(); let factory = self.factory.clone(); - let socket_addr = net::SocketAddr::new( - net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), - 8080, - ); + let socket_addr = + net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); self.sockets.push(Socket { scheme: "http", addr: socket_addr, @@ -533,23 +511,19 @@ where c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( - { - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout); + pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then({ + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout); - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| { - (&*handler)(io as &dyn Any, ext) - }) - } else { - svc - }; + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)) + } else { + svc + }; - svc.finish(map_config(factory(), move |_| config.clone())) - }, - ) + svc.finish(map_config(factory(), move |_| config.clone())) + }) })?; Ok(self) } @@ -564,10 +538,8 @@ where let cfg = self.config.clone(); let factory = self.factory.clone(); - let socket_addr = net::SocketAddr::new( - net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), - 8080, - ); + let socket_addr = + net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); self.sockets.push(Socket { scheme: "http", addr: socket_addr, @@ -583,13 +555,12 @@ where socket_addr, c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), ); - pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))) - .and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_timeout(c.client_timeout) - .finish(map_config(factory(), move |_| config.clone())), - ) + pipeline_factory(|io: UnixStream| ok((io, Protocol::Http1, None))).and_then( + HttpService::build() + .keep_alive(c.keep_alive) + .client_timeout(c.client_timeout) + .finish(map_config(factory(), move |_| config.clone())), + ) }, )?; Ok(self) @@ -633,10 +604,7 @@ where } } -fn create_tcp_listener( - addr: net::SocketAddr, - backlog: u32, -) -> io::Result { +fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; let domain = match addr { net::SocketAddr::V4(_) => Domain::ipv4(), diff --git a/src/service.rs b/src/service.rs index a7fb12848..fcbe61a02 100644 --- a/src/service.rs +++ b/src/service.rs @@ -5,8 +5,7 @@ use std::{fmt, net}; use actix_http::body::{Body, MessageBody, ResponseBody}; use actix_http::http::{HeaderMap, Method, StatusCode, Uri, Version}; use actix_http::{ - Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, - ResponseHead, + Error, Extensions, HttpMessage, Payload, PayloadStream, RequestHead, Response, ResponseHead, }; use actix_router::{IntoPattern, Path, Resource, ResourceDef, Url}; use actix_service::{IntoServiceFactory, ServiceFactory}; @@ -635,20 +634,18 @@ mod tests { #[actix_rt::test] async fn test_service_data() { - let srv = init_service( - App::new() - .data(42u32) - .service(web::service("/test").name("test").finish( - |req: ServiceRequest| { - assert_eq!( - req.app_data::>().unwrap().as_ref(), - &42 - ); - ok(req.into_response(HttpResponse::Ok().finish())) - }, - )), - ) - .await; + let srv = + init_service( + App::new() + .data(42u32) + .service(web::service("/test").name("test").finish( + |req: ServiceRequest| { + assert_eq!(req.app_data::>().unwrap().as_ref(), &42); + ok(req.into_response(HttpResponse::Ok().finish())) + }, + )), + ) + .await; let req = TestRequest::with_uri("/test").to_request(); let resp = srv.call(req).await.unwrap(); assert_eq!(resp.status(), http::StatusCode::OK); diff --git a/src/test.rs b/src/test.rs index c03f078ef..99602eb11 100644 --- a/src/test.rs +++ b/src/test.rs @@ -12,9 +12,7 @@ use actix_http::test::TestRequest as HttpTestRequest; use actix_http::{cookie::Cookie, 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 actix_service::{map_config, IntoService, IntoServiceFactory, Service, ServiceFactory}; use awc::error::PayloadError; use awc::{Client, ClientRequest, ClientResponse, Connector}; use bytes::{Bytes, BytesMut}; @@ -78,12 +76,7 @@ pub async fn init_service( ) -> impl Service, Error = E> where R: IntoServiceFactory, - S: ServiceFactory< - Request, - Config = AppConfig, - Response = ServiceResponse, - Error = E, - >, + S: ServiceFactory, Error = E>, S::InitError: std::fmt::Debug, { try_init_service(app) @@ -97,12 +90,7 @@ pub(crate) async fn try_init_service( ) -> Result, Error = E>, S::InitError> where R: IntoServiceFactory, - S: ServiceFactory< - Request, - Config = AppConfig, - Response = ServiceResponse, - Error = E, - >, + S: ServiceFactory, Error = E>, S::InitError: std::fmt::Debug, { let srv = app.into_factory(); @@ -264,9 +252,8 @@ 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: {}", e)) } pub async fn load_stream(mut stream: S) -> Result @@ -487,8 +474,7 @@ impl TestRequest { /// Serialize `data` to JSON and set it as the request payload. The `Content-Type` header is /// set to `application/json`. pub fn set_json(mut self, data: &T) -> Self { - let bytes = - serde_json::to_string(data).expect("Failed to serialize test data to json"); + let bytes = serde_json::to_string(data).expect("Failed to serialize test data to json"); self.req.set_payload(bytes); self.req.insert_header(ContentType::json()); self @@ -528,8 +514,7 @@ impl TestRequest { head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); - let app_state = - AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); ServiceRequest::new( HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)), @@ -548,8 +533,7 @@ impl TestRequest { head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); - let app_state = - AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)) } @@ -560,8 +544,7 @@ impl TestRequest { head.peer_addr = self.peer_addr; self.path.get_mut().update(&head.uri); - let app_state = - AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); + let app_state = AppInitServiceState::new(Rc::new(self.rmap), self.config.clone()); let req = HttpRequest::new(self.path, head, app_state, Rc::new(self.app_data)); @@ -678,24 +661,21 @@ where 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)); + 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)); + 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)); + let cfg = AppConfig::new(false, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .finish(map_config(factory(), move |_| cfg.clone())) @@ -705,24 +685,21 @@ where #[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)); + 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)); + 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)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .finish(map_config(factory(), move |_| cfg.clone())) @@ -732,24 +709,21 @@ where #[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)); + 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)); + 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)); + let cfg = AppConfig::new(true, local_addr, format!("{}", local_addr)); HttpService::build() .client_timeout(ctimeout) .finish(map_config(factory(), move |_| cfg.clone())) @@ -887,8 +861,7 @@ impl TestServerConfig { /// Get first available unused address pub fn unused_addr() -> net::SocketAddr { let addr: net::SocketAddr = "127.0.0.1:0".parse().unwrap(); - let socket = - Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); + let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); let tcp = socket.into_tcp_listener(); @@ -975,8 +948,7 @@ impl TestServer { pub async fn ws_at( &mut self, path: &str, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { let url = self.url(path); let connect = self.client.ws(url).connect(); connect.await.map(|(_, framed)| framed) @@ -985,8 +957,7 @@ impl TestServer { /// Connect to a websocket server pub async fn ws( &mut self, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { self.ws_at("/").await } @@ -1218,10 +1189,9 @@ mod tests { } } - let app = init_service( - App::new().service(web::resource("/index.html").to(async_with_block)), - ) - .await; + let app = + init_service(App::new().service(web::resource("/index.html").to(async_with_block))) + .await; let req = TestRequest::post().uri("/index.html").to_request(); let res = app.call(req).await.unwrap(); diff --git a/src/types/either.rs b/src/types/either.rs index d72a14fd0..bbab48dec 100644 --- a/src/types/either.rs +++ b/src/types/either.rs @@ -276,13 +276,12 @@ mod tests { .set_payload(Bytes::from_static(b"!@$%^&*()")) .to_http_parts(); - let payload = - Either::, Json>, Bytes>::from_request( - &req, &mut pl, - ) - .await - .unwrap() - .unwrap_right(); + let payload = Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_right(); assert_eq!(&payload.as_ref(), &b"!@$%^&*()"); } @@ -294,15 +293,14 @@ mod tests { }) .to_http_parts(); - let form = - Either::, Json>, Bytes>::from_request( - &req, &mut pl, - ) - .await - .unwrap() - .unwrap_left() - .unwrap_right() - .into_inner(); + let form = Either::, Json>, Bytes>::from_request( + &req, &mut pl, + ) + .await + .unwrap() + .unwrap_left() + .unwrap_right() + .into_inner(); assert_eq!(&form.hello, "world"); } } diff --git a/src/types/form.rs b/src/types/form.rs index 96e09ee1c..0b5c3c1b4 100644 --- a/src/types/form.rs +++ b/src/types/form.rs @@ -21,8 +21,8 @@ use serde::{de::DeserializeOwned, Serialize}; #[cfg(feature = "compress")] use crate::dev::Decompress; use crate::{ - error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, - Error, HttpMessage, HttpRequest, HttpResponse, Responder, + error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error, + HttpMessage, HttpRequest, HttpResponse, Responder, }; /// URL encoded payload extractor and responder. @@ -342,16 +342,14 @@ where } if encoding == UTF_8 { - serde_urlencoded::from_bytes::(&body) - .map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_bytes::(&body).map_err(|_| UrlencodedError::Parse) } else { let body = encoding .decode_without_bom_handling_and_without_replacement(&body) .map(|s| s.into_owned()) .ok_or(UrlencodedError::Parse)?; - serde_urlencoded::from_str::(&body) - .map_err(|_| UrlencodedError::Parse) + serde_urlencoded::from_str::(&body).map_err(|_| UrlencodedError::Parse) } } .boxed_local(), diff --git a/src/types/json.rs b/src/types/json.rs index 95d8fd4bb..28960402a 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -482,8 +482,8 @@ mod tests { let msg = MyObject { name: "invalid request".to_string(), }; - let resp = HttpResponse::BadRequest() - .body(serde_json::to_string(&msg).unwrap()); + let resp = + HttpResponse::BadRequest().body(serde_json::to_string(&msg).unwrap()); InternalError::from_response(err, resp).into() })) .to_http_parts(); diff --git a/src/types/path.rs b/src/types/path.rs index a44a3ca9e..4ab124d53 100644 --- a/src/types/path.rs +++ b/src/types/path.rs @@ -235,11 +235,9 @@ mod tests { assert_eq!(res.1, "user1"); let (Path(a), Path(b)) = - <(Path<(String, String)>, Path<(String, String)>)>::from_request( - &req, &mut pl, - ) - .await - .unwrap(); + <(Path<(String, String)>, Path<(String, String)>)>::from_request(&req, &mut pl) + .await + .unwrap(); assert_eq!(a.0, "name"); assert_eq!(a.1, "user1"); assert_eq!(b.0, "name"); @@ -300,11 +298,8 @@ mod tests { async fn test_custom_err_handler() { let (req, mut pl) = TestRequest::with_uri("/name/user1/") .app_data(PathConfig::default().error_handler(|err, _| { - error::InternalError::from_response( - err, - HttpResponse::Conflict().finish(), - ) - .into() + error::InternalError::from_response(err, HttpResponse::Conflict().finish()) + .into() })) .to_http_parts(); diff --git a/src/types/payload.rs b/src/types/payload.rs index ec91eb44c..781347b84 100644 --- a/src/types/payload.rs +++ b/src/types/payload.rs @@ -55,10 +55,7 @@ impl Stream for Payload { type Item = Result; #[inline] - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.0).poll_next(cx) } } @@ -396,9 +393,7 @@ mod tests { App::new() .service( web::resource("/bytes-app-data") - .app_data( - PayloadConfig::default().mimetype(mime::APPLICATION_JSON), - ) + .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON)) .route(web::get().to(bytes_handler)), ) .service( @@ -408,9 +403,7 @@ mod tests { ) .service( web::resource("/string-app-data") - .app_data( - PayloadConfig::default().mimetype(mime::APPLICATION_JSON), - ) + .app_data(PayloadConfig::default().mimetype(mime::APPLICATION_JSON)) .route(web::get().to(string_handler)), ) .service( diff --git a/src/types/query.rs b/src/types/query.rs index 13b331845..691a4792b 100644 --- a/src/types/query.rs +++ b/src/types/query.rs @@ -167,8 +167,7 @@ where /// ``` #[derive(Clone)] pub struct QueryConfig { - err_handler: - Option Error + Send + Sync>>, + err_handler: Option Error + Send + Sync>>, } impl QueryConfig { diff --git a/src/types/readlines.rs b/src/types/readlines.rs index 01aab64ab..b8bdcc504 100644 --- a/src/types/readlines.rs +++ b/src/types/readlines.rs @@ -74,10 +74,7 @@ where { type Item = Result; - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.get_mut(); if let Some(err) = this.err.take() { diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 07c171674..34d57008d 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -23,8 +23,7 @@ async fn test_start() { sys.block_on(async { let srv = HttpServer::new(|| { App::new().service( - web::resource("/") - .route(web::to(|| HttpResponse::Ok().body("test"))), + web::resource("/").route(web::to(|| HttpResponse::Ok().body("test"))), ) }) .workers(1) diff --git a/tests/test_server.rs b/tests/test_server.rs index c626474cf..c2caa9eb2 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -11,8 +11,7 @@ use std::{ }; use actix_http::http::header::{ - ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, - TRANSFER_ENCODING, + ContentEncoding, ACCEPT_ENCODING, CONTENT_ENCODING, CONTENT_LENGTH, TRANSFER_ENCODING, }; use brotli2::write::{BrotliDecoder, BrotliEncoder}; use bytes::Bytes; @@ -69,14 +68,10 @@ impl TestBody { impl futures_core::stream::Stream for TestBody { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { ready!(Pin::new(&mut self.delay).poll(cx)); - self.delay = - Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))); + self.delay = Box::pin(actix_rt::time::sleep(std::time::Duration::from_millis(10))); let chunk_size = std::cmp::min(self.chunk_size, self.data.len()); let chunk = self.data.split_to(chunk_size); if chunk.is_empty() { @@ -90,8 +85,7 @@ impl futures_core::stream::Stream for TestBody { #[actix_rt::test] async fn test_body() { let srv = test::start(|| { - App::new() - .service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) + App::new().service(web::resource("/").route(web::to(|| HttpResponse::Ok().body(STR)))) }); let mut response = srv.get("/").send().await.unwrap(); @@ -328,12 +322,12 @@ async fn test_body_chunked_implicit() { #[actix_rt::test] async fn test_body_br_streaming() { let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || { + App::new() + .wrap(Compress::new(ContentEncoding::Br)) + .service(web::resource("/").route(web::to(move || { HttpResponse::Ok() .streaming(TestBody::new(Bytes::from_static(STR.as_ref()), 24)) - })), - ) + }))) }); let mut response = srv @@ -361,8 +355,7 @@ async fn test_body_br_streaming() { async fn test_head_binary() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::head().to(move || HttpResponse::Ok().body(STR))), + web::resource("/").route(web::head().to(move || HttpResponse::Ok().body(STR))), ) }); @@ -403,9 +396,7 @@ async fn test_body_deflate() { let srv = test::start_with(test::config().h1(), || { App::new() .wrap(Compress::new(ContentEncoding::Deflate)) - .service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), - ) + .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) }); // client request @@ -430,9 +421,9 @@ async fn test_body_deflate() { #[actix_rt::test] async fn test_body_brotli() { let srv = test::start_with(test::config().h1(), || { - App::new().wrap(Compress::new(ContentEncoding::Br)).service( - web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR))), - ) + App::new() + .wrap(Compress::new(ContentEncoding::Br)) + .service(web::resource("/").route(web::to(move || HttpResponse::Ok().body(STR)))) }); // client request @@ -459,8 +450,7 @@ async fn test_body_brotli() { async fn test_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().wrap(Compress::default()).service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -485,8 +475,7 @@ async fn test_encoding() { async fn test_gzip_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -512,8 +501,7 @@ async fn test_gzip_encoding_large() { let data = STR.repeat(10); let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -544,8 +532,7 @@ async fn test_reading_gzip_encoding_large_random() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -571,8 +558,7 @@ async fn test_reading_gzip_encoding_large_random() { async fn test_reading_deflate_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -598,8 +584,7 @@ async fn test_reading_deflate_encoding_large() { let data = STR.repeat(10); let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -630,8 +615,7 @@ async fn test_reading_deflate_encoding_large_random() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -657,8 +641,7 @@ async fn test_reading_deflate_encoding_large_random() { async fn test_brotli_encoding() { let srv = test::start_with(test::config().h1(), || { App::new().service( - web::resource("/") - .route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), + web::resource("/").route(web::to(move |body: Bytes| HttpResponse::Ok().body(body))), ) }); @@ -883,9 +866,7 @@ async fn test_normalize() { let srv = test::start_with(test::config().h1(), || { App::new() .wrap(NormalizePath::new(TrailingSlash::Trim)) - .service( - web::resource("/one").route(web::to(|| HttpResponse::Ok().finish())), - ) + .service(web::resource("/one").route(web::to(|| HttpResponse::Ok().finish()))) }); let response = srv.get("/one/").send().await.unwrap(); From 81bef93e5ee26547681c2c20599648ee2abfb25d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 12 Feb 2021 00:15:25 +0000 Subject: [PATCH 162/187] add time parser year shift tests --- Cargo.toml | 3 ++ actix-http/src/encoding/decoder.rs | 61 ++++++++++++++++++---------- actix-http/src/encoding/encoder.rs | 37 +++++++++++------ actix-http/src/message.rs | 2 + actix-http/src/time_parser.rs | 64 ++++++++++++++++++++++-------- 5 files changed, 116 insertions(+), 51 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 3cbd1f358..569d1f8c2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -116,6 +116,9 @@ brotli2 = "0.3.2" flate2 = "1.0.13" criterion = "0.3" +[profile.dev] +debug = false + [profile.release] lto = true opt-level = 3 diff --git a/actix-http/src/encoding/decoder.rs b/actix-http/src/encoding/decoder.rs index e16d42288..f0abae865 100644 --- a/actix-http/src/encoding/decoder.rs +++ b/actix-http/src/encoding/decoder.rs @@ -1,7 +1,11 @@ //! Stream decoders. -use std::task::{Context, Poll}; -use std::{future::Future, io, io::Write as _, pin::Pin}; +use std::{ + future::Future, + io::{self, Write as _}, + pin::Pin, + task::{Context, Poll}, +}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliDecoder; @@ -9,11 +13,13 @@ use bytes::Bytes; use flate2::write::{GzDecoder, ZlibDecoder}; use futures_core::{ready, Stream}; -use super::Writer; -use crate::error::{BlockingError, PayloadError}; -use crate::http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}; +use crate::{ + encoding::Writer, + error::{BlockingError, PayloadError}, + http::header::{ContentEncoding, HeaderMap, CONTENT_ENCODING}, +}; -const INPLACE: usize = 2049; +const MAX_CHUNK_SIZE_DECODE_IN_PLACE: usize = 2049; pub struct Decoder { decoder: Option, @@ -41,6 +47,7 @@ where ))), _ => None, }; + Decoder { decoder, stream, @@ -53,15 +60,11 @@ where #[inline] pub fn from_headers(stream: S, headers: &HeaderMap) -> Decoder { // check content-encoding - let encoding = if let Some(enc) = headers.get(&CONTENT_ENCODING) { - if let Ok(enc) = enc.to_str() { - ContentEncoding::from(enc) - } else { - ContentEncoding::Identity - } - } else { - ContentEncoding::Identity - }; + let encoding = headers + .get(&CONTENT_ENCODING) + .and_then(|val| val.to_str().ok()) + .map(ContentEncoding::from) + .unwrap_or(ContentEncoding::Identity); Self::new(stream, encoding) } @@ -81,8 +84,10 @@ where if let Some(ref mut fut) = self.fut { let (chunk, decoder) = ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + self.decoder = Some(decoder); self.fut.take(); + if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } @@ -92,13 +97,15 @@ where return Poll::Ready(None); } - match Pin::new(&mut self.stream).poll_next(cx) { - Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))), - Poll::Ready(Some(Ok(chunk))) => { + match ready!(Pin::new(&mut self.stream).poll_next(cx)) { + Some(Err(err)) => return Poll::Ready(Some(Err(err))), + + Some(Ok(chunk)) => { if let Some(mut decoder) = self.decoder.take() { - if chunk.len() < INPLACE { + if chunk.len() < MAX_CHUNK_SIZE_DECODE_IN_PLACE { let chunk = decoder.feed_data(chunk)?; self.decoder = Some(decoder); + if let Some(chunk) = chunk { return Poll::Ready(Some(Ok(chunk))); } @@ -108,13 +115,16 @@ where Ok((chunk, decoder)) })); } + continue; } else { return Poll::Ready(Some(Ok(chunk))); } } - Poll::Ready(None) => { + + None => { self.eof = true; + return if let Some(mut decoder) = self.decoder.take() { match decoder.feed_eof() { Ok(Some(res)) => Poll::Ready(Some(Ok(res))), @@ -125,10 +135,8 @@ where Poll::Ready(None) }; } - Poll::Pending => break, } } - Poll::Pending } } @@ -144,6 +152,7 @@ impl ContentDecoder { ContentDecoder::Br(ref mut decoder) => match decoder.flush() { Ok(()) => { let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -152,9 +161,11 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -163,6 +174,7 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() { Ok(_) => { let b = decoder.get_mut().take(); @@ -183,6 +195,7 @@ impl ContentDecoder { Ok(_) => { decoder.flush()?; let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -191,10 +204,12 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; let b = decoder.get_mut().take(); + if !b.is_empty() { Ok(Some(b)) } else { @@ -203,9 +218,11 @@ impl ContentDecoder { } Err(e) => Err(e), }, + ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) { Ok(_) => { decoder.flush()?; + let b = decoder.get_mut().take(); if !b.is_empty() { Ok(Some(b)) diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 3a413a150..366ecb8c4 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -1,7 +1,11 @@ //! Stream encoders. -use std::task::{Context, Poll}; -use std::{future::Future, io, io::Write as _, pin::Pin}; +use std::{ + future::Future, + io::{self, Write as _}, + pin::Pin, + task::{Context, Poll}, +}; use actix_rt::task::{spawn_blocking, JoinHandle}; use brotli2::write::BrotliEncoder; @@ -10,15 +14,19 @@ use flate2::write::{GzEncoder, ZlibEncoder}; use futures_core::ready; use pin_project::pin_project; -use crate::body::{Body, BodySize, MessageBody, ResponseBody}; -use crate::http::header::{ContentEncoding, CONTENT_ENCODING}; -use crate::http::{HeaderValue, StatusCode}; -use crate::{Error, ResponseHead}; +use crate::{ + body::{Body, BodySize, MessageBody, ResponseBody}, + http::{ + header::{ContentEncoding, CONTENT_ENCODING}, + HeaderValue, StatusCode, + }, + Error, ResponseHead, +}; use super::Writer; use crate::error::BlockingError; -const INPLACE: usize = 1024; +const MAX_CHUNK_SIZE_ENCODE_IN_PLACE: usize = 1024; #[pin_project] pub struct Encoder { @@ -137,23 +145,28 @@ impl MessageBody for Encoder { if let Some(ref mut fut) = this.fut { let mut encoder = ready!(Pin::new(fut).poll(cx)).map_err(|_| BlockingError)??; + let chunk = encoder.take(); *this.encoder = Some(encoder); this.fut.take(); + if !chunk.is_empty() { return Poll::Ready(Some(Ok(chunk))); } } - let result = this.body.as_mut().poll_next(cx); + let result = ready!(this.body.as_mut().poll_next(cx)); match result { - Poll::Ready(Some(Ok(chunk))) => { + Some(Err(err)) => return Poll::Ready(Some(Err(err))), + + Some(Ok(chunk)) => { if let Some(mut encoder) = this.encoder.take() { - if chunk.len() < INPLACE { + if chunk.len() < MAX_CHUNK_SIZE_ENCODE_IN_PLACE { encoder.write(&chunk)?; let chunk = encoder.take(); *this.encoder = Some(encoder); + if !chunk.is_empty() { return Poll::Ready(Some(Ok(chunk))); } @@ -167,7 +180,8 @@ impl MessageBody for Encoder { return Poll::Ready(Some(Ok(chunk))); } } - Poll::Ready(None) => { + + None => { if let Some(encoder) = this.encoder.take() { let chunk = encoder.finish()?; if chunk.is_empty() { @@ -180,7 +194,6 @@ impl MessageBody for Encoder { return Poll::Ready(None); } } - val => return val, } } } diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 736f35ee1..6438ccba0 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -13,8 +13,10 @@ use crate::http::{header, Method, StatusCode, Uri, Version}; pub enum ConnectionType { /// Close connection after response Close, + /// Keep connection alive after response KeepAlive, + /// Connection is upgraded to different type Upgrade, } diff --git a/actix-http/src/time_parser.rs b/actix-http/src/time_parser.rs index 0d06a5867..46bf73037 100644 --- a/actix-http/src/time_parser.rs +++ b/actix-http/src/time_parser.rs @@ -8,35 +8,65 @@ pub fn parse_http_date(time: &str) -> Option { } /// Attempt to parse a `time` string as a RFC 1123 formatted date time string. +/// +/// Eg: `Fri, 12 Feb 2021 00:14:29 GMT` fn try_parse_rfc_1123(time: &str) -> Option { time::parse(time, "%a, %d %b %Y %H:%M:%S").ok() } /// Attempt to parse a `time` string as a RFC 850 formatted date time string. +/// +/// Eg: `Wednesday, 11-Jan-21 13:37:41 UTC` fn try_parse_rfc_850(time: &str) -> Option { - match PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S") { - Ok(dt) => { - // If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3, - // we consider the year as part of this century if it's within the next 50 years, - // otherwise we consider as part of the previous century. - let now = OffsetDateTime::now_utc(); - let century_start_year = (now.year() / 100) * 100; - let mut expanded_year = century_start_year + dt.year(); + let dt = PrimitiveDateTime::parse(time, "%A, %d-%b-%y %H:%M:%S").ok()?; - if expanded_year > now.year() + 50 { - expanded_year -= 100; - } + // If the `time` string contains a two-digit year, then as per RFC 2616 § 19.3, + // we consider the year as part of this century if it's within the next 50 years, + // otherwise we consider as part of the previous century. - match Date::try_from_ymd(expanded_year, dt.month(), dt.day()) { - Ok(date) => Some(PrimitiveDateTime::new(date, dt.time())), - Err(_) => None, - } - } - Err(_) => None, + let now = OffsetDateTime::now_utc(); + let century_start_year = (now.year() / 100) * 100; + let mut expanded_year = century_start_year + dt.year(); + + if expanded_year > now.year() + 50 { + expanded_year -= 100; } + + let date = Date::try_from_ymd(expanded_year, dt.month(), dt.day()).ok()?; + Some(PrimitiveDateTime::new(date, dt.time())) } /// Attempt to parse a `time` string using ANSI C's `asctime` format. +/// +/// Eg: `Wed Feb 13 15:46:11 2013` fn try_parse_asctime(time: &str) -> Option { time::parse(time, "%a %b %_d %H:%M:%S %Y").ok() } + +#[cfg(test)] +mod tests { + use time::{date, time}; + + use super::*; + + #[test] + fn test_rfc_850_year_shift() { + let date = try_parse_rfc_850("Friday, 19-Nov-82 16:14:55 EST").unwrap(); + assert_eq!(date, date!(1982 - 11 - 19).with_time(time!(16:14:55))); + + let date = try_parse_rfc_850("Wednesday, 11-Jan-62 13:37:41 EST").unwrap(); + assert_eq!(date, date!(2062 - 01 - 11).with_time(time!(13:37:41))); + + let date = try_parse_rfc_850("Wednesday, 11-Jan-21 13:37:41 EST").unwrap(); + assert_eq!(date, date!(2021 - 01 - 11).with_time(time!(13:37:41))); + + let date = try_parse_rfc_850("Wednesday, 11-Jan-23 13:37:41 EST").unwrap(); + assert_eq!(date, date!(2023 - 01 - 11).with_time(time!(13:37:41))); + + let date = try_parse_rfc_850("Wednesday, 11-Jan-99 13:37:41 EST").unwrap(); + assert_eq!(date, date!(1999 - 01 - 11).with_time(time!(13:37:41))); + + let date = try_parse_rfc_850("Wednesday, 11-Jan-00 13:37:41 EST").unwrap(); + assert_eq!(date, date!(2000 - 01 - 11).with_time(time!(13:37:41))); + } +} From 4fc7d7675964b8451ab17aea3a8998e3db64e87e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 12 Feb 2021 00:27:20 +0000 Subject: [PATCH 163/187] s/websocket/WebSocket in docs --- actix-http-test/src/lib.rs | 4 ++-- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/ws/codec.rs | 4 ++-- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/mod.rs | 16 ++++++++-------- actix-http/src/ws/proto.rs | 3 +-- actix-web-actors/src/ws.rs | 10 +++++----- awc/src/ws.rs | 10 +++++----- awc/tests/test_ws.rs | 2 +- src/test.rs | 4 ++-- 10 files changed, 28 insertions(+), 29 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 89cb0777f..df5774998 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -243,7 +243,7 @@ impl TestServer { response.body().limit(10_485_760).await } - /// Connect to websocket server at a given path + /// Connect to WebSocket server at a given path. pub async fn ws_at( &mut self, path: &str, @@ -253,7 +253,7 @@ impl TestServer { connect.await.map(|(_, framed)| framed) } - /// Connect to a websocket server + /// Connect to a WebSocket server. pub async fn ws( &mut self, ) -> Result, awc::error::WsClientError> { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 074616929..93a4b13d2 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -224,7 +224,7 @@ impl MessageType for Request { let decoder = match length { PayloadLength::Payload(pl) => pl, PayloadLength::UpgradeWebSocket => { - // upgrade(websocket) + // upgrade (WebSocket) PayloadType::Stream(PayloadDecoder::eof()) } PayloadLength::None => { diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index d01e8dab9..54d850854 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -54,7 +54,7 @@ pub enum Frame { Close(Option), } -/// A `WebSocket` continuation item. +/// A WebSocket continuation item. #[derive(Debug, PartialEq)] pub enum Item { FirstText(Bytes), @@ -79,7 +79,7 @@ bitflags! { } impl Codec { - /// Create new websocket frames decoder. + /// Create new WebSocket frames decoder. pub fn new() -> Codec { Codec { max_size: 65_536, diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 78d487dd2..46edf5d85 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -7,7 +7,7 @@ use crate::ws::mask::apply_mask; use crate::ws::proto::{CloseCode, CloseReason, OpCode}; use crate::ws::ProtocolError; -/// A struct representing a `WebSocket` frame. +/// A struct representing a WebSocket frame. #[derive(Debug)] pub struct Parser; diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index dad2646c1..0490163d5 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -1,6 +1,6 @@ -//! WebSocket protocol support. +//! WebSocket protocol. //! -//! To setup a WebSocket, first do web socket handshake then on success convert `Payload` into a +//! To setup a WebSocket, first perform the WebSocket handshake then on success convert `Payload` into a //! `WsStream` stream and then use `WsWriter` to communicate with the peer. use std::io; @@ -76,7 +76,7 @@ pub enum HandshakeError { #[display(fmt = "Method not allowed.")] GetMethodRequired, - /// Upgrade header if not set to websocket. + /// Upgrade header if not set to WebSocket. #[display(fmt = "WebSocket upgrade is expected.")] NoWebsocketUpgrade, @@ -88,7 +88,7 @@ pub enum HandshakeError { #[display(fmt = "WebSocket version header is required.")] NoVersionHeader, - /// Unsupported websocket version. + /// Unsupported WebSocket version. #[display(fmt = "Unsupported version.")] UnsupportedVersion, @@ -127,20 +127,20 @@ impl ResponseError for HandshakeError { } } -/// Verify `WebSocket` handshake request and create handshake response. +/// Verify WebSocket handshake request and create handshake response. pub fn handshake(req: &RequestHead) -> Result { verify_handshake(req)?; Ok(handshake_response(req)) } -/// Verify `WebSocket` handshake request. +/// Verify WebSocket handshake request. pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { // WebSocket accepts only GET if req.method != Method::GET { return Err(HandshakeError::GetMethodRequired); } - // Check for "UPGRADE" to websocket header + // Check for "UPGRADE" to WebSocket header let has_hdr = if let Some(hdr) = req.headers().get(header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") @@ -181,7 +181,7 @@ pub fn verify_handshake(req: &RequestHead) -> Result<(), HandshakeError> { Ok(()) } -/// Create websocket handshake response +/// Create WebSocket handshake response. /// /// This function returns handshake `Response`, ready to send to peer. pub fn handshake_response(req: &RequestHead) -> ResponseBuilder { diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 93468d232..1e8bf7af3 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -74,8 +74,7 @@ impl From for OpCode { } } -/// Status code used to indicate why an endpoint is closing the `WebSocket` -/// connection. +/// Status code used to indicate why an endpoint is closing the WebSocket connection. #[derive(Debug, Eq, PartialEq, Clone, Copy)] pub enum CloseCode { /// Indicates a normal closure, meaning that the purpose for diff --git a/actix-web-actors/src/ws.rs b/actix-web-actors/src/ws.rs index 3eb8cc1e7..1ab4cfce5 100644 --- a/actix-web-actors/src/ws.rs +++ b/actix-web-actors/src/ws.rs @@ -48,7 +48,7 @@ where /// /// If successful, returns a pair where the first item is an address for the /// created actor and the second item is the response that should be returned -/// from the websocket request. +/// from the WebSocket request. pub fn start_with_addr( actor: A, req: &HttpRequest, @@ -63,7 +63,7 @@ where Ok((addr, res.streaming(out_stream))) } -/// Do websocket handshake and start ws actor. +/// Do WebSocket handshake and start ws actor. /// /// `protocols` is a sequence of known protocols. pub fn start_with_protocols( @@ -80,7 +80,7 @@ where Ok(res.streaming(WebsocketContext::create(actor, stream))) } -/// Prepare `WebSocket` handshake response. +/// Prepare WebSocket handshake response. /// /// This function returns handshake `HttpResponse`, ready to send to peer. /// It does not perform any IO. @@ -88,7 +88,7 @@ pub fn handshake(req: &HttpRequest) -> Result, @@ -59,7 +59,7 @@ pub struct WebsocketsRequest { } impl WebsocketsRequest { - /// Create new websocket connection + /// Create new WebSocket connection pub(crate) fn new(uri: U, config: Rc) -> Self where Uri: TryFrom, @@ -102,7 +102,7 @@ impl WebsocketsRequest { self } - /// Set supported websocket protocols + /// Set supported WebSocket protocols pub fn protocols(mut self, protos: U) -> Self where U: IntoIterator, @@ -239,7 +239,7 @@ impl WebsocketsRequest { self.header(AUTHORIZATION, format!("Bearer {}", token)) } - /// Complete request construction and connect to a websockets server. + /// Complete request construction and connect to a WebSocket server. pub async fn connect( mut self, ) -> Result<(ClientResponse, Framed), WsClientError> { @@ -338,7 +338,7 @@ impl WebsocketsRequest { return Err(WsClientError::InvalidResponseStatus(head.status)); } - // Check for "UPGRADE" to websocket header + // check for "UPGRADE" to WebSocket header let has_hdr = if let Some(hdr) = head.headers.get(&header::UPGRADE) { if let Ok(s) = hdr.to_str() { s.to_ascii_lowercase().contains("websocket") diff --git a/awc/tests/test_ws.rs b/awc/tests/test_ws.rs index 8eb912dac..1b3f780dc 100644 --- a/awc/tests/test_ws.rs +++ b/awc/tests/test_ws.rs @@ -31,7 +31,7 @@ async fn test_simple() { .send(h1::Message::Item((res.drop_body(), BodySize::None))) .await?; - // start websocket service + // start WebSocket service let framed = framed.replace_codec(ws::Codec::new()); ws::Dispatcher::with(framed, ws_service).await } diff --git a/src/test.rs b/src/test.rs index 99602eb11..d576838de 100644 --- a/src/test.rs +++ b/src/test.rs @@ -944,7 +944,7 @@ impl TestServer { response.body().limit(10_485_760).await } - /// Connect to websocket server at a given path + /// Connect to WebSocket server at a given path. pub async fn ws_at( &mut self, path: &str, @@ -954,7 +954,7 @@ impl TestServer { connect.await.map(|(_, framed)| framed) } - /// Connect to a websocket server + /// Connect to a WebSocket server. pub async fn ws( &mut self, ) -> Result, awc::error::WsClientError> { From ce9b2770e2c7931cd29b574101cfbcbcba3c399b Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 02:37:28 -0800 Subject: [PATCH 164/187] remove unused Dispatcher::new_timeout (#1985) --- actix-http/src/h1/dispatcher.rs | 43 ++++++--------------------------- 1 file changed, 8 insertions(+), 35 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 7ee2ee258..e6533b7c5 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -160,62 +160,35 @@ where { /// Create HTTP/1 dispatcher. pub(crate) fn new( - stream: T, - config: ServiceConfig, - services: Rc>, - on_connect_data: OnConnectData, - peer_addr: Option, - ) -> Self { - Dispatcher::with_timeout( - stream, - Codec::new(config.clone()), - config, - BytesMut::with_capacity(HW_BUFFER_SIZE), - None, - services, - on_connect_data, - peer_addr, - ) - } - - /// Create HTTP/1 dispatcher with slow request timeout. - pub(crate) fn with_timeout( io: T, - codec: Codec, config: ServiceConfig, - read_buf: BytesMut, - timeout: Option, - services: Rc>, + flow: Rc>, on_connect_data: OnConnectData, peer_addr: Option, ) -> Self { - let keepalive = config.keep_alive_enabled(); - let flags = if keepalive { + let flags = if config.keep_alive_enabled() { Flags::KEEPALIVE } else { Flags::empty() }; // keep-alive timer - let (ka_expire, ka_timer) = if let Some(delay) = timeout { - (delay.deadline(), Some(delay)) - } else if let Some(delay) = config.keep_alive_timer() { - (delay.deadline(), Some(delay)) - } else { - (config.now(), None) + let (ka_expire, ka_timer) = match config.keep_alive_timer() { + Some(delay) => (delay.deadline(), Some(delay)), + None => (config.now(), None), }; Dispatcher { inner: DispatcherState::Normal(InnerDispatcher { + read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE), payload: None, state: State::None, error: None, messages: VecDeque::new(), io: Some(io), - codec, - read_buf, - flow: services, + codec: Codec::new(config), + flow, on_connect_data, flags, peer_addr, From 95113ad12f4aa34b6173019753fee335804fcbe1 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 12:33:13 -0800 Subject: [PATCH 165/187] do not self wake up when have a payload (#1984) --- actix-http/src/h1/dispatcher.rs | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index e6533b7c5..2e729b78d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -745,7 +745,12 @@ where // at this point it's not known io is still scheduled to // be waked up. so force wake up dispatcher just in case. // TODO: figure out the overhead. - cx.waker().wake_by_ref(); + if this.payload.is_none() { + // When dispatcher has a payload. The responsibility of + // wake up stream would be shift to PayloadSender. + // Therefore no self wake up is needed. + cx.waker().wake_by_ref(); + } return Ok(false); } From 366c032c36390bd1527970fe90cf2f7217736c65 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 13:52:58 -0800 Subject: [PATCH 166/187] refactor DateService (#1983) --- actix-http/src/config.rs | 171 +++++++++++++++++++++++------------ actix-http/src/h1/codec.rs | 4 +- actix-http/src/h1/encoder.rs | 12 +-- benches/server.rs | 29 +++--- 4 files changed, 138 insertions(+), 78 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index 61708cfc3..f2e71799a 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -4,9 +4,11 @@ use std::rc::Rc; use std::time::Duration; use std::{fmt, net}; -use actix_rt::time::{sleep, sleep_until, Instant, Sleep}; +use actix_rt::{ + task::JoinHandle, + time::{interval, sleep_until, Instant, Sleep}, +}; use bytes::BytesMut; -use futures_util::{future, FutureExt}; use time::OffsetDateTime; /// "Sun, 06 Nov 1994 08:49:37 GMT".len() @@ -49,7 +51,7 @@ struct Inner { ka_enabled: bool, secure: bool, local_addr: Option, - timer: DateService, + date_service: DateService, } impl Clone for ServiceConfig { @@ -91,7 +93,7 @@ impl ServiceConfig { client_disconnect, secure, local_addr, - timer: DateService::new(), + date_service: DateService::new(), })) } @@ -125,7 +127,7 @@ impl ServiceConfig { let delay_time = self.0.client_timeout; if delay_time != 0 { Some(sleep_until( - self.0.timer.now() + Duration::from_millis(delay_time), + self.0.date_service.now() + Duration::from_millis(delay_time), )) } else { None @@ -136,7 +138,7 @@ impl ServiceConfig { pub fn client_timer_expire(&self) -> Option { let delay = self.0.client_timeout; if delay != 0 { - Some(self.0.timer.now() + Duration::from_millis(delay)) + Some(self.0.date_service.now() + Duration::from_millis(delay)) } else { None } @@ -146,7 +148,7 @@ impl ServiceConfig { pub fn client_disconnect_timer(&self) -> Option { let delay = self.0.client_disconnect; if delay != 0 { - Some(self.0.timer.now() + Duration::from_millis(delay)) + Some(self.0.date_service.now() + Duration::from_millis(delay)) } else { None } @@ -156,7 +158,7 @@ impl ServiceConfig { /// Return keep-alive timer delay is configured. pub fn keep_alive_timer(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(sleep_until(self.0.timer.now() + ka)) + Some(sleep_until(self.0.date_service.now() + ka)) } else { None } @@ -165,7 +167,7 @@ impl ServiceConfig { /// Keep-alive expire time pub fn keep_alive_expire(&self) -> Option { if let Some(ka) = self.0.keep_alive { - Some(self.0.timer.now() + ka) + Some(self.0.date_service.now() + ka) } else { None } @@ -173,7 +175,7 @@ impl ServiceConfig { #[inline] pub(crate) fn now(&self) -> Instant { - self.0.timer.now() + self.0.date_service.now() } #[doc(hidden)] @@ -181,7 +183,7 @@ impl ServiceConfig { let mut buf: [u8; 39] = [0; 39]; buf[..6].copy_from_slice(b"date: "); self.0 - .timer + .date_service .set_date(|date| buf[6..35].copy_from_slice(&date.bytes)); buf[35..].copy_from_slice(b"\r\n\r\n"); dst.extend_from_slice(&buf); @@ -189,7 +191,7 @@ impl ServiceConfig { pub(crate) fn set_date_header(&self, dst: &mut BytesMut) { self.0 - .timer + .date_service .set_date(|date| dst.extend_from_slice(&date.bytes)); } } @@ -230,57 +232,75 @@ impl fmt::Write for Date { } } -#[derive(Clone)] -struct DateService(Rc); - -struct DateServiceInner { - current: Cell>, +/// Service for update Date and Instant periodically at 500 millis interval. +struct DateService { + current: Rc>, + handle: JoinHandle<()>, } -impl DateServiceInner { - fn new() -> Self { - DateServiceInner { - current: Cell::new(None), - } - } - - fn reset(&self) { - self.current.take(); - } - - fn update(&self) { - let now = Instant::now(); - let date = Date::new(); - self.current.set(Some((date, now))); +impl Drop for DateService { + fn drop(&mut self) { + // stop the timer update async task on drop. + self.handle.abort(); } } impl DateService { fn new() -> Self { - DateService(Rc::new(DateServiceInner::new())) - } + // shared date and timer for DateService and update async task. + let current = Rc::new(Cell::new((Date::new(), Instant::now()))); + let current_clone = Rc::clone(¤t); + // spawn an async task sleep for 500 milli and update current date/timer in a loop. + // handle is used to stop the task on DateService drop. + let handle = actix_rt::spawn(async move { + #[cfg(test)] + let _notify = notify_on_drop::NotifyOnDrop::new(); - fn check_date(&self) { - if self.0.current.get().is_none() { - self.0.update(); + let mut interval = interval(Duration::from_millis(500)); + loop { + let now = interval.tick().await; + let date = Date::new(); + current_clone.set((date, now)); + } + }); - // periodic date update - let s = self.clone(); - actix_rt::spawn(sleep(Duration::from_millis(500)).then(move |_| { - s.0.reset(); - future::ready(()) - })); - } + DateService { current, handle } } fn now(&self) -> Instant { - self.check_date(); - self.0.current.get().unwrap().1 + self.current.get().1 } fn set_date(&self, mut f: F) { - self.check_date(); - f(&self.0.current.get().unwrap().0); + f(&self.current.get().0); + } +} + +// test drop behavior of DateService. only enabled in tests. +// TODO: move to a util module for testing all spawn handle drop style tasks. +#[cfg(test)] +mod notify_on_drop { + use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; + + static NOTIFY_DROPPED: AtomicBool = AtomicBool::new(false); + + pub(crate) fn is_dropped() -> bool { + NOTIFY_DROPPED.load(SeqCst) + } + + pub(crate) struct NotifyOnDrop; + + impl NotifyOnDrop { + pub(crate) fn new() -> Self { + NOTIFY_DROPPED.store(false, SeqCst); + NotifyOnDrop + } + } + + impl Drop for NotifyOnDrop { + fn drop(&mut self) { + NOTIFY_DROPPED.store(true, SeqCst); + } } } @@ -288,14 +308,53 @@ impl DateService { mod tests { use super::*; - // Test modifying the date from within the closure - // passed to `set_date` - #[test] - fn test_evil_date() { - let service = DateService::new(); - // Make sure that `check_date` doesn't try to spawn a task - service.0.update(); - service.set_date(|_| service.0.reset()); + use actix_rt::task::yield_now; + + #[actix_rt::test] + async fn test_date_service_update() { + let settings = ServiceConfig::new(KeepAlive::Os, 0, 0, false, None); + + yield_now().await; + + let mut buf1 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf1); + let now1 = settings.now(); + + sleep_until(Instant::now() + Duration::from_secs(2)).await; + yield_now().await; + + let now2 = settings.now(); + let mut buf2 = BytesMut::with_capacity(DATE_VALUE_LENGTH + 10); + settings.set_date(&mut buf2); + + assert_ne!(now1, now2); + + assert_ne!(buf1, buf2); + + drop(settings); + assert!(notify_on_drop::is_dropped()); + } + + #[actix_rt::test] + async fn test_date_service_drop() { + let service = Rc::new(DateService::new()); + + // yield so date service have a chance to register the spawned timer update task. + yield_now().await; + + let clone1 = service.clone(); + let clone2 = service.clone(); + let clone3 = service.clone(); + + drop(clone1); + assert_eq!(false, notify_on_drop::is_dropped()); + drop(clone2); + assert_eq!(false, notify_on_drop::is_dropped()); + drop(clone3); + assert_eq!(false, notify_on_drop::is_dropped()); + + drop(service); + assert!(notify_on_drop::is_dropped()); } #[test] diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index 4aeb9f120..634ca25e8 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -201,8 +201,8 @@ mod tests { use super::*; use crate::HttpMessage; - #[test] - fn test_http_request_chunked_payload_and_next_message() { + #[actix_rt::test] + async fn test_http_request_chunked_payload_and_next_message() { let mut codec = Codec::default(); let mut buf = BytesMut::from( diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 2932a7dce..69e69de42 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -529,8 +529,8 @@ mod tests { ); } - #[test] - fn test_camel_case() { + #[actix_rt::test] + async fn test_camel_case() { let mut bytes = BytesMut::with_capacity(2048); let mut head = RequestHead::default(); head.set_camel_case_headers(true); @@ -593,8 +593,8 @@ mod tests { assert!(data.contains("date: date\r\n")); } - #[test] - fn test_extra_headers() { + #[actix_rt::test] + async fn test_extra_headers() { let mut bytes = BytesMut::with_capacity(2048); let mut head = RequestHead::default(); @@ -627,8 +627,8 @@ mod tests { assert!(data.contains("date: date\r\n")); } - #[test] - fn test_no_content_length() { + #[actix_rt::test] + async fn test_no_content_length() { let mut bytes = BytesMut::with_capacity(2048); let mut res: Response<()> = diff --git a/benches/server.rs b/benches/server.rs index 2471dd74c..9dd540a73 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -42,24 +42,25 @@ fn bench_async_burst(c: &mut Criterion) { c.bench_function("get_body_async_burst", move |b| { b.iter_custom(|iters| { - let client = - rt.block_on(async { Client::new().get(url.clone()).freeze().unwrap() }); + rt.block_on(async { + let client = Client::new().get(url.clone()).freeze().unwrap(); + + let start = std::time::Instant::now(); + // benchmark body - let start = std::time::Instant::now(); - // benchmark body - let resps = rt.block_on(async move { let burst = (0..iters).map(|_| client.send()); - join_all(burst).await - }); - let elapsed = start.elapsed(); + let resps = join_all(burst).await; - // if there are failed requests that might be an issue - let failed = resps.iter().filter(|r| r.is_err()).count(); - if failed > 0 { - eprintln!("failed {} requests (might be bench timeout)", failed); - }; + let elapsed = start.elapsed(); - elapsed + // if there are failed requests that might be an issue + let failed = resps.iter().filter(|r| r.is_err()).count(); + if failed > 0 { + eprintln!("failed {} requests (might be bench timeout)", failed); + }; + + elapsed + }) }) }); } From 1e538bf73efe5bebdfd920559ece136a23c911bf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 12 Feb 2021 21:53:21 +0000 Subject: [PATCH 167/187] rework ci (#1982) --- .github/workflows/{linux.yml => ci.yml} | 40 +++++++++---- .github/workflows/macos.yml | 56 ------------------ .github/workflows/windows.yml | 76 ------------------------- Cargo.toml | 14 +++-- actix-http-test/Cargo.toml | 6 ++ actix-http/Cargo.toml | 5 ++ awc/Cargo.toml | 6 ++ 7 files changed, 55 insertions(+), 148 deletions(-) rename .github/workflows/{linux.yml => ci.yml} (58%) delete mode 100644 .github/workflows/macos.yml delete mode 100644 .github/workflows/windows.yml diff --git a/.github/workflows/linux.yml b/.github/workflows/ci.yml similarity index 58% rename from .github/workflows/linux.yml rename to .github/workflows/ci.yml index 53f22df63..55af5a380 100644 --- a/.github/workflows/linux.yml +++ b/.github/workflows/ci.yml @@ -1,24 +1,27 @@ -name: CI (Linux) +name: CI on: pull_request: types: [opened, synchronize, reopened] push: - branches: - - master + branches: [master] jobs: build_and_test: strategy: fail-fast: false matrix: + target: + - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } + - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } + - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } version: - 1.46.0 # MSRV - stable - nightly - name: ${{ matrix.version }} - x86_64-unknown-linux-gnu - runs-on: ubuntu-latest + name: ${{ matrix.target.name }} / ${{ matrix.version }} + runs-on: ${{ matrix.target.os }} steps: - uses: actions/checkout@v2 @@ -26,7 +29,7 @@ jobs: - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.version }}-x86_64-unknown-linux-gnu + toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} profile: minimal override: true @@ -37,18 +40,25 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.0.1 - - name: check build + - name: check minimal uses: actions-rs/cargo@v1 with: command: check - args: --all --bins --examples --tests + args: --workspace --no-default-features --tests + + - name: check full + uses: actions-rs/cargo@v1 + with: + command: check + args: --workspace --bins --examples --tests - name: tests uses: actions-rs/cargo@v1 - timeout-minutes: 40 with: command: test - args: --all --all-features --no-fail-fast -- --nocapture + args: -v --workspace --all-features --no-fail-fast -- --nocapture + --skip=test_h2_content_length + --skip=test_reading_deflate_encoding_large_random_rustls - name: tests (actix-http) uses: actions-rs/cargo@v1 @@ -65,12 +75,18 @@ jobs: args: --package=awc --no-default-features --features=rustls -- --nocapture - name: Generate coverage file - if: matrix.version == 'stable' && github.ref == 'refs/heads/master' + if: > + matrix.target.os == 'ubuntu-latest' + && matrix.version == 'stable' + && github.ref == 'refs/heads/master' run: | cargo install cargo-tarpaulin --vers "^0.13" cargo tarpaulin --out Xml - name: Upload to Codecov - if: matrix.version == 'stable' && github.ref == 'refs/heads/master' + if: > + matrix.target.os == 'ubuntu-latest' + && matrix.version == 'stable' + && github.ref == 'refs/heads/master' uses: codecov/codecov-action@v1 with: file: cobertura.xml diff --git a/.github/workflows/macos.yml b/.github/workflows/macos.yml deleted file mode 100644 index 6b5366faf..000000000 --- a/.github/workflows/macos.yml +++ /dev/null @@ -1,56 +0,0 @@ -name: CI (macOS) - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - master - -jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - version: - - stable - - nightly - - name: ${{ matrix.version }} - x86_64-apple-darwin - runs-on: macOS-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-x86_64-apple-darwin - profile: minimal - override: true - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: - command: generate-lockfile - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.0.1 - - - name: check build - uses: actions-rs/cargo@v1 - with: - command: check - args: --all --bins --examples --tests - - - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache diff --git a/.github/workflows/windows.yml b/.github/workflows/windows.yml deleted file mode 100644 index d3de72a61..000000000 --- a/.github/workflows/windows.yml +++ /dev/null @@ -1,76 +0,0 @@ -name: CI (Windows) - -on: - pull_request: - types: [opened, synchronize, reopened] - push: - branches: - - master - -env: - VCPKGRS_DYNAMIC: 1 - -jobs: - build_and_test: - strategy: - fail-fast: false - matrix: - version: - - stable - - nightly - - name: ${{ matrix.version }} - x86_64-pc-windows-msvc - runs-on: windows-latest - - steps: - - uses: actions/checkout@v2 - - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }}-x86_64-pc-windows-msvc - profile: minimal - override: true - - - name: Install OpenSSL - run: | - vcpkg integrate install - vcpkg install openssl:x64-windows - Copy-Item C:\vcpkg\installed\x64-windows\bin\libcrypto-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libcrypto.dll - Copy-Item C:\vcpkg\installed\x64-windows\bin\libssl-1_1-x64.dll C:\vcpkg\installed\x64-windows\bin\libssl.dll - Get-ChildItem C:\vcpkg\installed\x64-windows\bin - Get-ChildItem C:\vcpkg\installed\x64-windows\lib - - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: - command: generate-lockfile - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.0.1 - - - name: check build - uses: actions-rs/cargo@v1 - with: - command: check - args: --all --bins --examples --tests - - - name: tests - uses: actions-rs/cargo@v1 - with: - command: test - args: --all --all-features --no-fail-fast -- --nocapture - --skip=test_h2_content_length - --skip=test_reading_deflate_encoding_large_random_rustls - --skip=test_params - --skip=test_simple - --skip=test_expect_continue - --skip=test_http10_keepalive - --skip=test_slow_request - --skip=test_connection_force_close - --skip=test_connection_server_close - --skip=test_connection_wait_queue_force_close - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --no-default-features --features ci-autoclean - cargo-cache diff --git a/Cargo.toml b/Cargo.toml index 569d1f8c2..b0302b352 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,10 +47,10 @@ compress = ["actix-http/compress", "awc/compress"] secure-cookies = ["actix-http/secure-cookies"] # openssl -openssl = ["tls_openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] +openssl = ["tls-openssl", "actix-tls/accept", "actix-tls/openssl", "awc/openssl"] # rustls -rustls = ["tls_rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] +rustls = ["tls-rustls", "actix-tls/accept", "actix-tls/rustls", "awc/rustls"] [[example]] name = "basic" @@ -103,10 +103,16 @@ serde_json = "1.0" serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } url = "2.1" -tls_openssl = { package = "openssl", version = "0.10.9", optional = true } -tls_rustls = { package = "rustls", version = "0.19.0", optional = true } +tls-openssl = { package = "openssl", version = "0.10.9", optional = true } +tls-rustls = { package = "rustls", version = "0.19.0", optional = true } smallvec = "1.6" +[target.'cfg(windows)'.dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] +optional = true + [dev-dependencies] actix = { version = "0.11.0-beta.2", default-features = false } rand = "0.8" diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6622e04e0..6dcf73637 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -50,6 +50,12 @@ serde_urlencoded = "0.7" time = { version = "0.2.23", default-features = false, features = ["std"] } tls-openssl = { version = "0.10.9", package = "openssl", optional = true } +[target.'cfg(windows)'.dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] +optional = true + [dev-dependencies] actix-web = "4.0.0-beta.3" actix-http = "3.0.0-beta.3" diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index c789c3ae5..2c4775898 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -94,6 +94,11 @@ serde_derive = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } +[target.'cfg(windows)'.dev-dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] + [[bench]] name = "write-camel-case" harness = false diff --git a/awc/Cargo.toml b/awc/Cargo.toml index a6da53664..615a76e09 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -60,6 +60,12 @@ serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tls-rustls = { version = "0.19.0", package = "rustls", optional = true, features = ["dangerous_configuration"] } +[target.'cfg(windows)'.dependencies.tls-openssl] +version = "0.10.9" +package = "openssl" +features = ["vendored"] +optional = true + [dev-dependencies] actix-web = { version = "4.0.0-beta.3", features = ["openssl"] } actix-http = { version = "3.0.0-beta.3", features = ["openssl"] } From b37669cb3b47eeb60c161ff12156d1c877af91a2 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Fri, 12 Feb 2021 20:23:37 -0800 Subject: [PATCH 168/187] fix notify on drop (#1987) --- actix-http/src/config.rs | 40 ++++++++++++++++++++++++++++++++++------ 1 file changed, 34 insertions(+), 6 deletions(-) diff --git a/actix-http/src/config.rs b/actix-http/src/config.rs index f2e71799a..9f84b8694 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -276,30 +276,58 @@ impl DateService { } } -// test drop behavior of DateService. only enabled in tests. // TODO: move to a util module for testing all spawn handle drop style tasks. #[cfg(test)] +/// Test Module for checking the drop state of certain async tasks that are spawned +/// with `actix_rt::spawn` +/// +/// The target task must explicitly generate `NotifyOnDrop` when spawn the task mod notify_on_drop { - use std::sync::atomic::{AtomicBool, Ordering::SeqCst}; + use std::cell::RefCell; - static NOTIFY_DROPPED: AtomicBool = AtomicBool::new(false); + thread_local! { + static NOTIFY_DROPPED: RefCell> = RefCell::new(None); + } + /// Check if the spawned task is dropped. + /// + /// # Panic: + /// + /// When there was no `NotifyOnDrop` instance on current thread pub(crate) fn is_dropped() -> bool { - NOTIFY_DROPPED.load(SeqCst) + NOTIFY_DROPPED.with(|bool| { + bool.borrow() + .expect("No NotifyOnDrop existed on current thread") + }) } pub(crate) struct NotifyOnDrop; impl NotifyOnDrop { + /// # Panic: + /// + /// When construct multiple instances on any given thread. pub(crate) fn new() -> Self { - NOTIFY_DROPPED.store(false, SeqCst); + NOTIFY_DROPPED.with(|bool| { + let mut bool = bool.borrow_mut(); + if bool.is_some() { + panic!("NotifyOnDrop existed on current thread"); + } else { + *bool = Some(false); + } + }); + NotifyOnDrop } } impl Drop for NotifyOnDrop { fn drop(&mut self) { - NOTIFY_DROPPED.store(true, SeqCst); + NOTIFY_DROPPED.with(|bool| { + if let Some(b) = bool.borrow_mut().as_mut() { + *b = true; + } + }); } } } From 3279070f9feb1983709626bae3bde431e287ce88 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 13 Feb 2021 15:08:43 +0000 Subject: [PATCH 169/187] optional cookies features (#1981) --- CHANGES.md | 4 ++ Cargo.toml | 17 ++++--- actix-http/CHANGES.md | 4 ++ actix-http/Cargo.toml | 23 +++++---- actix-http/src/error.rs | 5 +- actix-http/src/h1/encoder.rs | 1 - actix-http/src/http_message.rs | 10 ++-- actix-http/src/lib.rs | 20 +++++++- actix-http/src/response.rs | 92 ++++++++++++++++++++++------------ actix-http/src/test.rs | 35 +++++++------ awc/CHANGES.md | 4 ++ awc/Cargo.toml | 8 ++- awc/src/lib.rs | 4 +- awc/src/request.rs | 11 +++- awc/src/response.rs | 23 +++++---- awc/src/test.rs | 14 +++++- awc/src/ws.rs | 8 ++- src/error.rs | 2 + src/lib.rs | 1 + tests/test_server.rs | 8 +-- 20 files changed, 205 insertions(+), 89 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 954410b30..743f5066b 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Feature `cookies` is now optional and enabled by default. [#1981] + +[#1981]: https://github.com/actix/actix-web/pull/1981 ## 4.0.0-beta.3 - 2021-02-10 diff --git a/Cargo.toml b/Cargo.toml index b0302b352..64b447e14 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -15,6 +15,7 @@ license = "MIT OR Apache-2.0" edition = "2018" [package.metadata.docs.rs] +# features that docs.rs will build with features = ["openssl", "rustls", "compress", "secure-cookies"] [badges] @@ -38,12 +39,15 @@ members = [ ] [features] -default = ["compress"] +default = ["compress", "cookies"] # content-encoding support compress = ["actix-http/compress", "awc/compress"] -# sessions feature +# support for cookies +cookies = ["actix-http/cookies", "awc/cookies"] + +# secure cookies feature secure-cookies = ["actix-http/secure-cookies"] # openssl @@ -95,17 +99,17 @@ futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } log = "0.4" mime = "0.3" -socket2 = "0.3.16" pin-project = "1.0.0" regex = "1.4" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" serde_urlencoded = "0.7" +smallvec = "1.6" +socket2 = "0.3.16" time = { version = "0.2.23", default-features = false, features = ["std"] } -url = "2.1" tls-openssl = { package = "openssl", version = "0.10.9", optional = true } tls-rustls = { package = "rustls", version = "0.19.0", optional = true } -smallvec = "1.6" +url = "2.1" [target.'cfg(windows)'.dependencies.tls-openssl] version = "0.10.9" @@ -122,9 +126,6 @@ brotli2 = "0.3.2" flate2 = "1.0.13" criterion = "0.3" -[profile.dev] -debug = false - [profile.release] lto = true opt-level = 3 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index b781fe50e..54f7357f1 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Feature `cookies` is now optional and disabled by default. [#1981] + +[#1981]: https://github.com/actix/actix-web/pull/1981 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 2c4775898..0e6916a0b 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -15,7 +15,8 @@ license = "MIT OR Apache-2.0" edition = "2018" [package.metadata.docs.rs] -features = ["openssl", "rustls", "compress", "secure-cookies"] +# features that docs.rs will build with +features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"] [lib] name = "actix_http" @@ -30,11 +31,14 @@ openssl = ["actix-tls/openssl"] # rustls support rustls = ["actix-tls/rustls"] -# enable compressison support +# enable compression support compress = ["flate2", "brotli2"] +# support for cookies +cookies = ["cookie"] + # support for secure cookies -secure-cookies = ["cookie/secure"] +secure-cookies = ["cookies", "cookie/secure"] # trust-dns as client dns resolver trust-dns = ["trust-dns-resolver"] @@ -46,24 +50,25 @@ actix-utils = "3.0.0-beta.2" actix-rt = "2" actix-tls = "3.0.0-beta.2" +ahash = "0.7" base64 = "0.13" bitflags = "1.2" bytes = "1" bytestring = "1" -cookie = { version = "0.14.1", features = ["percent-encode"] } +cfg-if = "1" +cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } -ahash = "0.7" h2 = "0.3.0" http = "0.2.2" httparse = "1.3" indexmap = "1.3" itoa = "0.4" -lazy_static = "1.4" language-tags = "0.2" +lazy_static = "1.4" log = "0.4" mime = "0.3" percent-encoding = "2.1" @@ -72,10 +77,10 @@ rand = "0.8" regex = "1.3" serde = "1.0" serde_json = "1.0" -sha-1 = "0.9" -smallvec = "1.6" -slab = "0.4" serde_urlencoded = "0.7" +sha-1 = "0.9" +slab = "0.4" +smallvec = "1.6" time = { version = "0.2.23", default-features = false, features = ["std"] } # compression diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 5eb3c157a..6a9de2d3e 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -19,10 +19,12 @@ use serde_json::error::Error as JsonError; use serde_urlencoded::ser::Error as FormError; use crate::body::Body; -pub use crate::cookie::ParseError as CookieParseError; use crate::helpers::Writer; use crate::response::{Response, ResponseBuilder}; +#[cfg(feature = "cookies")] +pub use crate::cookie::ParseError as CookieParseError; + /// A specialized [`std::result::Result`] /// for actix web operations /// @@ -397,6 +399,7 @@ impl ResponseError for PayloadError { } /// Return `BadRequest` for `cookie::ParseError` +#[cfg(feature = "cookies")] impl ResponseError for crate::cookie::ParseError { fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index 69e69de42..97916e7db 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -549,7 +549,6 @@ mod tests { ); let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap(); - eprintln!("{}", &data); assert!(data.contains("Content-Length: 0\r\n")); assert!(data.contains("Connection: close\r\n")); diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs index 2610b8784..b1f04e50d 100644 --- a/actix-http/src/http_message.rs +++ b/actix-http/src/http_message.rs @@ -5,12 +5,14 @@ use encoding_rs::{Encoding, UTF_8}; use http::header; use mime::Mime; -use crate::cookie::Cookie; -use crate::error::{ContentTypeError, CookieParseError, ParseError}; +use crate::error::{ContentTypeError, ParseError}; use crate::extensions::Extensions; use crate::header::{Header, HeaderMap}; use crate::payload::Payload; +#[cfg(feature = "cookies")] +use crate::{cookie::Cookie, error::CookieParseError}; +#[cfg(feature = "cookies")] struct Cookies(Vec>); /// Trait that implements general purpose operations on HTTP messages. @@ -104,7 +106,7 @@ pub trait HttpMessage: Sized { } /// Load request cookies. - #[inline] + #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { if self.extensions().get::().is_none() { let mut cookies = Vec::new(); @@ -119,12 +121,14 @@ pub trait HttpMessage: Sized { } self.extensions_mut().insert(Cookies(cookies)); } + Ok(Ref::map(self.extensions(), |ext| { &ext.get::().unwrap().0 })) } /// Return request cookie. + #[cfg(feature = "cookies")] fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index a55aaadbf..574d4ef68 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,4 +1,19 @@ //! HTTP primitives for the Actix ecosystem. +//! +//! ## Crate Features +//! | Feature | Functionality | +//! | ---------------- | ----------------------------------------------------- | +//! | `openssl` | TLS support via [OpenSSL]. | +//! | `rustls` | TLS support via [rustls]. | +//! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) | +//! | `cookies` | Support for cookies backed by the [cookie] crate. | +//! | `secure-cookies` | Adds for secure cookies. Enables `cookies` feature. | +//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. | +//! +//! [OpenSSL]: https://crates.io/crates/openssl +//! [rustls]: https://crates.io/crates/rustls +//! [cookie]: https://crates.io/crates/cookie +//! [trust-dns]: https://crates.io/crates/trust-dns #![deny(rust_2018_idioms, nonstandard_style)] #![allow( @@ -34,13 +49,15 @@ mod response; mod service; mod time_parser; -pub use cookie; pub mod error; pub mod h1; pub mod h2; pub mod test; pub mod ws; +#[cfg(feature = "cookies")] +pub use cookie; + pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; @@ -61,6 +78,7 @@ pub mod http { pub use http::{uri, Error, Uri}; pub use http::{Method, StatusCode, Version}; + #[cfg(feature = "cookies")] pub use crate::cookie::{Cookie, CookieBuilder}; pub use crate::header::HeaderMap; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 763243a63..471dacd28 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -16,13 +16,17 @@ use futures_core::Stream; use serde::Serialize; use crate::body::{Body, BodyStream, MessageBody, ResponseBody}; -use crate::cookie::{Cookie, CookieJar}; use crate::error::Error; use crate::extensions::Extensions; use crate::header::{IntoHeaderPair, IntoHeaderValue}; -use crate::http::header::{self, HeaderName, HeaderValue}; +use crate::http::header::{self, HeaderName}; use crate::http::{Error as HttpError, HeaderMap, StatusCode}; use crate::message::{BoxedResponseHead, ConnectionType, ResponseHead}; +#[cfg(feature = "cookies")] +use crate::{ + cookie::{Cookie, CookieJar}, + http::header::HeaderValue, +}; /// An HTTP Response pub struct Response { @@ -133,6 +137,7 @@ impl Response { } /// Get an iterator for the cookies set by this response + #[cfg(feature = "cookies")] #[inline] pub fn cookies(&self) -> CookieIter<'_> { CookieIter { @@ -141,6 +146,7 @@ impl Response { } /// Add a cookie to this response + #[cfg(feature = "cookies")] #[inline] pub fn add_cookie(&mut self, cookie: &Cookie<'_>) -> Result<(), HttpError> { let h = &mut self.head.headers; @@ -153,6 +159,7 @@ impl Response { /// Remove all cookies with the given name from this response. Returns /// the number of cookies removed. + #[cfg(feature = "cookies")] #[inline] pub fn del_cookie(&mut self, name: &str) -> usize { let h = &mut self.head.headers; @@ -298,10 +305,12 @@ impl Future for Response { } } +#[cfg(feature = "cookies")] pub struct CookieIter<'a> { iter: header::GetAll<'a>, } +#[cfg(feature = "cookies")] impl<'a> Iterator for CookieIter<'a> { type Item = Cookie<'a>; @@ -316,13 +325,13 @@ impl<'a> Iterator for CookieIter<'a> { } } -/// An HTTP response builder +/// An HTTP response builder. /// -/// This type can be used to construct an instance of `Response` through a -/// builder-like pattern. +/// This type can be used to construct an instance of `Response` through a builder-like pattern. pub struct ResponseBuilder { head: Option, err: Option, + #[cfg(feature = "cookies")] cookies: Option, } @@ -333,6 +342,7 @@ impl ResponseBuilder { ResponseBuilder { head: Some(BoxedResponseHead::new(status)), err: None, + #[cfg(feature = "cookies")] cookies: None, } } @@ -531,6 +541,7 @@ impl ResponseBuilder { /// .finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie<'c>(&mut self, cookie: Cookie<'c>) -> &mut Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -557,6 +568,7 @@ impl ResponseBuilder { /// builder.finish() /// } /// ``` + #[cfg(feature = "cookies")] pub fn del_cookie<'a>(&mut self, cookie: &Cookie<'a>) -> &mut Self { if self.cookies.is_none() { self.cookies = Some(CookieJar::new()) @@ -624,8 +636,11 @@ impl ResponseBuilder { return Response::from(Error::from(e)).into_body(); } + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] let mut response = self.head.take().expect("cannot reuse response builder"); + #[cfg(feature = "cookies")] if let Some(ref jar) = self.cookies { for cookie in jar.delta() { match HeaderValue::from_str(&cookie.to_string()) { @@ -693,6 +708,7 @@ impl ResponseBuilder { ResponseBuilder { head: self.head.take(), err: self.err.take(), + #[cfg(feature = "cookies")] cookies: self.cookies.take(), } } @@ -712,21 +728,28 @@ fn parts<'a>( /// Convert `Response` to a `ResponseBuilder`. Body get dropped. impl From> for ResponseBuilder { fn from(res: Response) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - for c in res.cookies() { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); + #[cfg(feature = "cookies")] + let jar = { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + + for c in res.cookies() { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } } - } + + jar + }; ResponseBuilder { head: Some(res.head), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } @@ -735,22 +758,6 @@ impl From> for ResponseBuilder { /// Convert `ResponseHead` to a `ResponseBuilder` impl<'a> From<&'a ResponseHead> for ResponseBuilder { fn from(head: &'a ResponseHead) -> ResponseBuilder { - // If this response has cookies, load them into a jar - let mut jar: Option = None; - - let cookies = CookieIter { - iter: head.headers.get_all(header::SET_COOKIE), - }; - for c in cookies { - if let Some(ref mut j) = jar { - j.add_original(c.into_owned()); - } else { - let mut j = CookieJar::new(); - j.add_original(c.into_owned()); - jar = Some(j); - } - } - let mut msg = BoxedResponseHead::new(head.status); msg.version = head.version; msg.reason = head.reason; @@ -761,9 +768,32 @@ impl<'a> From<&'a ResponseHead> for ResponseBuilder { msg.no_chunking(!head.chunked()); + #[cfg(feature = "cookies")] + let jar = { + // If this response has cookies, load them into a jar + let mut jar: Option = None; + + let cookies = CookieIter { + iter: head.headers.get_all(header::SET_COOKIE), + }; + + for c in cookies { + if let Some(ref mut j) = jar { + j.add_original(c.into_owned()); + } else { + let mut j = CookieJar::new(); + j.add_original(c.into_owned()); + jar = Some(j); + } + } + + jar + }; + ResponseBuilder { head: Some(msg), err: None, + #[cfg(feature = "cookies")] cookies: jar, } } diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 8f0a7d21a..870a656df 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -11,13 +11,14 @@ use std::{ use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use bytes::{Bytes, BytesMut}; -use http::{ - header::{self, HeaderValue}, - Method, Uri, Version, -}; +use http::{Method, Uri, Version}; +#[cfg(feature = "cookies")] use crate::{ cookie::{Cookie, CookieJar}, + header::{self, HeaderValue}, +}; +use crate::{ header::{HeaderMap, IntoHeaderPair}, payload::Payload, Request, @@ -53,6 +54,7 @@ struct Inner { method: Method, uri: Uri, headers: HeaderMap, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -64,6 +66,7 @@ impl Default for TestRequest { uri: Uri::from_str("/").unwrap(), version: Version::HTTP_11, headers: HeaderMap::new(), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, })) @@ -132,6 +135,7 @@ impl TestRequest { } /// Set cookie for this request. + #[cfg(feature = "cookies")] pub fn cookie<'a>(&mut self, cookie: Cookie<'a>) -> &mut Self { parts(&mut self.0).cookies.add(cookie.into_owned()); self @@ -165,17 +169,20 @@ impl TestRequest { head.version = inner.version; head.headers = inner.headers; - let cookie: String = inner - .cookies - .delta() - // ensure only name=value is written to cookie header - .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) - .collect::>() - .join("; "); + #[cfg(feature = "cookies")] + { + let cookie: String = inner + .cookies + .delta() + // ensure only name=value is written to cookie header + .map(|c| Cookie::new(c.name(), c.value()).encoded().to_string()) + .collect::>() + .join("; "); - if !cookie.is_empty() { - head.headers - .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); + if !cookie.is_empty() { + head.headers + .insert(header::COOKIE, HeaderValue::from_str(&cookie).unwrap()); + } } req diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 20e8af6df..9224f414d 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Changed +* Feature `cookies` is now optional and enabled by default. [#1981] + +[#1981]: https://github.com/actix/actix-web/pull/1981 ## 3.0.0-beta.2 - 2021-02-10 diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 615a76e09..9beecc6d4 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -22,10 +22,11 @@ name = "awc" path = "src/lib.rs" [package.metadata.docs.rs] -features = ["openssl", "rustls", "compress"] +# features that docs.rs will build with +features = ["openssl", "rustls", "compress", "cookies"] [features] -default = ["compress"] +default = ["compress", "cookies"] # openssl openssl = ["tls-openssl", "actix-http/openssl"] @@ -36,6 +37,9 @@ rustls = ["tls-rustls", "actix-http/rustls"] # content-encoding support compress = ["actix-http/compress"] +# cookie parsing and cookie jar +cookies = ["actix-http/cookies"] + # trust-dns as dns resolver trust-dns = ["actix-http/trust-dns"] diff --git a/awc/src/lib.rs b/awc/src/lib.rs index bd52f7ab1..e50c19c8c 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -97,7 +97,9 @@ use std::convert::TryFrom; use std::rc::Rc; use std::time::Duration; -pub use actix_http::{client::Connector, cookie, http}; +#[cfg(feature = "cookies")] +pub use actix_http::cookie; +pub use actix_http::{client::Connector, http}; use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri}; use actix_http::RequestHead; diff --git a/awc/src/request.rs b/awc/src/request.rs index db8196c5b..812c0e805 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -8,6 +8,7 @@ use futures_core::Stream; use serde::Serialize; use actix_http::body::Body; +#[cfg(feature = "cookies")] use actix_http::cookie::{Cookie, CookieJar}; use actix_http::http::header::{self, IntoHeaderPair}; use actix_http::http::{ @@ -54,10 +55,12 @@ pub struct ClientRequest { pub(crate) head: RequestHead, err: Option, addr: Option, - cookies: Option, response_decompress: bool, timeout: Option, config: Rc, + + #[cfg(feature = "cookies")] + cookies: Option, } impl ClientRequest { @@ -72,6 +75,7 @@ impl ClientRequest { head: RequestHead::default(), err: None, addr: None, + #[cfg(feature = "cookies")] cookies: None, timeout: None, response_decompress: true, @@ -290,6 +294,7 @@ impl ClientRequest { /// println!("Response: {:?}", resp); /// } /// ``` + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -472,7 +477,8 @@ impl ClientRequest { ) } - fn prep_for_sending(mut self) -> Result { + // allow unused mut when cookies feature is disabled + fn prep_for_sending(#[allow(unused_mut)] mut self) -> Result { if let Some(e) = self.err { return Err(e.into()); } @@ -493,6 +499,7 @@ impl ClientRequest { } // set cookies + #[cfg(feature = "cookies")] if let Some(ref mut jar) = self.cookies { let cookie: String = jar .delta() diff --git a/awc/src/response.rs b/awc/src/response.rs index 1c887ee4e..cf687329d 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,20 +1,25 @@ -use std::cell::{Ref, RefMut}; use std::fmt; use std::future::Future; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; +use std::{ + cell::{Ref, RefMut}, + mem, +}; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; -use actix_http::cookie::Cookie; -use actix_http::error::{CookieParseError, PayloadError}; -use actix_http::http::header::{CONTENT_LENGTH, SET_COOKIE}; +use actix_http::error::PayloadError; +use actix_http::http::header; use actix_http::http::{HeaderMap, StatusCode, Version}; use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; use serde::de::DeserializeOwned; +#[cfg(feature = "cookies")] +use actix_http::{cookie::Cookie, error::CookieParseError}; + use crate::error::JsonPayloadError; /// Client Response @@ -39,17 +44,17 @@ impl HttpMessage for ClientResponse { } fn take_payload(&mut self) -> Payload { - std::mem::replace(&mut self.payload, Payload::None) + mem::replace(&mut self.payload, Payload::None) } /// Load request cookies. - #[inline] + #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { struct Cookies(Vec>); if self.extensions().get::().is_none() { let mut cookies = Vec::new(); - for hdr in self.headers().get_all(&SET_COOKIE) { + for hdr in self.headers().get_all(&header::SET_COOKIE) { let s = std::str::from_utf8(hdr.as_bytes()).map_err(CookieParseError::from)?; cookies.push(Cookie::parse_encoded(s)?.into_owned()); } @@ -161,7 +166,7 @@ where /// Create `MessageBody` for request. pub fn new(res: &mut ClientResponse) -> MessageBody { let mut len = None; - if let Some(l) = res.headers().get(&CONTENT_LENGTH) { + if let Some(l) = res.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) @@ -256,7 +261,7 @@ where } let mut len = None; - if let Some(l) = req.headers().get(&CONTENT_LENGTH) { + if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) diff --git a/awc/src/test.rs b/awc/src/test.rs index 84646b9f7..97bbb9c3d 100644 --- a/awc/src/test.rs +++ b/awc/src/test.rs @@ -1,9 +1,13 @@ //! Test helpers for actix http client to use during testing. use std::convert::TryFrom; -use actix_http::cookie::{Cookie, CookieJar}; -use actix_http::http::header::{self, Header, HeaderValue, IntoHeaderValue}; +use actix_http::http::header::{Header, IntoHeaderValue}; use actix_http::http::{Error as HttpError, HeaderName, StatusCode, Version}; +#[cfg(feature = "cookies")] +use actix_http::{ + cookie::{Cookie, CookieJar}, + http::header::{self, HeaderValue}, +}; use actix_http::{h1, Payload, ResponseHead}; use bytes::Bytes; @@ -12,6 +16,7 @@ use crate::ClientResponse; /// Test `ClientResponse` builder pub struct TestResponse { head: ResponseHead, + #[cfg(feature = "cookies")] cookies: CookieJar, payload: Option, } @@ -20,6 +25,7 @@ impl Default for TestResponse { fn default() -> TestResponse { TestResponse { head: ResponseHead::new(StatusCode::OK), + #[cfg(feature = "cookies")] cookies: CookieJar::new(), payload: None, } @@ -69,6 +75,7 @@ impl TestResponse { } /// Set cookie for this response + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.cookies.add(cookie.into_owned()); self @@ -84,8 +91,11 @@ impl TestResponse { /// Complete response creation and generate `ClientResponse` instance pub fn finish(self) -> ClientResponse { + // allow unused mut when cookies feature is disabled + #[allow(unused_mut)] let mut head = self.head; + #[cfg(feature = "cookies")] for cookie in self.cookies.delta() { head.headers.insert( header::SET_COOKIE, diff --git a/awc/src/ws.rs b/awc/src/ws.rs index 8db002836..d5528595d 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -32,6 +32,7 @@ use std::rc::Rc; use std::{fmt, str}; use actix_codec::Framed; +#[cfg(feature = "cookies")] use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; @@ -54,8 +55,10 @@ pub struct WebsocketsRequest { addr: Option, max_size: usize, server_mode: bool, - cookies: Option, config: Rc, + + #[cfg(feature = "cookies")] + cookies: Option, } impl WebsocketsRequest { @@ -89,6 +92,7 @@ impl WebsocketsRequest { protocols: None, max_size: 65_536, server_mode: false, + #[cfg(feature = "cookies")] cookies: None, } } @@ -117,6 +121,7 @@ impl WebsocketsRequest { } /// Set a cookie + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { if self.cookies.is_none() { let mut jar = CookieJar::new(); @@ -270,6 +275,7 @@ impl WebsocketsRequest { } // set cookies + #[cfg(feature = "cookies")] if let Some(ref mut jar) = self.cookies { let cookie: String = jar .delta() diff --git a/src/error.rs b/src/error.rs index 1d7c781d8..0865257d3 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,9 +13,11 @@ pub enum UrlGenerationError { /// Resource not found #[display(fmt = "Resource not found")] ResourceNotFound, + /// Not all path pattern covered #[display(fmt = "Not all path pattern covered")] NotEnoughElements, + /// URL parse error #[display(fmt = "{}", _0)] ParseError(UrlParseError), diff --git a/src/lib.rs b/src/lib.rs index 39cfaf197..73390a55e 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -61,6 +61,7 @@ //! ## Crate Features //! //! * `compress` - content encoding compression support (enabled by default) +//! * `cookies` - cookies support (enabled by default) //! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2` //! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2` //! * `secure-cookies` - secure cookies support diff --git a/tests/test_server.rs b/tests/test_server.rs index c2caa9eb2..c2e627a3f 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -806,15 +806,15 @@ async fn test_server_cookies() { })) }); + let req = srv.get("/"); + let res = req.send().await.unwrap(); + assert!(res.status().is_success()); + let first_cookie = http::CookieBuilder::new("first", "first_value") .http_only(true) .finish(); let second_cookie = http::Cookie::new("second", "second_value"); - let req = srv.get("/"); - let res = req.send().await.unwrap(); - assert!(res.status().is_success()); - let cookies = res.cookies().expect("To have cookies"); assert_eq!(cookies.len(), 2); if cookies[0] == first_cookie { From 7fa6333a0cdc6b7786b767e93ff73042ac365680 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 13 Feb 2021 17:16:36 +0000 Subject: [PATCH 170/187] use rcgen for tls key generation (#1989) --- .github/workflows/ci.yml | 10 ++- Cargo.toml | 10 +-- actix-http/Cargo.toml | 1 + actix-http/tests/test_openssl.rs | 61 +++++++------- actix-http/tests/test_rustls.rs | 45 +++++----- awc/tests/test_connector.rs | 27 +++--- awc/tests/test_ssl_client.rs | 27 +++--- src/lib.rs | 4 +- src/middleware/compat.rs | 11 ++- src/request.rs | 2 + src/test.rs | 5 +- tests/cert.pem | 19 ----- tests/key.pem | 28 ------- tests/test_httpserver.rs | 23 ++++-- tests/test_server.rs | 138 ++++++++++++++++++++----------- 15 files changed, 226 insertions(+), 185 deletions(-) delete mode 100644 tests/cert.pem delete mode 100644 tests/key.pem diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 55af5a380..0126238ef 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,11 +40,17 @@ jobs: - name: Cache Dependencies uses: Swatinem/rust-cache@v1.0.1 + - name: Install cargo-hack + uses: actions-rs/cargo@v1 + with: + command: install + args: cargo-hack + - name: check minimal uses: actions-rs/cargo@v1 with: - command: check - args: --workspace --no-default-features --tests + command: hack + args: --clean-per-run check --workspace --no-default-features --tests - name: check full uses: actions-rs/cargo@v1 diff --git a/Cargo.toml b/Cargo.toml index 64b447e14..1a1b8645c 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -118,13 +118,13 @@ features = ["vendored"] optional = true [dev-dependencies] -actix = { version = "0.11.0-beta.2", default-features = false } -rand = "0.8" -env_logger = "0.8" -serde_derive = "1.0" brotli2 = "0.3.2" -flate2 = "1.0.13" criterion = "0.3" +env_logger = "0.8" +flate2 = "1.0.13" +rand = "0.8" +rcgen = "0.8" +serde_derive = "1.0" [profile.release] lto = true diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 0e6916a0b..69a344d4a 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -95,6 +95,7 @@ actix-http-test = { version = "3.0.0-beta.2", features = ["openssl"] } actix-tls = { version = "3.0.0-beta.2", features = ["openssl"] } criterion = "0.3" env_logger = "0.8" +rcgen = "0.8" serde_derive = "1.0" tls-openssl = { version = "0.10", package = "openssl" } tls-rustls = { version = "0.19", package = "rustls" } diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 188516f83..f44968baa 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -14,7 +14,11 @@ use actix_service::{fn_service, ServiceFactoryExt}; use bytes::{Bytes, BytesMut}; use futures_util::future::{err, ok, ready}; use futures_util::stream::{once, Stream, StreamExt}; -use openssl::ssl::{AlpnError, SslAcceptor, SslFiletype, SslMethod}; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslMethod}, + x509::X509, +}; async fn load_body(stream: S) -> Result where @@ -34,29 +38,26 @@ where Ok(body) } -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys +fn tls_config() -> SslAcceptor { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + let cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; - const H11: &[u8] = b"\x08http/1.1"; if protos.windows(3).any(|window| window == H2) { Ok(b"h2") - } else if protos.windows(9).any(|window| window == H11) { - Ok(b"http/1.1") } else { - Err(AlpnError::NOACK) + Err(openssl::ssl::AlpnError::NOACK) } }); - builder - .set_alpn_protos(b"\x08http/1.1\x02h2") - .expect("Can not contrust SslAcceptor"); + builder.set_alpn_protos(b"\x02h2").unwrap(); builder.build() } @@ -66,7 +67,7 @@ async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, Error>(Response::Ok().finish())) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -85,7 +86,7 @@ async fn test_h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); ok::<_, Error>(Response::Ok().finish()) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -104,7 +105,7 @@ async fn test_h2_body() -> io::Result<()> { let body = load_body(req.take_payload()).await?; Ok::<_, Error>(Response::Ok().body(body)) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -133,7 +134,7 @@ async fn test_h2_content_length() { ]; ok::<_, ()>(Response::new(statuses[indx])) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -195,7 +196,7 @@ async fn test_h2_headers() { } ok::<_, ()>(builder.body(data.clone())) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }).await; @@ -234,7 +235,7 @@ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -252,7 +253,7 @@ async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -276,7 +277,7 @@ async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -299,7 +300,7 @@ async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -323,7 +324,7 @@ async fn test_h2_body_length() { Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), ) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -348,7 +349,7 @@ async fn test_h2_body_chunked_explicit() { .streaming(body), ) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -376,7 +377,7 @@ async fn test_h2_response_http_error_handling() { .body(STR), ) })) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -394,7 +395,7 @@ async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::(ErrorBadRequest("error"))) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; @@ -418,7 +419,7 @@ async fn test_h2_on_connect() { assert!(req.extensions().contains::()); ok::<_, ()>(Response::Ok().finish()) }) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 59ffcfeb0..a36400910 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -31,14 +31,19 @@ where Ok(body) } -fn ssl_acceptor() -> RustlsServerConfig { - // load ssl keys +fn tls_config() -> RustlsServerConfig { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + let mut config = RustlsServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("../tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("../tests/key.pem").unwrap()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); + let cert_chain = certs(cert_file).unwrap(); let mut keys = pkcs8_private_keys(key_file).unwrap(); config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + config } @@ -47,7 +52,7 @@ async fn test_h1() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h1(|_| future::ok::<_, Error>(Response::Ok().finish())) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -61,7 +66,7 @@ async fn test_h2() -> io::Result<()> { let srv = test_server(move || { HttpService::build() .h2(|_| future::ok::<_, Error>(Response::Ok().finish())) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -79,7 +84,7 @@ async fn test_h1_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_11); future::ok::<_, Error>(Response::Ok().finish()) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -97,7 +102,7 @@ async fn test_h2_1() -> io::Result<()> { assert_eq!(req.version(), Version::HTTP_2); future::ok::<_, Error>(Response::Ok().finish()) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -115,7 +120,7 @@ async fn test_h2_body1() -> io::Result<()> { let body = load_body(req.take_payload()).await?; Ok::<_, Error>(Response::Ok().body(body)) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -143,7 +148,7 @@ async fn test_h2_content_length() { ]; future::ok::<_, ()>(Response::new(statuses[indx])) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -203,7 +208,7 @@ async fn test_h2_headers() { } future::ok::<_, ()>(config.body(data.clone())) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }).await; let response = srv.sget("/").send().await.unwrap(); @@ -241,7 +246,7 @@ async fn test_h2_body2() { let mut srv = test_server(move || { HttpService::build() .h2(|_| future::ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -258,7 +263,7 @@ async fn test_h2_head_empty() { let mut srv = test_server(move || { HttpService::build() .finish(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -284,7 +289,7 @@ async fn test_h2_head_binary() { let mut srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -309,7 +314,7 @@ async fn test_h2_head_binary2() { let srv = test_server(move || { HttpService::build() .h2(|_| ok::<_, ()>(Response::Ok().body(STR))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -335,7 +340,7 @@ async fn test_h2_body_length() { Response::Ok().body(body::SizedStream::new(STR.len() as u64, body)), ) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -359,7 +364,7 @@ async fn test_h2_body_chunked_explicit() { .streaming(body), ) }) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -388,7 +393,7 @@ async fn test_h2_response_http_error_handling() { ) })) })) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -405,7 +410,7 @@ async fn test_h2_service_error() { let mut srv = test_server(move || { HttpService::build() .h2(|_| err::(error::ErrorBadRequest("error"))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; @@ -422,7 +427,7 @@ async fn test_h1_service_error() { let mut srv = test_server(move || { HttpService::build() .h1(|_| err::(error::ErrorBadRequest("error"))) - .rustls(ssl_acceptor()) + .rustls(tls_config()) }) .await; diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index 4e4fa5833..fd725506d 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -7,17 +7,23 @@ use actix_http_test::test_server; use actix_service::{map_config, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; -use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, + x509::X509, +}; + +fn tls_config() -> SslAcceptor { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + let cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; if protos.windows(3).any(|window| window == H2) { @@ -27,6 +33,7 @@ fn ssl_acceptor() -> SslAcceptor { } }); builder.set_alpn_protos(b"\x02h2").unwrap(); + builder.build() } @@ -38,7 +45,7 @@ async fn test_connection_window_size() { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()) }) .await; diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index b93c729e5..08aa125cd 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -11,17 +11,23 @@ use actix_service::{map_config, pipeline_factory, ServiceFactoryExt}; use actix_web::http::Version; use actix_web::{dev::AppConfig, web, App, HttpResponse}; use futures_util::future::ok; -use openssl::ssl::{SslAcceptor, SslConnector, SslFiletype, SslMethod, SslVerifyMode}; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslConnector, SslMethod, SslVerifyMode}, + x509::X509, +}; + +fn tls_config() -> SslAcceptor { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + let cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); -fn ssl_acceptor() -> SslAcceptor { - // load ssl keys let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("../tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("../tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + builder.set_alpn_select_callback(|_, protos| { const H2: &[u8] = b"\x02h2"; if protos.windows(3).any(|window| window == H2) { @@ -31,6 +37,7 @@ fn ssl_acceptor() -> SslAcceptor { } }); builder.set_alpn_protos(b"\x02h2").unwrap(); + builder.build() } @@ -51,7 +58,7 @@ async fn test_connection_reuse_h2() { App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))), |_| AppConfig::default(), )) - .openssl(ssl_acceptor()) + .openssl(tls_config()) .map_err(|_| ()), ) }) diff --git a/src/lib.rs b/src/lib.rs index 73390a55e..16b2ab186 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -99,8 +99,10 @@ pub mod test; pub(crate) mod types; pub mod web; +#[cfg(feature = "cookies")] +pub use actix_http::cookie; pub use actix_http::Response as HttpResponse; -pub use actix_http::{body, cookie, http, Error, HttpMessage, ResponseError, Result}; +pub use actix_http::{body, http, 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 97742e92c..6f60264b1 100644 --- a/src/middleware/compat.rs +++ b/src/middleware/compat.rs @@ -123,18 +123,24 @@ impl MapServiceResponseBody for ServiceRespons #[cfg(test)] mod tests { + // easier to code when cookies feature is disabled + #![allow(unused_imports)] + use super::*; use actix_service::IntoService; use crate::dev::ServiceRequest; use crate::http::StatusCode; - use crate::middleware::{Compress, Condition, Logger}; + use crate::middleware::{self, Condition, Logger}; use crate::test::{call_service, init_service, TestRequest}; use crate::{web, App, HttpResponse}; #[actix_rt::test] + #[cfg(feature = "cookies")] async fn test_scope_middleware() { + use crate::middleware::Compress; + let logger = Logger::default(); let compress = Compress::default(); @@ -154,7 +160,10 @@ mod tests { } #[actix_rt::test] + #[cfg(feature = "cookies")] async fn test_resource_scope_middleware() { + use crate::middleware::Compress; + let logger = Logger::default(); let compress = Compress::default(); diff --git a/src/request.rs b/src/request.rs index d62fca300..514b7466e 100644 --- a/src/request.rs +++ b/src/request.rs @@ -429,12 +429,14 @@ mod tests { } #[test] + #[cfg(feature = "cookies")] fn test_no_request_cookies() { let req = TestRequest::default().to_http_request(); assert!(req.cookies().unwrap().is_empty()); } #[test] + #[cfg(feature = "cookies")] fn test_request_cookies() { let req = TestRequest::default() .append_header((header::COOKIE, "cookie1=value1")) diff --git a/src/test.rs b/src/test.rs index d576838de..2ec4252b1 100644 --- a/src/test.rs +++ b/src/test.rs @@ -6,10 +6,12 @@ use std::sync::mpsc; use std::{fmt, net, thread, time}; 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::{cookie::Cookie, ws, Extensions, HttpService, Request}; +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}; @@ -438,6 +440,7 @@ impl TestRequest { } /// Set cookie for this request. + #[cfg(feature = "cookies")] pub fn cookie(mut self, cookie: Cookie<'_>) -> Self { self.req.cookie(cookie); self diff --git a/tests/cert.pem b/tests/cert.pem deleted file mode 100644 index 0eeb6721d..000000000 --- a/tests/cert.pem +++ /dev/null @@ -1,19 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIDEDCCAfgCCQCQdmIZc/Ib/jANBgkqhkiG9w0BAQsFADBKMQswCQYDVQQGEwJ1 -czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZIhvcNAQkBFhJmYWZo -cmQ5MUBnbWFpbC5jb20wHhcNMTkxMTE5MTEwNjU1WhcNMjkxMTE2MTEwNjU1WjBK -MQswCQYDVQQGEwJ1czELMAkGA1UECAwCY2ExCzAJBgNVBAcMAnNmMSEwHwYJKoZI -hvcNAQkBFhJmYWZocmQ5MUBnbWFpbC5jb20wggEiMA0GCSqGSIb3DQEBAQUAA4IB -DwAwggEKAoIBAQDcnaz12CKzUL7248V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C -+kLWKjAc2coqDSbGsrsR6KiH2g06Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezy -XRe/olcHFTeCk/Tllz4xGEplhPua6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqc -K2xntIPreumXpiE3QY4+MWyteiJko4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvu -GccHd/ex8cOwotUqd6emZb+0bVE24Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zU -b2GJosbmfGaf+xTfnGGhTLLL7kCtva+NvZr5AgMBAAEwDQYJKoZIhvcNAQELBQAD -ggEBANftoL8zDGrjCwWvct8kOOqset2ukK8vjIGwfm88CKsy0IfSochNz2qeIu9R -ZuO7c0pfjmRkir9ZQdq9vXgG3ccL9UstFsferPH9W3YJ83kgXg3fa0EmCiN/0hwz -6Ij1ZBiN1j3+d6+PJPgyYFNu2nGwox5mJ9+aRAGe0/9c63PEOY8P2TI4HsiPmYSl -fFR8k/03vr6e+rTKW85BgctjvYKe/TnFxeCQ7dZ+na7vlEtch4tNmy6O/vEk2kCt -5jW0DUxhmRsv2wGmfFRI0+LotHjoXQQZi6nN5aGL3odaGF3gYwIVlZNd3AdkwDQz -BzG0ZwXuDDV9bSs3MfWEWcy4xuU= ------END CERTIFICATE----- diff --git a/tests/key.pem b/tests/key.pem deleted file mode 100644 index a6d308168..000000000 --- a/tests/key.pem +++ /dev/null @@ -1,28 +0,0 @@ ------BEGIN PRIVATE KEY----- -MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQDcnaz12CKzUL72 -48V7Axhms/O9UQXfAdw0yolEfC3P5jADa/1C+kLWKjAc2coqDSbGsrsR6KiH2g06 -Kunx+tSGqUO+Sct7HEehmxndiSwx/hfMWezyXRe/olcHFTeCk/Tllz4xGEplhPua -6GLhJygLOhAMiV8cwCYrgyPqsDduExLDFCqcK2xntIPreumXpiE3QY4+MWyteiJk -o4IWDFf/UwwsdCY5MlFfw1F/Uv9vz7FfOfvuGccHd/ex8cOwotUqd6emZb+0bVE2 -4Sv8U+yLnHIVx/tOkxgMAnJEpAnf2G3Wp3zUb2GJosbmfGaf+xTfnGGhTLLL7kCt -va+NvZr5AgMBAAECggEBAKoU0UwzVgVCQgca8Jt2dnBvWYDhnxIfYAI/BvaKedMm -1ms87OKfB7oOiksjyI0E2JklH72dzZf2jm4CuZt5UjGC+xwPzlTaJ4s6hQVbBHyC -NRyxU1BCXtW5tThbrhD4OjxqjmLRJEIB9OunLtwAEQoeuFLB8Va7+HFhR+Zd9k3f -7aVA93pC5A50NRbZlke4miJ3Q8n7ZF0+UmxkBfm3fbqLk7aMWkoEKwLLTadjRlu1 -bBp0YDStX66I/p1kujqBOdh6VpPvxFOa1sV9pq0jeiGc9YfSkzRSKzIn8GoyviFB -fHeszQdNlcnrSDSNnMABAw+ZpxUO7SCaftjwejEmKZUCgYEA+TY43VpmV95eY7eo -WKwGepiHE0fwQLuKGELmZdZI80tFi73oZMuiB5WzwmkaKGcJmm7KGE9KEvHQCo9j -xvmktBR0VEZH8pmVfun+4h6+0H7m/NKMBBeOyv/IK8jBgHjkkB6e6nmeR7CqTxCw -tf9tbajl1QN8gNzXZSjBDT/lanMCgYEA4qANOKOSiEARtgwyXQeeSJcM2uPv6zF3 -ffM7vjSedtuEOHUSVeyBP/W8KDt7zyPppO/WNbURHS+HV0maS9yyj6zpVS2HGmbs -3fetswsQ+zYVdokW89x4oc2z4XOGHd1LcSlyhRwPt0u2g1E9L0irwTQLWU0npFmG -PRf7sN9+LeMCgYAGkDUDL2ROoB6gRa/7Vdx90hKMoXJkYgwLA4gJ2pDlR3A3c/Lw -5KQJyxmG3zm/IqeQF6be6QesZA30mT4peV2rGHbP2WH/s6fKReNelSy1VQJEWk8x -tGUgV4gwDwN5nLV4TjYlOrq+bJqvpmLhCC8bmj0jVQosYqSRl3cuICasnQKBgGlV -VO/Xb1su1EyWPK5qxRIeSxZOTYw2sMB01nbgxCqge0M2fvA6/hQ5ZlwY0cIEgits -YlcSMsMq/TAAANxz1vbaupUhlSMbZcsBvNV0Nk9c4vr2Wxm7hsJF9u66IEMvQUp2 -pkjiMxfR9CHzF4orr9EcHI5EQ0Grbq5kwFKEfoRbAoGAcWoFPILeJOlp2yW/Ds3E -g2fQdI9BAamtEZEaslJmZMmsDTg5ACPcDkOSFEQIaJ7wLPXeZy74FVk/NrY5F8Gz -bjX9OD/xzwp852yW5L9r62vYJakAlXef5jI6CFdYKDDCcarU0S7W5k6kq9n+wrBR -i1NklYmUAMr2q59uJA5zsic= ------END PRIVATE KEY----- diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 34d57008d..3aa1d36b0 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -73,15 +73,22 @@ async fn test_start() { #[cfg(feature = "openssl")] fn ssl_acceptor() -> std::io::Result { - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - // load ssl keys + use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslMethod}, + x509::X509, + }; + + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + let cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + Ok(builder) } diff --git a/tests/test_server.rs b/tests/test_server.rs index c2e627a3f..2466730f9 100644 --- a/tests/test_server.rs +++ b/tests/test_server.rs @@ -21,6 +21,11 @@ use flate2::{ Compression, }; use futures_util::ready; +use openssl::{ + pkey::PKey, + ssl::{SslAcceptor, SslMethod}, + x509::X509, +}; use rand::{distributions::Alphanumeric, Rng}; use actix_web::dev::BodyEncoding; @@ -49,6 +54,30 @@ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World"; +fn openssl_config() -> SslAcceptor { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); + let cert = X509::from_pem(cert_file.as_bytes()).unwrap(); + let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap(); + + let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); + builder.set_certificate(&cert).unwrap(); + builder.set_private_key(&key).unwrap(); + + builder.set_alpn_select_callback(|_, protos| { + const H2: &[u8] = b"\x02h2"; + if protos.windows(3).any(|window| window == H2) { + Ok(b"h2") + } else { + Err(openssl::ssl::AlpnError::NOACK) + } + }); + builder.set_alpn_protos(b"\x02h2").unwrap(); + + builder.build() +} + struct TestBody { data: Bytes, chunk_size: usize, @@ -700,18 +729,8 @@ async fn test_brotli_encoding_large() { #[cfg(feature = "openssl")] #[actix_rt::test] async fn test_brotli_encoding_large_openssl() { - // load ssl keys - use openssl::ssl::{SslAcceptor, SslFiletype, SslMethod}; - let mut builder = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap(); - builder - .set_private_key_file("tests/key.pem", SslFiletype::PEM) - .unwrap(); - builder - .set_certificate_chain_file("tests/cert.pem") - .unwrap(); - let data = STR.repeat(10); - let srv = test::start_with(test::config().openssl(builder.build()), move || { + 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) @@ -739,53 +758,72 @@ async fn test_brotli_encoding_large_openssl() { } #[cfg(all(feature = "rustls", feature = "openssl"))] -#[actix_rt::test] -async fn test_reading_deflate_encoding_large_random_rustls() { - use rustls::internal::pemfile::{certs, pkcs8_private_keys}; - use rustls::{NoClientAuth, ServerConfig}; - use std::fs::File; +mod plus_rustls { use std::io::BufReader; - let data = rand::thread_rng() - .sample_iter(&Alphanumeric) - .take(160_000) - .map(char::from) - .collect::(); + use rustls::{ + internal::pemfile::{certs, pkcs8_private_keys}, + NoClientAuth, ServerConfig as RustlsServerConfig, + }; - // load ssl keys - let mut config = ServerConfig::new(NoClientAuth::new()); - let cert_file = &mut BufReader::new(File::open("tests/cert.pem").unwrap()); - let key_file = &mut BufReader::new(File::open("tests/key.pem").unwrap()); - let cert_chain = certs(cert_file).unwrap(); - let mut keys = pkcs8_private_keys(key_file).unwrap(); - config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); + use super::*; - let srv = test::start_with(test::config().rustls(config), || { - App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { - HttpResponse::Ok() - .encoding(actix_web::http::ContentEncoding::Identity) - .body(bytes) - }))) - }); + fn rustls_config() -> RustlsServerConfig { + let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap(); + let cert_file = cert.serialize_pem().unwrap(); + let key_file = cert.serialize_private_key_pem(); - // encode data - let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); - e.write_all(data.as_ref()).unwrap(); - let enc = e.finish().unwrap(); + let mut config = RustlsServerConfig::new(NoClientAuth::new()); + let cert_file = &mut BufReader::new(cert_file.as_bytes()); + let key_file = &mut BufReader::new(key_file.as_bytes()); - // client request - let req = srv - .post("/") - .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) - .send_stream(TestBody::new(Bytes::from(enc), 1024)); + let cert_chain = certs(cert_file).unwrap(); + let mut keys = pkcs8_private_keys(key_file).unwrap(); + config.set_single_cert(cert_chain, keys.remove(0)).unwrap(); - let mut response = req.await.unwrap(); - assert!(response.status().is_success()); + config + } - // read response - let bytes = response.body().await.unwrap(); - assert_eq!(bytes.len(), data.len()); - assert_eq!(bytes, Bytes::from(data)); + #[actix_rt::test] + async fn test_reading_deflate_encoding_large_random_rustls() { + use rustls::internal::pemfile::{certs, pkcs8_private_keys}; + use rustls::{NoClientAuth, ServerConfig}; + use std::fs::File; + use std::io::BufReader; + + let data = rand::thread_rng() + .sample_iter(&Alphanumeric) + .take(160_000) + .map(char::from) + .collect::(); + + let srv = test::start_with(test::config().rustls(rustls_config()), || { + App::new().service(web::resource("/").route(web::to(|bytes: Bytes| { + HttpResponse::Ok() + .encoding(actix_web::http::ContentEncoding::Identity) + .body(bytes) + }))) + }); + + // encode data + let mut e = ZlibEncoder::new(Vec::new(), Compression::default()); + e.write_all(data.as_ref()).unwrap(); + let enc = e.finish().unwrap(); + + // client request + let req = srv + .post("/") + .insert_header((actix_web::http::header::CONTENT_ENCODING, "deflate")) + .send_stream(TestBody::new(Bytes::from(enc), 1024)); + + let mut response = req.await.unwrap(); + assert!(response.status().is_success()); + + // read response + let bytes = response.body().await.unwrap(); + assert_eq!(bytes.len(), data.len()); + assert_eq!(bytes, Bytes::from(data)); + } } #[actix_rt::test] From 308b70b03917aa505781a2cc551612279abee74c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 14 Feb 2021 09:36:18 -0800 Subject: [PATCH 171/187] fix potential over read (#1991) --- actix-http/src/h1/dispatcher.rs | 88 ++++++++++++++++++++++----------- 1 file changed, 59 insertions(+), 29 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 2e729b78d..cb15bc1e9 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -728,6 +728,53 @@ where let mut read_some = false; loop { + // Return early when read buf exceed decoder's max buffer size. + if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE { + /* + At this point it's not known IO stream is still scheduled + to be waked up. so force wake up dispatcher just in case. + + Reason: + AsyncRead mostly would only have guarantee wake up + when the poll_read return Poll::Pending. + + Case: + When read_buf is beyond max buffer size the early return + could be successfully be parsed as a new Request. + This case would not generate ParseError::TooLarge + and at this point IO stream is not fully read to Pending + and would result in dispatcher stuck until timeout (KA) + + Note: + This is a perf choice to reduce branch on + ::decode. + + A Request head too large to parse is only checked on + httparse::Status::Partial condition. + */ + if this.payload.is_none() { + /* + When dispatcher has a payload the responsibility of + wake up it would be shift to h1::payload::Payload. + + Reason: + Self wake up when there is payload would waste poll + and/or result in over read. + + Case: + When payload is (partial) dropped by user there is + no need to do read anymore. + At this case read_buf could always remain beyond + MAX_BUFFER_SIZE and self wake up would be busy poll + dispatcher and waste resource. + + */ + cx.waker().wake_by_ref(); + } + + return Ok(false); + } + // grow buffer if necessary. let remaining = this.read_buf.capacity() - this.read_buf.len(); if remaining < LW_BUFFER_SIZE { @@ -735,35 +782,18 @@ where } match actix_codec::poll_read_buf(io.as_mut(), cx, this.read_buf) { - Poll::Pending => return Ok(false), Poll::Ready(Ok(n)) => { if n == 0 { return Ok(true); - } else { - // Return early when read buf exceed decoder's max buffer size. - if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE { - // at this point it's not known io is still scheduled to - // be waked up. so force wake up dispatcher just in case. - // TODO: figure out the overhead. - if this.payload.is_none() { - // When dispatcher has a payload. The responsibility of - // wake up stream would be shift to PayloadSender. - // Therefore no self wake up is needed. - cx.waker().wake_by_ref(); - } - return Ok(false); - } - - read_some = true; } + read_some = true; } + Poll::Pending => return Ok(false), Poll::Ready(Err(err)) => { - return if err.kind() == io::ErrorKind::WouldBlock { - Ok(false) - } else if err.kind() == io::ErrorKind::ConnectionReset && read_some { - Ok(true) - } else { - Err(DispatchError::Io(err)) + return match err.kind() { + io::ErrorKind::WouldBlock => Ok(false), + io::ErrorKind::ConnectionReset if read_some => Ok(true), + _ => Err(DispatchError::Io(err)), } } } @@ -985,7 +1015,7 @@ mod tests { None, ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); match h1.as_mut().poll(cx) { Poll::Pending => panic!(), @@ -1025,7 +1055,7 @@ mod tests { None, ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1079,7 +1109,7 @@ mod tests { None, ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1138,7 +1168,7 @@ mod tests { ", ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_pending()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1210,7 +1240,7 @@ mod tests { ", ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Normal(_))); @@ -1271,7 +1301,7 @@ mod tests { ", ); - futures_util::pin_mut!(h1); + actix_rt::pin!(h1); assert!(h1.as_mut().poll(cx).is_ready()); assert!(matches!(&h1.inner, DispatcherState::Upgrade(_))); From 68d1bd88b13836e2b0ad785b84dcf36d1f96861c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Sun, 14 Feb 2021 10:13:05 -0800 Subject: [PATCH 172/187] remove unused flag upgrade (#1992) --- actix-http/src/h1/dispatcher.rs | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index cb15bc1e9..f7d7f32c3 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -40,7 +40,6 @@ bitflags! { const SHUTDOWN = 0b0000_0100; const READ_DISCONNECT = 0b0000_1000; const WRITE_DISCONNECT = 0b0001_0000; - const UPGRADE = 0b0010_0000; } } @@ -215,10 +214,7 @@ where U::Error: fmt::Display, { fn can_read(&self, cx: &mut Context<'_>) -> bool { - if self - .flags - .intersects(Flags::READ_DISCONNECT | Flags::UPGRADE) - { + if self.flags.contains(Flags::READ_DISCONNECT) { false } else if let Some(ref info) = self.payload { info.need_read(cx) == PayloadStatus::Read @@ -501,7 +497,7 @@ where } /// Process one incoming request. - pub(self) fn poll_request( + fn poll_request( mut self: Pin<&mut Self>, cx: &mut Context<'_>, ) -> Result { From 0404b78b54bc80658630b1af588562aa7245d217 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 15 Feb 2021 11:24:46 +0000 Subject: [PATCH 173/187] improve body size docs --- actix-http/src/body.rs | 26 ++++++++++++++++++++++++++ actix-http/src/client/h2proto.rs | 1 + actix-http/src/encoding/encoder.rs | 1 + 3 files changed, 28 insertions(+) diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs index 0dbe93a4a..4fd578c87 100644 --- a/actix-http/src/body.rs +++ b/actix-http/src/body.rs @@ -15,13 +15,39 @@ use crate::error::Error; /// Body size hint. #[derive(Debug, PartialEq, Copy, Clone)] pub enum BodySize { + /// Absence of body can be assumed from method or status code. + /// + /// Will skip writing Content-Length header. None, + + /// Zero size body. + /// + /// Will write `Content-Length: 0` header. Empty, + + /// Known size body. + /// + /// Will write `Content-Length: N` header. `Sized(0)` is treated the same as `Empty`. Sized(u64), + + /// Unknown size body. + /// + /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. Stream, } impl BodySize { + /// Returns true if size hint indicates no or empty body. + /// + /// ``` + /// # use actix_http::body::BodySize; + /// assert!(BodySize::None.is_eof()); + /// assert!(BodySize::Empty.is_eof()); + /// assert!(BodySize::Sized(0).is_eof()); + /// + /// assert!(!BodySize::Sized(64).is_eof()); + /// assert!(!BodySize::Stream.is_eof()); + /// ``` pub fn is_eof(&self) -> bool { matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0)) } diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index a70bc1738..105e04c73 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -36,6 +36,7 @@ where B: MessageBody, { trace!("Sending client request: {:?} {:?}", head, body.size()); + let head_req = head.as_ref().method == Method::HEAD; let length = body.size(); let eof = matches!( diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 366ecb8c4..ee0587fbd 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -78,6 +78,7 @@ impl Encoder { }); } } + ResponseBody::Body(Encoder { body, eof: false, From 55db3ec65c7bae11a29b9b088d924f2b6bd065c1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 15 Feb 2021 12:20:43 +0000 Subject: [PATCH 174/187] split up http body module --- .github/workflows/ci.yml | 2 +- actix-http/src/body.rs | 740 --------------------------- actix-http/src/body/body.rs | 158 ++++++ actix-http/src/body/body_stream.rs | 59 +++ actix-http/src/body/message_body.rs | 142 +++++ actix-http/src/body/mod.rs | 251 +++++++++ actix-http/src/body/response_body.rs | 77 +++ actix-http/src/body/size.rs | 40 ++ actix-http/src/body/sized_stream.rs | 59 +++ awc/src/error.rs | 3 +- 10 files changed, 789 insertions(+), 742 deletions(-) delete mode 100644 actix-http/src/body.rs create mode 100644 actix-http/src/body/body.rs create mode 100644 actix-http/src/body/body_stream.rs create mode 100644 actix-http/src/body/message_body.rs create mode 100644 actix-http/src/body/mod.rs create mode 100644 actix-http/src/body/response_body.rs create mode 100644 actix-http/src/body/size.rs create mode 100644 actix-http/src/body/sized_stream.rs diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0126238ef..d61c96963 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,7 +87,7 @@ jobs: && github.ref == 'refs/heads/master' run: | cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --out Xml + cargo tarpaulin --out Xml --verbose - name: Upload to Codecov if: > matrix.target.os == 'ubuntu-latest' diff --git a/actix-http/src/body.rs b/actix-http/src/body.rs deleted file mode 100644 index 4fd578c87..000000000 --- a/actix-http/src/body.rs +++ /dev/null @@ -1,740 +0,0 @@ -//! Traits and structures to aid consuming and writing HTTP payloads. - -use std::{ - fmt, mem, - pin::Pin, - task::{Context, Poll}, -}; - -use bytes::{Bytes, BytesMut}; -use futures_core::{ready, Stream}; -use pin_project::pin_project; - -use crate::error::Error; - -/// Body size hint. -#[derive(Debug, PartialEq, Copy, Clone)] -pub enum BodySize { - /// Absence of body can be assumed from method or status code. - /// - /// Will skip writing Content-Length header. - None, - - /// Zero size body. - /// - /// Will write `Content-Length: 0` header. - Empty, - - /// Known size body. - /// - /// Will write `Content-Length: N` header. `Sized(0)` is treated the same as `Empty`. - Sized(u64), - - /// Unknown size body. - /// - /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. - Stream, -} - -impl BodySize { - /// Returns true if size hint indicates no or empty body. - /// - /// ``` - /// # use actix_http::body::BodySize; - /// assert!(BodySize::None.is_eof()); - /// assert!(BodySize::Empty.is_eof()); - /// assert!(BodySize::Sized(0).is_eof()); - /// - /// assert!(!BodySize::Sized(64).is_eof()); - /// assert!(!BodySize::Stream.is_eof()); - /// ``` - pub fn is_eof(&self) -> bool { - matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0)) - } -} - -/// Type that implement this trait can be streamed to a peer. -pub trait MessageBody { - fn size(&self) -> BodySize; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>>; - - downcast_get_type_id!(); -} - -downcast!(MessageBody); - -impl MessageBody for () { - fn size(&self) -> BodySize { - BodySize::Empty - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - Poll::Ready(None) - } -} - -impl MessageBody for Box { - fn size(&self) -> BodySize { - self.as_ref().size() - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - Pin::new(self.get_mut().as_mut()).poll_next(cx) - } -} - -#[pin_project(project = ResponseBodyProj)] -pub enum ResponseBody { - Body(#[pin] B), - Other(Body), -} - -impl ResponseBody { - pub fn into_body(self) -> ResponseBody { - match self { - ResponseBody::Body(b) => ResponseBody::Other(b), - ResponseBody::Other(b) => ResponseBody::Other(b), - } - } -} - -impl ResponseBody { - pub fn take_body(&mut self) -> ResponseBody { - mem::replace(self, ResponseBody::Other(Body::None)) - } -} - -impl ResponseBody { - pub fn as_ref(&self) -> Option<&B> { - if let ResponseBody::Body(ref b) = self { - Some(b) - } else { - None - } - } -} - -impl MessageBody for ResponseBody { - fn size(&self) -> BodySize { - match self { - ResponseBody::Body(ref body) => body.size(), - ResponseBody::Other(ref body) => body.size(), - } - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } - } -} - -impl Stream for ResponseBody { - type Item = Result; - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { - match self.project() { - ResponseBodyProj::Body(body) => body.poll_next(cx), - ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), - } - } -} - -/// Represents various types of HTTP message body. -pub enum Body { - /// Empty response. `Content-Length` header is not set. - None, - /// Zero sized response body. `Content-Length` header is set to `0`. - Empty, - /// Specific response body. - Bytes(Bytes), - /// Generic message body. - Message(Box), -} - -impl Body { - /// Create body from slice (copy) - pub fn from_slice(s: &[u8]) -> Body { - Body::Bytes(Bytes::copy_from_slice(s)) - } - - /// Create body from generic message body. - pub fn from_message(body: B) -> Body { - Body::Message(Box::new(body)) - } -} - -impl MessageBody for Body { - fn size(&self) -> BodySize { - match self { - Body::None => BodySize::None, - Body::Empty => BodySize::Empty, - Body::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), - Body::Message(ref body) => body.size(), - } - } - - fn poll_next( - self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - match self.get_mut() { - Body::None => Poll::Ready(None), - Body::Empty => Poll::Ready(None), - Body::Bytes(ref mut bin) => { - let len = bin.len(); - if len == 0 { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(bin)))) - } - } - Body::Message(body) => Pin::new(&mut **body).poll_next(cx), - } - } -} - -impl PartialEq for Body { - fn eq(&self, other: &Body) -> bool { - match *self { - Body::None => matches!(*other, Body::None), - Body::Empty => matches!(*other, Body::Empty), - Body::Bytes(ref b) => match *other { - Body::Bytes(ref b2) => b == b2, - _ => false, - }, - Body::Message(_) => false, - } - } -} - -impl fmt::Debug for Body { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { - Body::None => write!(f, "Body::None"), - Body::Empty => write!(f, "Body::Empty"), - Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), - Body::Message(_) => write!(f, "Body::Message(_)"), - } - } -} - -impl From<&'static str> for Body { - fn from(s: &'static str) -> Body { - Body::Bytes(Bytes::from_static(s.as_ref())) - } -} - -impl From<&'static [u8]> for Body { - fn from(s: &'static [u8]) -> Body { - Body::Bytes(Bytes::from_static(s)) - } -} - -impl From> for Body { - fn from(vec: Vec) -> Body { - Body::Bytes(Bytes::from(vec)) - } -} - -impl From for Body { - fn from(s: String) -> Body { - s.into_bytes().into() - } -} - -impl<'a> From<&'a String> for Body { - fn from(s: &'a String) -> Body { - Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) - } -} - -impl From for Body { - fn from(s: Bytes) -> Body { - Body::Bytes(s) - } -} - -impl From for Body { - fn from(s: BytesMut) -> Body { - Body::Bytes(s.freeze()) - } -} - -impl From for Body { - fn from(v: serde_json::Value) -> Body { - Body::Bytes(v.to_string().into()) - } -} - -impl From> for Body -where - S: Stream> + Unpin + 'static, -{ - fn from(s: SizedStream) -> Body { - Body::from_message(s) - } -} - -impl From> for Body -where - S: Stream> + Unpin + 'static, - E: Into + 'static, -{ - fn from(s: BodyStream) -> Body { - Body::from_message(s) - } -} - -impl MessageBody for Bytes { - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(self.get_mut())))) - } - } -} - -impl MessageBody for BytesMut { - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze()))) - } - } -} - -impl MessageBody for &'static str { - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from_static( - mem::take(self.get_mut()).as_ref(), - )))) - } - } -} - -impl MessageBody for Vec { - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut()))))) - } - } -} - -impl MessageBody for String { - fn size(&self) -> BodySize { - BodySize::Sized(self.len() as u64) - } - - fn poll_next( - self: Pin<&mut Self>, - _: &mut Context<'_>, - ) -> Poll>> { - if self.is_empty() { - Poll::Ready(None) - } else { - Poll::Ready(Some(Ok(Bytes::from( - mem::take(self.get_mut()).into_bytes(), - )))) - } - } -} - -/// Type represent streaming body. -/// Response does not contain `content-length` header and appropriate transfer encoding is used. -pub struct BodyStream { - stream: S, -} - -impl BodyStream -where - S: Stream> + Unpin, - E: Into, -{ - pub fn new(stream: S) -> Self { - BodyStream { stream } - } -} - -impl MessageBody for BodyStream -where - S: Stream> + Unpin, - E: Into, -{ - fn size(&self) -> BodySize { - BodySize::Stream - } - - /// Attempts to pull out the next value of the underlying [`Stream`]. - /// - /// Empty values are skipped to prevent [`BodyStream`]'s transmission being - /// ended on a zero-length chunk, but rather proceed until the underlying - /// [`Stream`] ends. - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - loop { - let stream = &mut self.as_mut().stream; - return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { - Some(Ok(ref bytes)) if bytes.is_empty() => continue, - opt => opt.map(|res| res.map_err(Into::into)), - }); - } - } -} - -/// Type represent streaming body. This body implementation should be used -/// if total size of stream is known. Data get sent as is without using transfer encoding. -pub struct SizedStream { - size: u64, - stream: S, -} - -impl SizedStream -where - S: Stream> + Unpin, -{ - pub fn new(size: u64, stream: S) -> Self { - SizedStream { size, stream } - } -} - -impl MessageBody for SizedStream -where - S: Stream> + Unpin, -{ - fn size(&self) -> BodySize { - BodySize::Sized(self.size as u64) - } - - /// Attempts to pull out the next value of the underlying [`Stream`]. - /// - /// Empty values are skipped to prevent [`SizedStream`]'s transmission being - /// ended on a zero-length chunk, but rather proceed until the underlying - /// [`Stream`] ends. - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll>> { - loop { - let stream = &mut self.as_mut().stream; - return Poll::Ready(match ready!(Pin::new(stream).poll_next(cx)) { - Some(Ok(ref bytes)) if bytes.is_empty() => continue, - val => val, - }); - } - } -} - -#[cfg(test)] -mod tests { - use super::*; - use futures_util::future::poll_fn; - use futures_util::pin_mut; - use futures_util::stream; - - impl Body { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - Body::Bytes(ref bin) => &bin, - _ => panic!(), - } - } - } - - impl ResponseBody { - pub(crate) fn get_ref(&self) -> &[u8] { - match *self { - ResponseBody::Body(ref b) => b.get_ref(), - ResponseBody::Other(ref b) => b.get_ref(), - } - } - } - - #[actix_rt::test] - async fn test_static_str() { - assert_eq!(Body::from("").size(), BodySize::Sized(0)); - assert_eq!(Body::from("test").size(), BodySize::Sized(4)); - assert_eq!(Body::from("test").get_ref(), b"test"); - - assert_eq!("test".size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| Pin::new(&mut "test").poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_static_bytes() { - assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); - assert_eq!( - Body::from_slice(b"test".as_ref()).size(), - BodySize::Sized(4) - ); - assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); - let sb = Bytes::from(&b"test"[..]); - pin_mut!(sb); - - assert_eq!(sb.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_vec() { - assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); - assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); - let test_vec = Vec::from("test"); - pin_mut!(test_vec); - - assert_eq!(test_vec.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| test_vec.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_bytes() { - let b = Bytes::from("test"); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - pin_mut!(b); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_bytes_mut() { - let b = BytesMut::from("test"); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - pin_mut!(b); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_string() { - let b = "test".to_owned(); - assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); - assert_eq!(Body::from(b.clone()).get_ref(), b"test"); - assert_eq!(Body::from(&b).size(), BodySize::Sized(4)); - assert_eq!(Body::from(&b).get_ref(), b"test"); - pin_mut!(b); - - assert_eq!(b.size(), BodySize::Sized(4)); - assert_eq!( - poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), - Some(Bytes::from("test")) - ); - } - - #[actix_rt::test] - async fn test_unit() { - assert_eq!(().size(), BodySize::Empty); - assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx)) - .await - .is_none()); - } - - #[actix_rt::test] - async fn test_box() { - let val = Box::new(()); - pin_mut!(val); - assert_eq!(val.size(), BodySize::Empty); - assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); - } - - #[actix_rt::test] - async fn test_body_eq() { - assert!( - Body::Bytes(Bytes::from_static(b"1")) - == Body::Bytes(Bytes::from_static(b"1")) - ); - assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); - } - - #[actix_rt::test] - async fn test_body_debug() { - assert!(format!("{:?}", Body::None).contains("Body::None")); - assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); - assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains('1')); - } - - #[actix_rt::test] - async fn test_serde_json() { - use serde_json::json; - assert_eq!( - Body::from(serde_json::Value::String("test".into())).size(), - BodySize::Sized(6) - ); - assert_eq!( - Body::from(json!({"test-key":"test-value"})).size(), - BodySize::Sized(25) - ); - } - - mod body_stream { - use super::*; - //use futures::task::noop_waker; - //use futures::stream::once; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let body = BodyStream::new(stream::iter( - ["1", "", "2"] - .iter() - .map(|&v| Ok(Bytes::from(v)) as Result), - )); - pin_mut!(body); - - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - - /* Now it does not compile as it should - #[actix_rt::test] - async fn move_pinned_pointer() { - let (sender, receiver) = futures::channel::oneshot::channel(); - let mut body_stream = Ok(BodyStream::new(once(async { - let x = Box::new(0i32); - let y = &x; - receiver.await.unwrap(); - let _z = **y; - Ok::<_, ()>(Bytes::new()) - }))); - - let waker = noop_waker(); - let mut context = Context::from_waker(&waker); - pin_mut!(body_stream); - - let _ = body_stream.as_mut().unwrap().poll_next(&mut context); - sender.send(()).unwrap(); - let _ = std::mem::replace(&mut body_stream, Err([0; 32])).unwrap().poll_next(&mut context); - }*/ - } - - mod sized_stream { - use super::*; - - #[actix_rt::test] - async fn skips_empty_chunks() { - let body = SizedStream::new( - 2, - stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), - ); - pin_mut!(body); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("1")), - ); - assert_eq!( - poll_fn(|cx| body.as_mut().poll_next(cx)) - .await - .unwrap() - .ok(), - Some(Bytes::from("2")), - ); - } - } - - #[actix_rt::test] - async fn test_body_casting() { - let mut body = String::from("hello cast"); - let resp_body: &mut dyn MessageBody = &mut body; - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast"); - let body = &mut resp_body.downcast_mut::().unwrap(); - body.push('!'); - let body = resp_body.downcast_ref::().unwrap(); - assert_eq!(body, "hello cast!"); - let not_body = resp_body.downcast_ref::<()>(); - assert!(not_body.is_none()); - } -} diff --git a/actix-http/src/body/body.rs b/actix-http/src/body/body.rs new file mode 100644 index 000000000..a3fd7d41c --- /dev/null +++ b/actix-http/src/body/body.rs @@ -0,0 +1,158 @@ +use std::{ + fmt, mem, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::{Bytes, BytesMut}; +use futures_core::Stream; + +use crate::error::Error; + +use super::{BodySize, BodyStream, MessageBody, SizedStream}; + +/// Represents various types of HTTP message body. +pub enum Body { + /// Empty response. `Content-Length` header is not set. + None, + /// Zero sized response body. `Content-Length` header is set to `0`. + Empty, + /// Specific response body. + Bytes(Bytes), + /// Generic message body. + Message(Box), +} + +impl Body { + /// Create body from slice (copy) + pub fn from_slice(s: &[u8]) -> Body { + Body::Bytes(Bytes::copy_from_slice(s)) + } + + /// Create body from generic message body. + pub fn from_message(body: B) -> Body { + Body::Message(Box::new(body)) + } +} + +impl MessageBody for Body { + fn size(&self) -> BodySize { + match self { + Body::None => BodySize::None, + Body::Empty => BodySize::Empty, + Body::Bytes(ref bin) => BodySize::Sized(bin.len() as u64), + Body::Message(ref body) => body.size(), + } + } + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + match self.get_mut() { + Body::None => Poll::Ready(None), + Body::Empty => Poll::Ready(None), + Body::Bytes(ref mut bin) => { + let len = bin.len(); + if len == 0 { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(mem::take(bin)))) + } + } + Body::Message(body) => Pin::new(&mut **body).poll_next(cx), + } + } +} + +impl PartialEq for Body { + fn eq(&self, other: &Body) -> bool { + match *self { + Body::None => matches!(*other, Body::None), + Body::Empty => matches!(*other, Body::Empty), + Body::Bytes(ref b) => match *other { + Body::Bytes(ref b2) => b == b2, + _ => false, + }, + Body::Message(_) => false, + } + } +} + +impl fmt::Debug for Body { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match *self { + Body::None => write!(f, "Body::None"), + Body::Empty => write!(f, "Body::Empty"), + Body::Bytes(ref b) => write!(f, "Body::Bytes({:?})", b), + Body::Message(_) => write!(f, "Body::Message(_)"), + } + } +} + +impl From<&'static str> for Body { + fn from(s: &'static str) -> Body { + Body::Bytes(Bytes::from_static(s.as_ref())) + } +} + +impl From<&'static [u8]> for Body { + fn from(s: &'static [u8]) -> Body { + Body::Bytes(Bytes::from_static(s)) + } +} + +impl From> for Body { + fn from(vec: Vec) -> Body { + Body::Bytes(Bytes::from(vec)) + } +} + +impl From for Body { + fn from(s: String) -> Body { + s.into_bytes().into() + } +} + +impl<'a> From<&'a String> for Body { + fn from(s: &'a String) -> Body { + Body::Bytes(Bytes::copy_from_slice(AsRef::<[u8]>::as_ref(&s))) + } +} + +impl From for Body { + fn from(s: Bytes) -> Body { + Body::Bytes(s) + } +} + +impl From for Body { + fn from(s: BytesMut) -> Body { + Body::Bytes(s.freeze()) + } +} + +impl From for Body { + fn from(v: serde_json::Value) -> Body { + Body::Bytes(v.to_string().into()) + } +} + +impl From> for Body +where + S: Stream> + Unpin + 'static, +{ + fn from(s: SizedStream) -> Body { + Body::from_message(s) + } +} + +impl From> for Body +where + S: Stream> + Unpin + 'static, + E: Into + 'static, +{ + fn from(s: BodyStream) -> Body { + Body::from_message(s) + } +} diff --git a/actix-http/src/body/body_stream.rs b/actix-http/src/body/body_stream.rs new file mode 100644 index 000000000..60e33b161 --- /dev/null +++ b/actix-http/src/body/body_stream.rs @@ -0,0 +1,59 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::{ready, Stream}; + +use crate::error::Error; + +use super::{BodySize, MessageBody}; + +/// Streaming response wrapper. +/// +/// Response does not contain `Content-Length` header and appropriate transfer encoding is used. +pub struct BodyStream { + stream: S, +} + +impl BodyStream +where + S: Stream> + Unpin, + E: Into, +{ + pub fn new(stream: S) -> Self { + BodyStream { stream } + } +} + +impl MessageBody for BodyStream +where + S: Stream> + Unpin, + E: Into, +{ + fn size(&self) -> BodySize { + BodySize::Stream + } + + /// Attempts to pull out the next value of the underlying [`Stream`]. + /// + /// Empty values are skipped to prevent [`BodyStream`]'s transmission being + /// ended on a zero-length chunk, but rather proceed until the underlying + /// [`Stream`] ends. + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + loop { + let stream = &mut self.as_mut().stream; + + let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + Some(Ok(ref bytes)) if bytes.is_empty() => continue, + opt => opt.map(|res| res.map_err(Into::into)), + }; + + return Poll::Ready(chunk); + } + } +} diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs new file mode 100644 index 000000000..012329146 --- /dev/null +++ b/actix-http/src/body/message_body.rs @@ -0,0 +1,142 @@ +//! [`MessageBody`] trait and foreign implementations. + +use std::{ + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::{Bytes, BytesMut}; + +use crate::error::Error; + +use super::BodySize; + +/// Type that implement this trait can be streamed to a peer. +pub trait MessageBody { + fn size(&self) -> BodySize; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>>; + + downcast_get_type_id!(); +} + +downcast!(MessageBody); + +impl MessageBody for () { + fn size(&self) -> BodySize { + BodySize::Empty + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + Poll::Ready(None) + } +} + +impl MessageBody for Box { + fn size(&self) -> BodySize { + self.as_ref().size() + } + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + Pin::new(self.get_mut().as_mut()).poll_next(cx) + } +} + +impl MessageBody for Bytes { + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(mem::take(self.get_mut())))) + } + } +} + +impl MessageBody for BytesMut { + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze()))) + } + } +} + +impl MessageBody for &'static str { + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(Bytes::from_static( + mem::take(self.get_mut()).as_ref(), + )))) + } + } +} + +impl MessageBody for Vec { + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(Bytes::from(mem::take(self.get_mut()))))) + } + } +} + +impl MessageBody for String { + fn size(&self) -> BodySize { + BodySize::Sized(self.len() as u64) + } + + fn poll_next( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll>> { + if self.is_empty() { + Poll::Ready(None) + } else { + Poll::Ready(Some(Ok(Bytes::from( + mem::take(self.get_mut()).into_bytes(), + )))) + } + } +} diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs new file mode 100644 index 000000000..1080c67e2 --- /dev/null +++ b/actix-http/src/body/mod.rs @@ -0,0 +1,251 @@ +//! Traits and structures to aid consuming and writing HTTP payloads. + +mod body; +mod body_stream; +mod message_body; +mod response_body; +mod size; +mod sized_stream; + +pub use self::body::Body; +pub use self::body_stream::BodyStream; +pub use self::message_body::MessageBody; +pub use self::response_body::ResponseBody; +pub use self::size::BodySize; +pub use self::sized_stream::SizedStream; + +#[cfg(test)] +mod tests { + use std::pin::Pin; + + use actix_rt::pin; + use bytes::{Bytes, BytesMut}; + use futures_util::{future::poll_fn, stream}; + + use super::*; + + impl Body { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + Body::Bytes(ref bin) => &bin, + _ => panic!(), + } + } + } + + impl ResponseBody { + pub(crate) fn get_ref(&self) -> &[u8] { + match *self { + ResponseBody::Body(ref b) => b.get_ref(), + ResponseBody::Other(ref b) => b.get_ref(), + } + } + } + + #[actix_rt::test] + async fn test_static_str() { + assert_eq!(Body::from("").size(), BodySize::Sized(0)); + assert_eq!(Body::from("test").size(), BodySize::Sized(4)); + assert_eq!(Body::from("test").get_ref(), b"test"); + + assert_eq!("test".size(), BodySize::Sized(4)); + assert_eq!( + poll_fn(|cx| Pin::new(&mut "test").poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("test")) + ); + } + + #[actix_rt::test] + async fn test_static_bytes() { + assert_eq!(Body::from(b"test".as_ref()).size(), BodySize::Sized(4)); + assert_eq!(Body::from(b"test".as_ref()).get_ref(), b"test"); + assert_eq!( + Body::from_slice(b"test".as_ref()).size(), + BodySize::Sized(4) + ); + assert_eq!(Body::from_slice(b"test".as_ref()).get_ref(), b"test"); + let sb = Bytes::from(&b"test"[..]); + pin!(sb); + + assert_eq!(sb.size(), BodySize::Sized(4)); + assert_eq!( + poll_fn(|cx| sb.as_mut().poll_next(cx)).await.unwrap().ok(), + Some(Bytes::from("test")) + ); + } + + #[actix_rt::test] + async fn test_vec() { + assert_eq!(Body::from(Vec::from("test")).size(), BodySize::Sized(4)); + assert_eq!(Body::from(Vec::from("test")).get_ref(), b"test"); + let test_vec = Vec::from("test"); + pin!(test_vec); + + assert_eq!(test_vec.size(), BodySize::Sized(4)); + assert_eq!( + poll_fn(|cx| test_vec.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("test")) + ); + } + + #[actix_rt::test] + async fn test_bytes() { + let b = Bytes::from("test"); + assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + pin!(b); + + assert_eq!(b.size(), BodySize::Sized(4)); + assert_eq!( + poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), + Some(Bytes::from("test")) + ); + } + + #[actix_rt::test] + async fn test_bytes_mut() { + let b = BytesMut::from("test"); + assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + pin!(b); + + assert_eq!(b.size(), BodySize::Sized(4)); + assert_eq!( + poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), + Some(Bytes::from("test")) + ); + } + + #[actix_rt::test] + async fn test_string() { + let b = "test".to_owned(); + assert_eq!(Body::from(b.clone()).size(), BodySize::Sized(4)); + assert_eq!(Body::from(b.clone()).get_ref(), b"test"); + assert_eq!(Body::from(&b).size(), BodySize::Sized(4)); + assert_eq!(Body::from(&b).get_ref(), b"test"); + pin!(b); + + assert_eq!(b.size(), BodySize::Sized(4)); + assert_eq!( + poll_fn(|cx| b.as_mut().poll_next(cx)).await.unwrap().ok(), + Some(Bytes::from("test")) + ); + } + + #[actix_rt::test] + async fn test_unit() { + assert_eq!(().size(), BodySize::Empty); + assert!(poll_fn(|cx| Pin::new(&mut ()).poll_next(cx)) + .await + .is_none()); + } + + #[actix_rt::test] + async fn test_box() { + let val = Box::new(()); + pin!(val); + assert_eq!(val.size(), BodySize::Empty); + assert!(poll_fn(|cx| val.as_mut().poll_next(cx)).await.is_none()); + } + + #[actix_rt::test] + async fn test_body_eq() { + assert!( + Body::Bytes(Bytes::from_static(b"1")) + == Body::Bytes(Bytes::from_static(b"1")) + ); + assert!(Body::Bytes(Bytes::from_static(b"1")) != Body::None); + } + + #[actix_rt::test] + async fn test_body_debug() { + assert!(format!("{:?}", Body::None).contains("Body::None")); + assert!(format!("{:?}", Body::Empty).contains("Body::Empty")); + assert!(format!("{:?}", Body::Bytes(Bytes::from_static(b"1"))).contains('1')); + } + + #[actix_rt::test] + async fn test_serde_json() { + use serde_json::json; + assert_eq!( + Body::from(serde_json::Value::String("test".into())).size(), + BodySize::Sized(6) + ); + assert_eq!( + Body::from(json!({"test-key":"test-value"})).size(), + BodySize::Sized(25) + ); + } + + #[actix_rt::test] + async fn body_stream_skips_empty_chunks() { + let body = BodyStream::new(stream::iter( + ["1", "", "2"] + .iter() + .map(|&v| Ok(Bytes::from(v)) as Result), + )); + pin!(body); + + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + + mod sized_stream { + use super::*; + + #[actix_rt::test] + async fn skips_empty_chunks() { + let body = SizedStream::new( + 2, + stream::iter(["1", "", "2"].iter().map(|&v| Ok(Bytes::from(v)))), + ); + pin!(body); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("1")), + ); + assert_eq!( + poll_fn(|cx| body.as_mut().poll_next(cx)) + .await + .unwrap() + .ok(), + Some(Bytes::from("2")), + ); + } + } + + #[actix_rt::test] + async fn test_body_casting() { + let mut body = String::from("hello cast"); + let resp_body: &mut dyn MessageBody = &mut body; + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast"); + let body = &mut resp_body.downcast_mut::().unwrap(); + body.push('!'); + let body = resp_body.downcast_ref::().unwrap(); + assert_eq!(body, "hello cast!"); + let not_body = resp_body.downcast_ref::<()>(); + assert!(not_body.is_none()); + } +} diff --git a/actix-http/src/body/response_body.rs b/actix-http/src/body/response_body.rs new file mode 100644 index 000000000..97141e11e --- /dev/null +++ b/actix-http/src/body/response_body.rs @@ -0,0 +1,77 @@ +use std::{ + mem, + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::Stream; +use pin_project::pin_project; + +use crate::error::Error; + +use super::{Body, BodySize, MessageBody}; + +#[pin_project(project = ResponseBodyProj)] +pub enum ResponseBody { + Body(#[pin] B), + Other(Body), +} + +impl ResponseBody { + pub fn into_body(self) -> ResponseBody { + match self { + ResponseBody::Body(b) => ResponseBody::Other(b), + ResponseBody::Other(b) => ResponseBody::Other(b), + } + } +} + +impl ResponseBody { + pub fn take_body(&mut self) -> ResponseBody { + mem::replace(self, ResponseBody::Other(Body::None)) + } +} + +impl ResponseBody { + pub fn as_ref(&self) -> Option<&B> { + if let ResponseBody::Body(ref b) = self { + Some(b) + } else { + None + } + } +} + +impl MessageBody for ResponseBody { + fn size(&self) -> BodySize { + match self { + ResponseBody::Body(ref body) => body.size(), + ResponseBody::Other(ref body) => body.size(), + } + } + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + match self.project() { + ResponseBodyProj::Body(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), + } + } +} + +impl Stream for ResponseBody { + type Item = Result; + + fn poll_next( + self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll> { + match self.project() { + ResponseBodyProj::Body(body) => body.poll_next(cx), + ResponseBodyProj::Other(body) => Pin::new(body).poll_next(cx), + } + } +} diff --git a/actix-http/src/body/size.rs b/actix-http/src/body/size.rs new file mode 100644 index 000000000..775d5b8f1 --- /dev/null +++ b/actix-http/src/body/size.rs @@ -0,0 +1,40 @@ +/// Body size hint. +#[derive(Debug, Clone, Copy, PartialEq)] +pub enum BodySize { + /// Absence of body can be assumed from method or status code. + /// + /// Will skip writing Content-Length header. + None, + + /// Zero size body. + /// + /// Will write `Content-Length: 0` header. + Empty, + + /// Known size body. + /// + /// Will write `Content-Length: N` header. `Sized(0)` is treated the same as `Empty`. + Sized(u64), + + /// Unknown size body. + /// + /// Will not write Content-Length header. Can be used with chunked Transfer-Encoding. + Stream, +} + +impl BodySize { + /// Returns true if size hint indicates no or empty body. + /// + /// ``` + /// # use actix_http::body::BodySize; + /// assert!(BodySize::None.is_eof()); + /// assert!(BodySize::Empty.is_eof()); + /// assert!(BodySize::Sized(0).is_eof()); + /// + /// assert!(!BodySize::Sized(64).is_eof()); + /// assert!(!BodySize::Stream.is_eof()); + /// ``` + pub fn is_eof(&self) -> bool { + matches!(self, BodySize::None | BodySize::Empty | BodySize::Sized(0)) + } +} diff --git a/actix-http/src/body/sized_stream.rs b/actix-http/src/body/sized_stream.rs new file mode 100644 index 000000000..af995a0fb --- /dev/null +++ b/actix-http/src/body/sized_stream.rs @@ -0,0 +1,59 @@ +use std::{ + pin::Pin, + task::{Context, Poll}, +}; + +use bytes::Bytes; +use futures_core::{ready, Stream}; + +use crate::error::Error; + +use super::{BodySize, MessageBody}; + +/// Known sized streaming response wrapper. +/// +/// This body implementation should be used if total size of stream is known. Data get sent as is +/// without using transfer encoding. +pub struct SizedStream { + size: u64, + stream: S, +} + +impl SizedStream +where + S: Stream> + Unpin, +{ + pub fn new(size: u64, stream: S) -> Self { + SizedStream { size, stream } + } +} + +impl MessageBody for SizedStream +where + S: Stream> + Unpin, +{ + fn size(&self) -> BodySize { + BodySize::Sized(self.size as u64) + } + + /// Attempts to pull out the next value of the underlying [`Stream`]. + /// + /// Empty values are skipped to prevent [`SizedStream`]'s transmission being + /// ended on a zero-length chunk, but rather proceed until the underlying + /// [`Stream`] ends. + fn poll_next( + mut self: Pin<&mut Self>, + cx: &mut Context<'_>, + ) -> Poll>> { + loop { + let stream = &mut self.as_mut().stream; + + let chunk = match ready!(Pin::new(stream).poll_next(cx)) { + Some(Ok(ref bytes)) if bytes.is_empty() => continue, + val => val, + }; + + return Poll::Ready(chunk); + } + } +} diff --git a/awc/src/error.rs b/awc/src/error.rs index c60339f76..f86224e62 100644 --- a/awc/src/error.rs +++ b/awc/src/error.rs @@ -1,4 +1,5 @@ -//! Http client errors +//! HTTP client errors + pub use actix_http::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use actix_http::error::PayloadError; pub use actix_http::http::Error as HttpError; From c06572946855e0ac9fdd95d09f83f904374a9f07 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 16 Feb 2021 00:27:14 -0800 Subject: [PATCH 175/187] rework client connection pool (#1994) --- actix-http/CHANGES.md | 5 + actix-http/Cargo.toml | 4 +- actix-http/src/client/connection.rs | 18 +- actix-http/src/client/h1proto.rs | 5 +- actix-http/src/client/pool.rs | 1052 ++++++++++++++------------- actix-http/src/error.rs | 4 - 6 files changed, 564 insertions(+), 524 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 54f7357f1..6ba111eb3 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -4,7 +4,12 @@ ### Changed * Feature `cookies` is now optional and disabled by default. [#1981] +### Removed +* re-export of `futures_channel::oneshot::Canceled` is removed from `error` mod. [#1994] +* `ResponseError` impl for `futures_channel::oneshot::Canceled` is removed. [#1994] + [#1981]: https://github.com/actix/actix-web/pull/1981 +[#1994]: https://github.com/actix/actix-web/pull/1994 ## 3.0.0-beta.3 - 2021-02-10 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 69a344d4a..78fb55079 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -59,13 +59,11 @@ cfg-if = "1" cookie = { version = "0.14.1", features = ["percent-encode"], optional = true } derive_more = "0.99.5" encoding_rs = "0.8" -futures-channel = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.0" http = "0.2.2" httparse = "1.3" -indexmap = "1.3" itoa = "0.4" language-tags = "0.2" lazy_static = "1.4" @@ -79,9 +77,9 @@ serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" sha-1 = "0.9" -slab = "0.4" smallvec = "1.6" time = { version = "0.2.23", default-features = false, features = ["std"] } +tokio = { version = "1.2", features = ["sync"] } # compression brotli2 = { version="0.3.2", optional = true } diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 4c6a6dcb8..0b2c6e1df 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -103,7 +103,10 @@ pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { #[doc(hidden)] /// HTTP client connection -pub struct IoConnection { +pub struct IoConnection +where + T: AsyncWrite + Unpin + 'static, +{ io: Option>, created: time::Instant, pool: Option>, @@ -111,7 +114,7 @@ pub struct IoConnection { impl fmt::Debug for IoConnection where - T: fmt::Debug, + T: AsyncWrite + Unpin + fmt::Debug + 'static, { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { match self.io { @@ -138,6 +141,11 @@ impl IoConnection { pub(crate) fn into_inner(self) -> (ConnectionType, time::Instant) { (self.io.unwrap(), self.created) } + + #[cfg(test)] + pub(crate) fn into_parts(self) -> (ConnectionType, time::Instant, Acquired) { + (self.io.unwrap(), self.created, self.pool.unwrap()) + } } impl Connection for IoConnection @@ -202,7 +210,11 @@ where } #[allow(dead_code)] -pub(crate) enum EitherConnection { +pub(crate) enum EitherConnection +where + A: AsyncRead + AsyncWrite + Unpin + 'static, + B: AsyncRead + AsyncWrite + Unpin + 'static, +{ A(IoConnection), B(IoConnection), } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 92c3c0e1b..1ff5c7017 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -165,7 +165,10 @@ where #[doc(hidden)] /// HTTP client connection -pub struct H1Connection { +pub struct H1Connection +where + T: AsyncWrite + Unpin + 'static, +{ /// T should be `Unpin` io: Option, created: time::Instant, diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index 867ba5c0c..b0b1613ab 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,32 +1,27 @@ use std::cell::RefCell; use std::collections::VecDeque; use std::future::Future; +use std::ops::Deref; use std::pin::Pin; use std::rc::Rc; +use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; -use actix_utils::task::LocalWaker; use ahash::AHashMap; -use bytes::Bytes; -use futures_channel::oneshot; use futures_core::future::LocalBoxFuture; -use futures_util::future::{poll_fn, FutureExt}; -use h2::client::{Connection, SendRequest}; use http::uri::Authority; -use indexmap::IndexSet; use pin_project::pin_project; -use slab::Slab; +use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; -use super::connection::{ConnectionType, IoConnection}; +use super::connection::{ConnectionType, H2Connection, IoConnection}; use super::error::ConnectError; use super::h2proto::handshake; use super::Connect; -use crate::client::connection::H2Connection; #[derive(Clone, Copy, PartialEq)] /// Protocol version @@ -46,358 +41,279 @@ impl From for Key { } } -/// Connections pool -pub(crate) struct ConnectionPool(Rc, Rc>>); - -impl ConnectionPool +/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key +pub(crate) struct ConnectionPool where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, + Io: AsyncWrite + Unpin + 'static, { - pub(crate) fn new(connector: T, config: ConnectorConfig) -> Self { - let connector_rc = Rc::new(connector); - let inner_rc = Rc::new(RefCell::new(Inner { - config, - acquired: 0, - waiters: Slab::new(), - waiters_queue: IndexSet::new(), - available: AHashMap::default(), - waker: LocalWaker::new(), - })); + connector: Rc, + inner: ConnectionPoolInner, +} - // start support future - actix_rt::spawn(ConnectorPoolSupport { - connector: Rc::clone(&connector_rc), - inner: Rc::clone(&inner_rc), - }); +/// wrapper type for check the ref count of Rc. +struct ConnectionPoolInner(Rc>) +where + Io: AsyncWrite + Unpin + 'static; - ConnectionPool(connector_rc, inner_rc) +impl ConnectionPoolInner +where + Io: AsyncWrite + Unpin + 'static, +{ + /// spawn a async for graceful shutdown h1 Io type with a timeout. + fn close(&self, conn: ConnectionType) { + if let Some(timeout) = self.config.disconnect_timeout { + if let ConnectionType::H1(io) = conn { + actix_rt::spawn(CloseConnection::new(io, timeout)); + } + } } } -impl Clone for ConnectionPool +impl Clone for ConnectionPoolInner where - Io: 'static, + Io: AsyncWrite + Unpin + 'static, { fn clone(&self) -> Self { - ConnectionPool(self.0.clone(), self.1.clone()) + Self(Rc::clone(&self.0)) } } -impl Drop for ConnectionPool { - fn drop(&mut self) { - // wake up the ConnectorPoolSupport when dropping so it can exit properly. - self.1.borrow().waker.wake(); - } -} - -impl Service for ConnectionPool +impl Deref for ConnectionPoolInner where + Io: AsyncWrite + Unpin + 'static, +{ + type Target = ConnectionPoolInnerPriv; + + fn deref(&self) -> &Self::Target { + &*self.0 + } +} + +impl Drop for ConnectionPoolInner +where + Io: AsyncWrite + Unpin + 'static, +{ + fn drop(&mut self) { + // When strong count is one it means the pool is dropped + // remove and drop all Io types. + if Rc::strong_count(&self.0) == 1 { + self.permits.close(); + std::mem::take(&mut *self.available.borrow_mut()) + .into_iter() + .for_each(|(_, conns)| { + conns.into_iter().for_each(|pooled| self.close(pooled.conn)) + }); + } + } +} + +struct ConnectionPoolInnerPriv +where + Io: AsyncWrite + Unpin + 'static, +{ + config: ConnectorConfig, + available: RefCell>>>, + permits: Arc, +} + +impl ConnectionPool +where + Io: AsyncWrite + Unpin + 'static, +{ + /// construct a new connection pool. + /// + /// [`super::config::ConnectorConfig`]'s `limit` is used as the max permits allowed + /// for on flight connections. + /// + /// The pool can only have equal to `limit` amount of requests spawning/using Io type + /// concurrently. + /// + /// Any requests beyond limit would be wait in fifo order and get notified in async + /// manner by [`tokio::sync::Semaphore`] + pub(crate) fn new(connector: S, config: ConnectorConfig) -> Self { + let permits = Arc::new(Semaphore::new(config.limit)); + let available = RefCell::new(AHashMap::default()); + let connector = Rc::new(connector); + + let inner = ConnectionPoolInner(Rc::new(ConnectionPoolInnerPriv { + config, + available, + permits, + })); + + Self { connector, inner } + } +} + +impl Clone for ConnectionPool +where + Io: AsyncWrite + Unpin + 'static, +{ + fn clone(&self) -> Self { + Self { + connector: self.connector.clone(), + inner: self.inner.clone(), + } + } +} + +impl Service for ConnectionPool +where + S: Service + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, { type Response = IoConnection; type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.0.poll_ready(cx) + self.connector.poll_ready(cx) } fn call(&self, req: Connect) -> Self::Future { - let connector = self.0.clone(); - let inner = self.1.clone(); + let connector = self.connector.clone(); + let inner = self.inner.clone(); - let fut = async move { + Box::pin(async move { let key = if let Some(authority) = req.uri.authority() { authority.clone().into() } else { return Err(ConnectError::Unresolved); }; - // acquire connection - match poll_fn(|cx| Poll::Ready(inner.borrow_mut().acquire(&key, cx))).await { - Acquire::Acquired(io, created) => { - // use existing connection - Ok(IoConnection::new( - io, - created, - Some(Acquired(key, Some(inner))), - )) + // acquire an owned permit and carry it with connection + let permit = inner + .permits + .clone() + .acquire_owned() + .await + // TODO: use specific error for semaphore acquire error + .map_err(|_| ConnectError::NoRecords)?; + + // check if there is idle connection for given key. + let mut map = inner.available.borrow_mut(); + + let mut conn = None; + if let Some(conns) = map.get_mut(&key) { + let now = Instant::now(); + while let Some(mut c) = conns.pop_front() { + // check the lifetime and drop connection that live for too long. + if (now - c.used) > inner.config.conn_keep_alive + || (now - c.created) > inner.config.conn_lifetime + { + inner.close(c.conn); + // check if the connection is still usable. + } else { + if let ConnectionType::H1(ref mut io) = c.conn { + let check = ConnectionCheckFuture { io }; + match check.await { + ConnectionState::Break => { + inner.close(c.conn); + continue; + } + ConnectionState::Skip => continue, + ConnectionState::Live => conn = Some(c), + } + } else { + conn = Some(c); + } + + break; + } } - Acquire::Available => { - // open tcp connection + }; + + // drop map early to end the borrow_mut of RefCell. + drop(map); + + // construct acquired. It's used to put Io type back to pool/ close the Io type. + // permit is carried with the whole lifecycle of Acquired. + let acquired = Some(Acquired { key, inner, permit }); + + // match the connection and spawn new one if did not get anything. + match conn { + Some(conn) => Ok(IoConnection::new(conn.conn, conn.created, acquired)), + None => { let (io, proto) = connector.call(req).await?; - let config = inner.borrow().config.clone(); - - let guard = OpenGuard::new(key, inner); - if proto == Protocol::Http1 { Ok(IoConnection::new( ConnectionType::H1(io), Instant::now(), - Some(guard.consume()), + acquired, )) } else { - let (sender, connection) = handshake(io, &config).await?; + let config = &acquired.as_ref().unwrap().inner.config; + let (sender, connection) = handshake(io, config).await?; Ok(IoConnection::new( ConnectionType::H2(H2Connection::new(sender, connection)), Instant::now(), - Some(guard.consume()), + acquired, )) } } - _ => { - // connection is not available, wait - let (rx, token) = inner.borrow_mut().wait_for(req); - - let guard = WaiterGuard::new(key, token, inner); - let res = match rx.await { - Err(_) => Err(ConnectError::Disconnected), - Ok(res) => res, - }; - guard.consume(); - res - } } + }) + } +} + +/// Type for check the connection and determine if it's usable. +struct ConnectionCheckFuture<'a, Io> { + io: &'a mut Io, +} + +enum ConnectionState { + Live, + Break, + Skip, +} + +impl Future for ConnectionCheckFuture<'_, Io> +where + Io: AsyncRead + Unpin, +{ + type Output = ConnectionState; + + // this future is only used to get access to Context. + // It should never return Poll::Pending. + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { + let this = self.get_mut(); + let mut buf = [0; 2]; + let mut read_buf = ReadBuf::new(&mut buf); + + let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) { + // io is pending and new data would wake up it. + Poll::Pending => ConnectionState::Live, + // io have data inside. drop it. + Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { + ConnectionState::Break + } + // otherwise skip to next. + _ => ConnectionState::Skip, }; - fut.boxed_local() + Poll::Ready(state) } } -struct WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - key: Key, - token: usize, - inner: Option>>>, -} - -impl WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn new(key: Key, token: usize, inner: Rc>>) -> Self { - Self { - key, - token, - inner: Some(inner), - } - } - - fn consume(mut self) { - let _ = self.inner.take(); - } -} - -impl Drop for WaiterGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(&mut self) { - if let Some(i) = self.inner.take() { - let mut inner = i.as_ref().borrow_mut(); - inner.release_waiter(&self.key, self.token); - inner.check_availability(); - } - } -} - -struct OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - key: Key, - inner: Option>>>, -} - -impl OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn new(key: Key, inner: Rc>>) -> Self { - Self { - key, - inner: Some(inner), - } - } - - fn consume(mut self) -> Acquired { - Acquired(self.key.clone(), self.inner.take()) - } -} - -impl Drop for OpenGuard -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(&mut self) { - if let Some(i) = self.inner.take() { - let mut inner = i.as_ref().borrow_mut(); - inner.release(); - inner.check_availability(); - } - } -} - -enum Acquire { - Acquired(ConnectionType, Instant), - Available, - NotAvailable, -} - -struct AvailableConnection { - io: ConnectionType, +struct PooledConnection { + conn: ConnectionType, used: Instant, created: Instant, } -pub(crate) struct Inner { - config: ConnectorConfig, - acquired: usize, - available: AHashMap>>, - waiters: Slab< - Option<( - Connect, - oneshot::Sender, ConnectError>>, - )>, - >, - waiters_queue: IndexSet<(Key, usize)>, - waker: LocalWaker, -} - -impl Inner { - fn reserve(&mut self) { - self.acquired += 1; - } - - fn release(&mut self) { - self.acquired -= 1; - } - - fn release_waiter(&mut self, key: &Key, token: usize) { - self.waiters.remove(token); - let _ = self.waiters_queue.shift_remove(&(key.clone(), token)); - } -} - -impl Inner -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - /// connection is not available, wait - fn wait_for( - &mut self, - connect: Connect, - ) -> ( - oneshot::Receiver, ConnectError>>, - usize, - ) { - let (tx, rx) = oneshot::channel(); - - let key: Key = connect.uri.authority().unwrap().clone().into(); - let entry = self.waiters.vacant_entry(); - let token = entry.key(); - entry.insert(Some((connect, tx))); - assert!(self.waiters_queue.insert((key, token))); - - (rx, token) - } - - fn acquire(&mut self, key: &Key, cx: &mut Context<'_>) -> Acquire { - // check limits - if self.config.limit > 0 && self.acquired >= self.config.limit { - return Acquire::NotAvailable; - } - - self.reserve(); - - // check if open connection is available - // cleanup stale connections at the same time - if let Some(ref mut connections) = self.available.get_mut(key) { - let now = Instant::now(); - while let Some(conn) = connections.pop_back() { - // check if it still usable - if (now - conn.used) > self.config.conn_keep_alive - || (now - conn.created) > self.config.conn_lifetime - { - if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = conn.io { - actix_rt::spawn(CloseConnection::new(io, timeout)); - } - } - } else { - let mut io = conn.io; - let mut buf = [0; 2]; - let mut read_buf = ReadBuf::new(&mut buf); - if let ConnectionType::H1(ref mut s) = io { - match Pin::new(s).poll_read(cx, &mut read_buf) { - Poll::Pending => {} - Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { - if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new( - io, timeout, - )); - } - } - continue; - } - _ => continue, - } - } - return Acquire::Acquired(io, conn.created); - } - } - } - Acquire::Available - } - - fn release_conn(&mut self, key: &Key, io: ConnectionType, created: Instant) { - self.acquired -= 1; - self.available - .entry(key.clone()) - .or_insert_with(VecDeque::new) - .push_back(AvailableConnection { - io, - created, - used: Instant::now(), - }); - self.check_availability(); - } - - fn release_close(&mut self, io: ConnectionType) { - self.acquired -= 1; - if let Some(timeout) = self.config.disconnect_timeout { - if let ConnectionType::H1(io) = io { - actix_rt::spawn(CloseConnection::new(io, timeout)); - } - } - self.check_availability(); - } - - fn check_availability(&self) { - if !self.waiters_queue.is_empty() && self.acquired < self.config.limit { - self.waker.wake(); - } - } -} - -#[pin_project::pin_project] -struct CloseConnection { - io: T, +#[pin_project] +struct CloseConnection { + io: Io, #[pin] timeout: Sleep, } -impl CloseConnection +impl CloseConnection where - T: AsyncWrite + Unpin, + Io: AsyncWrite + Unpin, { - fn new(io: T, timeout: Duration) -> Self { + fn new(io: Io, timeout: Duration) -> Self { CloseConnection { io, timeout: sleep(timeout), @@ -405,9 +321,9 @@ where } } -impl Future for CloseConnection +impl Future for CloseConnection where - T: AsyncWrite + Unpin, + Io: AsyncWrite + Unpin, { type Output = (); @@ -416,230 +332,340 @@ where match this.timeout.poll(cx) { Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => match Pin::new(this.io).poll_shutdown(cx) { - Poll::Ready(_) => Poll::Ready(()), - Poll::Pending => Poll::Pending, - }, + Poll::Pending => Pin::new(this.io).poll_shutdown(cx).map(|_| ()), } } } -#[pin_project] -struct ConnectorPoolSupport +pub(crate) struct Acquired where - Io: AsyncRead + AsyncWrite + Unpin + 'static, + Io: AsyncWrite + Unpin + 'static, { - connector: Rc, - inner: Rc>>, -} - -impl Future for ConnectorPoolSupport -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service, - T::Future: 'static, -{ - type Output = (); - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.project(); - - if Rc::strong_count(this.inner) == 1 { - // If we are last copy of Inner it means the ConnectionPool is already gone - // and we are safe to exit. - return Poll::Ready(()); - } - - let mut inner = this.inner.borrow_mut(); - inner.waker.register(cx.waker()); - - // check waiters - loop { - let (key, token) = { - if let Some((key, token)) = inner.waiters_queue.get_index(0) { - (key.clone(), *token) - } else { - break; - } - }; - if inner.waiters.get(token).unwrap().is_none() { - continue; - } - - match inner.acquire(&key, cx) { - Acquire::NotAvailable => break, - Acquire::Acquired(io, created) => { - let tx = inner.waiters.get_mut(token).unwrap().take().unwrap().1; - if let Err(conn) = tx.send(Ok(IoConnection::new( - io, - created, - Some(Acquired(key.clone(), Some(this.inner.clone()))), - ))) { - let (io, created) = conn.unwrap().into_inner(); - inner.release_conn(&key, io, created); - } - } - Acquire::Available => { - let (connect, tx) = - inner.waiters.get_mut(token).unwrap().take().unwrap(); - OpenWaitingConnection::spawn( - key.clone(), - tx, - this.inner.clone(), - this.connector.call(connect), - inner.config.clone(), - ); - } - } - let _ = inner.waiters_queue.swap_remove_index(0); - } - - Poll::Pending - } -} - -#[pin_project::pin_project(PinnedDrop)] -struct OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - #[pin] - fut: F, key: Key, - h2: Option< - LocalBoxFuture< - 'static, - Result<(SendRequest, Connection), h2::Error>, - >, - >, - rx: Option, ConnectError>>>, - inner: Option>>>, - config: ConnectorConfig, + inner: ConnectionPoolInner, + permit: OwnedSemaphorePermit, } -impl OpenWaitingConnection +impl Acquired where - F: Future> + 'static, Io: AsyncRead + AsyncWrite + Unpin + 'static, { - fn spawn( - key: Key, - rx: oneshot::Sender, ConnectError>>, - inner: Rc>>, - fut: F, - config: ConnectorConfig, - ) { - actix_rt::spawn(OpenWaitingConnection { - key, - fut, - h2: None, - rx: Some(rx), - inner: Some(inner), - config, + // close the Io type. + pub(crate) fn close(&mut self, conn: IoConnection) { + let (conn, _) = conn.into_inner(); + self.inner.close(conn); + } + + // put the Io type back to pool. + pub(crate) fn release(&mut self, conn: IoConnection) { + let (io, created) = conn.into_inner(); + let Acquired { key, inner, .. } = self; + inner + .available + .borrow_mut() + .entry(key.clone()) + .or_insert_with(VecDeque::new) + .push_back(PooledConnection { + conn: io, + created, + used: Instant::now(), + }); + + // a no op bind. used to stop clippy warning without adding allow attribute. + let _permit = &mut self.permit; + } +} + +#[cfg(test)] +mod test { + use super::*; + + use std::cell::Cell; + use std::io; + + use http::Uri; + + use crate::client::connection::IoConnection; + + // A stream type always return pending on async read. + // mock a usable tcp stream that ready to be used as client + struct TestStream(Rc>); + + impl Drop for TestStream { + fn drop(&mut self) { + self.0.set(self.0.get() - 1); + } + } + + impl AsyncRead for TestStream { + fn poll_read( + self: Pin<&mut Self>, + _: &mut Context<'_>, + _: &mut ReadBuf<'_>, + ) -> Poll> { + Poll::Pending + } + } + + impl AsyncWrite for TestStream { + fn poll_write( + self: Pin<&mut Self>, + _: &mut Context<'_>, + _: &[u8], + ) -> Poll> { + unimplemented!() + } + + fn poll_flush( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll> { + unimplemented!() + } + + fn poll_shutdown( + self: Pin<&mut Self>, + _: &mut Context<'_>, + ) -> Poll> { + Poll::Ready(Ok(())) + } + } + + struct TestPoolConnector { + generated: Rc>, + } + + impl Service for TestPoolConnector { + type Response = (TestStream, Protocol); + type Error = ConnectError; + type Future = LocalBoxFuture<'static, Result>; + + fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { + unimplemented!("poll_ready is not used in test") + } + + fn call(&self, _: Connect) -> Self::Future { + self.generated.set(self.generated.get() + 1); + let generated = self.generated.clone(); + Box::pin(async { Ok((TestStream(generated), Protocol::Http1)) }) + } + } + + fn release(conn: IoConnection) + where + T: AsyncRead + AsyncWrite + Unpin + 'static, + { + let (conn, created, mut acquired) = conn.into_parts(); + acquired.release(IoConnection::new(conn, created, None)); + } + + #[actix_rt::test] + async fn test_pool_limit() { + let connector = TestPoolConnector { + generated: Rc::new(Cell::new(0)), + }; + + let config = ConnectorConfig { + limit: 1, + ..Default::default() + }; + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("http://localhost"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + + let waiting = Rc::new(Cell::new(true)); + + let waiting_clone = waiting.clone(); + actix_rt::spawn(async move { + actix_rt::time::sleep(Duration::from_millis(100)).await; + waiting_clone.set(false); + drop(conn); }); - } -} - -#[pin_project::pinned_drop] -impl PinnedDrop for OpenWaitingConnection -where - Io: AsyncRead + AsyncWrite + Unpin + 'static, -{ - fn drop(self: Pin<&mut Self>) { - if let Some(inner) = self.project().inner.take() { - let mut inner = inner.as_ref().borrow_mut(); - inner.release(); - inner.check_availability(); - } - } -} - -impl Future for OpenWaitingConnection -where - F: Future>, - Io: AsyncRead + AsyncWrite + Unpin, -{ - type Output = (); - - fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - let this = self.as_mut().project(); - - if let Some(ref mut h2) = this.h2 { - return match Pin::new(h2).poll(cx) { - Poll::Ready(Ok((sender, connection))) => { - let rx = this.rx.take().unwrap(); - let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H2(H2Connection::new(sender, connection)), - Instant::now(), - Some(Acquired(this.key.clone(), this.inner.take())), - ))); - Poll::Ready(()) - } - Poll::Pending => Poll::Pending, - Poll::Ready(Err(err)) => { - let _ = this.inner.take(); - if let Some(rx) = this.rx.take() { - let _ = rx.send(Err(ConnectError::H2(err))); - } - Poll::Ready(()) - } - }; - } - - match this.fut.poll(cx) { - Poll::Ready(Err(err)) => { - let _ = this.inner.take(); - if let Some(rx) = this.rx.take() { - let _ = rx.send(Err(err)); - } - Poll::Ready(()) - } - Poll::Ready(Ok((io, proto))) => { - if proto == Protocol::Http1 { - let rx = this.rx.take().unwrap(); - let _ = rx.send(Ok(IoConnection::new( - ConnectionType::H1(io), - Instant::now(), - Some(Acquired(this.key.clone(), this.inner.take())), - ))); - Poll::Ready(()) - } else { - *this.h2 = Some(handshake(io, this.config).boxed_local()); - self.poll(cx) - } - } - Poll::Pending => Poll::Pending, - } - } -} - -pub(crate) struct Acquired(Key, Option>>>); - -impl Acquired -where - T: AsyncRead + AsyncWrite + Unpin + 'static, -{ - pub(crate) fn close(&mut self, conn: IoConnection) { - if let Some(inner) = self.1.take() { - let (io, _) = conn.into_inner(); - inner.as_ref().borrow_mut().release_close(io); - } - } - pub(crate) fn release(&mut self, conn: IoConnection) { - if let Some(inner) = self.1.take() { - let (io, created) = conn.into_inner(); - inner - .as_ref() - .borrow_mut() - .release_conn(&self.0, io, created); - } - } -} - -impl Drop for Acquired { - fn drop(&mut self) { - if let Some(inner) = self.1.take() { - inner.as_ref().borrow_mut().release(); - } + + assert!(waiting.get()); + + let now = Instant::now(); + let conn = pool.call(req).await.unwrap(); + + release(conn); + assert!(!waiting.get()); + assert!(now.elapsed() >= Duration::from_millis(100)); + } + + #[actix_rt::test] + async fn test_pool_keep_alive() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig { + conn_keep_alive: Duration::from_secs(1), + ..Default::default() + }; + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("http://localhost"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + actix_rt::time::sleep(Duration::from_millis(1500)).await; + actix_rt::task::yield_now().await; + + let conn = pool.call(req).await.unwrap(); + // Note: spawned recycle connection is not ran yet. + // This is tokio current thread runtime specific behavior. + assert_eq!(2, generated_clone.get()); + + // yield task so the old connection is properly dropped. + actix_rt::task::yield_now().await; + assert_eq!(1, generated_clone.get()); + + release(conn); + } + + #[actix_rt::test] + async fn test_pool_lifetime() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig { + conn_lifetime: Duration::from_secs(1), + ..Default::default() + }; + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("http://localhost"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + actix_rt::time::sleep(Duration::from_millis(1500)).await; + actix_rt::task::yield_now().await; + + let conn = pool.call(req).await.unwrap(); + // Note: spawned recycle connection is not ran yet. + // This is tokio current thread runtime specific behavior. + assert_eq!(2, generated_clone.get()); + + // yield task so the old connection is properly dropped. + actix_rt::task::yield_now().await; + assert_eq!(1, generated_clone.get()); + + release(conn); + } + + #[actix_rt::test] + async fn test_pool_authority_key() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig::default(); + + let pool = super::ConnectionPool::new(connector, config); + + let req = Connect { + uri: Uri::from_static("https://crates.io"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let conn = pool.call(req).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let req = Connect { + uri: Uri::from_static("https://google.com"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(2, generated_clone.get()); + release(conn); + let conn = pool.call(req).await.unwrap(); + assert_eq!(2, generated_clone.get()); + release(conn); + } + + #[actix_rt::test] + async fn test_pool_drop() { + let generated = Rc::new(Cell::new(0)); + let generated_clone = generated.clone(); + + let connector = TestPoolConnector { generated }; + + let config = ConnectorConfig::default(); + + let pool = Rc::new(super::ConnectionPool::new(connector, config)); + + let req = Connect { + uri: Uri::from_static("https://crates.io"), + addr: None, + }; + + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(1, generated_clone.get()); + release(conn); + + let req = Connect { + uri: Uri::from_static("https://google.com"), + addr: None, + }; + let conn = pool.call(req.clone()).await.unwrap(); + assert_eq!(2, generated_clone.get()); + release(conn); + + let clone1 = pool.clone(); + let clone2 = clone1.clone(); + + drop(clone2); + for _ in 0..2 { + actix_rt::task::yield_now().await; + } + assert_eq!(2, generated_clone.get()); + + drop(clone1); + for _ in 0..2 { + actix_rt::task::yield_now().await; + } + assert_eq!(2, generated_clone.get()); + + drop(pool); + for _ in 0..2 { + actix_rt::task::yield_now().await; + } + assert_eq!(0, generated_clone.get()); } } diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 6a9de2d3e..97f2b3eff 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -11,7 +11,6 @@ use actix_utils::dispatcher::DispatcherError as FramedDispatcherError; use actix_utils::timeout::TimeoutError; use bytes::BytesMut; use derive_more::{Display, From}; -pub use futures_channel::oneshot::Canceled; use http::uri::InvalidUri; use http::{header, Error as HttpError, StatusCode}; use serde::de::value::Error as DeError; @@ -186,9 +185,6 @@ impl ResponseError for DeError { } } -/// Returns [`StatusCode::INTERNAL_SERVER_ERROR`] for [`Canceled`]. -impl ResponseError for Canceled {} - /// Returns [`StatusCode::BAD_REQUEST`] for [`Utf8Error`]. impl ResponseError for Utf8Error { fn status_code(&self) -> StatusCode { From 17b3e7e22512f1bdb17df35369b70fdd67aa6e9d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Feb 2021 09:08:30 +0000 Subject: [PATCH 176/187] pool doc nits (#1999) --- actix-http/src/body/mod.rs | 1 + actix-http/src/client/connection.rs | 7 +- actix-http/src/client/error.rs | 2 +- actix-http/src/client/pool.rs | 141 +++++++++++++++------------- 4 files changed, 80 insertions(+), 71 deletions(-) diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 1080c67e2..a4d6ba2b6 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -1,5 +1,6 @@ //! Traits and structures to aid consuming and writing HTTP payloads. +#[allow(clippy::module_inception)] mod body; mod body_stream; mod message_body; diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 0b2c6e1df..3ab902422 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -26,9 +26,10 @@ pub(crate) enum ConnectionType { H2(H2Connection), } -// h2 connection has two parts: SendRequest and Connection. -// Connection is spawned as async task on runtime and H2Connection would hold a handle for -// this task. So it can wake up and quit the task when SendRequest is dropped. +/// `H2Connection` has two parts: `SendRequest` and `Connection`. +/// +/// `Connection` is spawned as an async task on runtime and `H2Connection` holds a handle for +/// this task. Therefore, it can wake up and quit the task when SendRequest is dropped. pub(crate) struct H2Connection { handle: JoinHandle<()>, sender: SendRequest, diff --git a/actix-http/src/client/error.rs b/actix-http/src/client/error.rs index 7768462b8..d27363456 100644 --- a/actix-http/src/client/error.rs +++ b/actix-http/src/client/error.rs @@ -25,7 +25,7 @@ pub enum ConnectError { Resolver(Box), /// No dns records - #[display(fmt = "No dns records found for the input")] + #[display(fmt = "No DNS records found for the input")] NoRecords, /// Http2 error diff --git a/actix-http/src/client/pool.rs b/actix-http/src/client/pool.rs index b0b1613ab..3800696fa 100644 --- a/actix-http/src/client/pool.rs +++ b/actix-http/src/client/pool.rs @@ -1,4 +1,5 @@ -use std::cell::RefCell; +//! Client connection pooling keyed on the authority part of the connection URI. + use std::collections::VecDeque; use std::future::Future; use std::ops::Deref; @@ -7,14 +8,16 @@ use std::rc::Rc; use std::sync::Arc; use std::task::{Context, Poll}; use std::time::{Duration, Instant}; +use std::{cell::RefCell, io}; -use actix_codec::{AsyncRead, AsyncWrite, ReadBuf}; +use actix_codec::{AsyncRead, AsyncWrite}; use actix_rt::time::{sleep, Sleep}; use actix_service::Service; use ahash::AHashMap; use futures_core::future::LocalBoxFuture; use http::uri::Authority; use pin_project::pin_project; +use tokio::io::ReadBuf; use tokio::sync::{OwnedSemaphorePermit, Semaphore}; use super::config::ConnectorConfig; @@ -41,7 +44,7 @@ impl From for Key { } } -/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key +/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key. pub(crate) struct ConnectionPool where Io: AsyncWrite + Unpin + 'static, @@ -120,16 +123,16 @@ impl ConnectionPool where Io: AsyncWrite + Unpin + 'static, { - /// construct a new connection pool. + /// Construct a new connection pool. /// - /// [`super::config::ConnectorConfig`]'s `limit` is used as the max permits allowed - /// for on flight connections. + /// [`super::config::ConnectorConfig`]'s `limit` is used as the max permits allowed for + /// in-flight connections. /// /// The pool can only have equal to `limit` amount of requests spawning/using Io type /// concurrently. /// - /// Any requests beyond limit would be wait in fifo order and get notified in async - /// manner by [`tokio::sync::Semaphore`] + /// Any requests beyond limit would be wait in fifo order and get notified in async manner + /// by [`tokio::sync::Semaphore`] pub(crate) fn new(connector: S, config: ConnectorConfig) -> Self { let permits = Arc::new(Semaphore::new(config.limit)); let available = RefCell::new(AHashMap::default()); @@ -166,9 +169,7 @@ where type Error = ConnectError; type Future = LocalBoxFuture<'static, Result, ConnectError>>; - fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { - self.connector.poll_ready(cx) - } + actix_service::forward_ready!(connector); fn call(&self, req: Connect) -> Self::Future { let connector = self.connector.clone(); @@ -182,49 +183,55 @@ where }; // acquire an owned permit and carry it with connection - let permit = inner - .permits - .clone() - .acquire_owned() - .await - // TODO: use specific error for semaphore acquire error - .map_err(|_| ConnectError::NoRecords)?; + let permit = inner.permits.clone().acquire_owned().await.map_err(|_| { + ConnectError::Io(io::Error::new( + io::ErrorKind::Other, + "failed to acquire semaphore on client connection pool", + )) + })?; - // check if there is idle connection for given key. - let mut map = inner.available.borrow_mut(); + let conn = { + let mut conn = None; - let mut conn = None; - if let Some(conns) = map.get_mut(&key) { - let now = Instant::now(); - while let Some(mut c) = conns.pop_front() { - // check the lifetime and drop connection that live for too long. - if (now - c.used) > inner.config.conn_keep_alive - || (now - c.created) > inner.config.conn_lifetime - { - inner.close(c.conn); - // check if the connection is still usable. - } else { - if let ConnectionType::H1(ref mut io) = c.conn { - let check = ConnectionCheckFuture { io }; - match check.await { - ConnectionState::Break => { - inner.close(c.conn); - continue; - } - ConnectionState::Skip => continue, - ConnectionState::Live => conn = Some(c), - } + // check if there is idle connection for given key. + let mut map = inner.available.borrow_mut(); + + if let Some(conns) = map.get_mut(&key) { + let now = Instant::now(); + + while let Some(mut c) = conns.pop_front() { + let config = &inner.config; + let idle_dur = now - c.used; + let age = now - c.created; + let conn_ineligible = idle_dur > config.conn_keep_alive + || age > config.conn_lifetime; + + if conn_ineligible { + // drop connections that are too old + inner.close(c.conn); } else { - conn = Some(c); + // check if the connection is still usable + if let ConnectionType::H1(ref mut io) = c.conn { + let check = ConnectionCheckFuture { io }; + match check.await { + ConnectionState::Tainted => { + inner.close(c.conn); + continue; + } + ConnectionState::Skip => continue, + ConnectionState::Live => conn = Some(c), + } + } else { + conn = Some(c); + } + + break; } - - break; } - } - }; + }; - // drop map early to end the borrow_mut of RefCell. - drop(map); + conn + }; // construct acquired. It's used to put Io type back to pool/ close the Io type. // permit is carried with the whole lifecycle of Acquired. @@ -263,8 +270,13 @@ struct ConnectionCheckFuture<'a, Io> { } enum ConnectionState { + /// IO is pending and a new request would wake it. Live, - Break, + + /// IO unexpectedly has unread data and should be dropped. + Tainted, + + /// IO should be skipped but not dropped. Skip, } @@ -282,13 +294,11 @@ where let mut read_buf = ReadBuf::new(&mut buf); let state = match Pin::new(&mut this.io).poll_read(cx, &mut read_buf) { - // io is pending and new data would wake up it. - Poll::Pending => ConnectionState::Live, - // io have data inside. drop it. Poll::Ready(Ok(())) if !read_buf.filled().is_empty() => { - ConnectionState::Break + ConnectionState::Tainted } - // otherwise skip to next. + + Poll::Pending => ConnectionState::Live, _ => ConnectionState::Skip, }; @@ -350,16 +360,17 @@ impl Acquired where Io: AsyncRead + AsyncWrite + Unpin + 'static, { - // close the Io type. + /// Close the IO. pub(crate) fn close(&mut self, conn: IoConnection) { let (conn, _) = conn.into_inner(); self.inner.close(conn); } - // put the Io type back to pool. + /// Release IO back into pool. pub(crate) fn release(&mut self, conn: IoConnection) { let (io, created) = conn.into_inner(); let Acquired { key, inner, .. } = self; + inner .available .borrow_mut() @@ -371,24 +382,22 @@ where used: Instant::now(), }); - // a no op bind. used to stop clippy warning without adding allow attribute. - let _permit = &mut self.permit; + let _ = &mut self.permit; } } #[cfg(test)] mod test { - use super::*; - - use std::cell::Cell; - use std::io; + use std::{cell::Cell, io}; use http::Uri; + use super::*; use crate::client::connection::IoConnection; - // A stream type always return pending on async read. - // mock a usable tcp stream that ready to be used as client + /// A stream type that always returns pending on async read. + /// + /// Mocks an idle TCP stream that is ready to be used for client connections. struct TestStream(Rc>); impl Drop for TestStream { @@ -440,9 +449,7 @@ mod test { type Error = ConnectError; type Future = LocalBoxFuture<'static, Result>; - fn poll_ready(&self, _: &mut Context<'_>) -> Poll> { - unimplemented!("poll_ready is not used in test") - } + actix_service::always_ready!(); fn call(&self, _: Connect) -> Self::Future { self.generated.set(self.generated.get() + 1); From 3e0a9b99ff7327bf57ba4f842242c3e703b9894a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 16 Feb 2021 09:28:14 +0000 Subject: [PATCH 177/187] update rust-cache action --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d61c96963..7d0520d52 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -38,7 +38,7 @@ jobs: with: command: generate-lockfile - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.0.1 + uses: Swatinem/rust-cache@v1.2.0 - name: Install cargo-hack uses: actions-rs/cargo@v1 From 117025a96bc3b30a427551ab4305c65b73b5b39c Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Tue, 16 Feb 2021 06:10:22 -0800 Subject: [PATCH 178/187] simplify client::connection::Connection trait (#1998) --- actix-http/src/client/connection.rs | 100 +++++++++++++--------------- actix-http/src/client/h1proto.rs | 4 +- actix-http/src/client/h2proto.rs | 3 +- awc/src/builder.rs | 1 - awc/src/connect.rs | 2 - 5 files changed, 50 insertions(+), 60 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 3ab902422..707d5551b 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -1,4 +1,3 @@ -use std::future::Future; use std::ops::{Deref, DerefMut}; use std::pin::Pin; use std::task::{Context, Poll}; @@ -8,7 +7,6 @@ use actix_codec::{AsyncRead, AsyncWrite, Framed, ReadBuf}; use actix_rt::task::JoinHandle; use bytes::Bytes; use futures_core::future::LocalBoxFuture; -use futures_util::future::{err, Either, FutureExt, Ready}; use h2::client::SendRequest; use pin_project::pin_project; @@ -75,7 +73,6 @@ impl DerefMut for H2Connection { pub trait Connection { type Io: AsyncRead + AsyncWrite + Unpin; - type Future: Future>; fn protocol(&self) -> Protocol; @@ -84,14 +81,16 @@ pub trait Connection { self, head: H, body: B, - ) -> Self::Future; - - type TunnelFuture: Future< - Output = Result<(ResponseHead, Framed), SendRequestError>, - >; + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; /// Send request, returns Response and Framed - fn open_tunnel>(self, head: H) -> Self::TunnelFuture; + fn open_tunnel + 'static>( + self, + head: H, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, + >; } pub(crate) trait ConnectionLifetime: AsyncRead + AsyncWrite + 'static { @@ -154,8 +153,6 @@ where T: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = T; - type Future = - LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; fn protocol(&self) -> Protocol { match self.io { @@ -169,33 +166,35 @@ where mut self, head: H, body: B, - ) -> Self::Future { + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { match self.io.take().unwrap() { - ConnectionType::H1(io) => { - h1proto::send_request(io, head.into(), body, self.created, self.pool) - .boxed_local() - } - ConnectionType::H2(io) => { - h2proto::send_request(io, head.into(), body, self.created, self.pool) - .boxed_local() - } + ConnectionType::H1(io) => Box::pin(h1proto::send_request( + io, + head.into(), + body, + self.created, + self.pool, + )), + ConnectionType::H2(io) => Box::pin(h2proto::send_request( + io, + head.into(), + body, + self.created, + self.pool, + )), } } - type TunnelFuture = Either< - LocalBoxFuture< - 'static, - Result<(ResponseHead, Framed), SendRequestError>, - >, - Ready), SendRequestError>>, - >; - /// Send request, returns Response and Framed - fn open_tunnel>(mut self, head: H) -> Self::TunnelFuture { + fn open_tunnel>( + mut self, + head: H, + ) -> LocalBoxFuture< + 'static, + Result<(ResponseHead, Framed), SendRequestError>, + > { match self.io.take().unwrap() { - ConnectionType::H1(io) => { - Either::Left(h1proto::open_tunnel(io, head.into()).boxed_local()) - } + ConnectionType::H1(io) => Box::pin(h1proto::open_tunnel(io, head.into())), ConnectionType::H2(io) => { if let Some(mut pool) = self.pool.take() { pool.release(IoConnection::new( @@ -204,7 +203,7 @@ where None, )); } - Either::Right(err(SendRequestError::TunnelNotSupported)) + Box::pin(async { Err(SendRequestError::TunnelNotSupported) }) } } } @@ -226,8 +225,6 @@ where B: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = EitherIo; - type Future = - LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>>; fn protocol(&self) -> Protocol { match self { @@ -240,33 +237,30 @@ where self, head: H, body: RB, - ) -> Self::Future { + ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { match self { EitherConnection::A(con) => con.send_request(head, body), EitherConnection::B(con) => con.send_request(head, body), } } - type TunnelFuture = LocalBoxFuture< + /// Send request, returns Response and Framed + fn open_tunnel + 'static>( + self, + head: H, + ) -> LocalBoxFuture< 'static, Result<(ResponseHead, Framed), SendRequestError>, - >; - - /// Send request, returns Response and Framed - fn open_tunnel>(self, head: H) -> Self::TunnelFuture { + > { match self { - EitherConnection::A(con) => con - .open_tunnel(head) - .map(|res| { - res.map(|(head, framed)| (head, framed.into_map_io(EitherIo::A))) - }) - .boxed_local(), - EitherConnection::B(con) => con - .open_tunnel(head) - .map(|res| { - res.map(|(head, framed)| (head, framed.into_map_io(EitherIo::B))) - }) - .boxed_local(), + EitherConnection::A(con) => Box::pin(async { + let (head, framed) = con.open_tunnel(head).await?; + Ok((head, framed.into_map_io(EitherIo::A))) + }), + EitherConnection::B(con) => Box::pin(async { + let (head, framed) = con.open_tunnel(head).await?; + Ok((head, framed.into_map_io(EitherIo::B))) + }), } } } diff --git a/actix-http/src/client/h1proto.rs b/actix-http/src/client/h1proto.rs index 1ff5c7017..082c4b8e2 100644 --- a/actix-http/src/client/h1proto.rs +++ b/actix-http/src/client/h1proto.rs @@ -8,7 +8,7 @@ use bytes::buf::BufMut; use bytes::{Bytes, BytesMut}; use futures_core::Stream; use futures_util::future::poll_fn; -use futures_util::{pin_mut, SinkExt, StreamExt}; +use futures_util::{SinkExt, StreamExt}; use crate::error::PayloadError; use crate::h1; @@ -127,7 +127,7 @@ where T: ConnectionLifetime + Unpin, B: MessageBody, { - pin_mut!(body); + actix_rt::pin!(body); let mut eof = false; while !eof { diff --git a/actix-http/src/client/h2proto.rs b/actix-http/src/client/h2proto.rs index 105e04c73..0deb5c014 100644 --- a/actix-http/src/client/h2proto.rs +++ b/actix-http/src/client/h2proto.rs @@ -5,7 +5,6 @@ use std::time; use actix_codec::{AsyncRead, AsyncWrite}; use bytes::Bytes; use futures_util::future::poll_fn; -use futures_util::pin_mut; use h2::{ client::{Builder, Connection, SendRequest}, SendStream, @@ -131,7 +130,7 @@ async fn send_body( mut send: SendStream, ) -> Result<(), SendRequestError> { let mut buf = None; - pin_mut!(body); + actix_rt::pin!(body); loop { if buf.is_none() { match poll_fn(|cx| body.as_mut().poll_next(cx)).await { diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 94ffb8a71..3d1613c66 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -52,7 +52,6 @@ impl ClientBuilder { where T: Service + 'static, T::Response: Connection, - ::Future: 'static, T::Future: 'static, { self.connector = Some(Box::new(ConnectorWrapper(connector))); diff --git a/awc/src/connect.rs b/awc/src/connect.rs index a9b8f9f83..9a2ded195 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -41,8 +41,6 @@ where T: Service, T::Response: Connection, ::Io: 'static, - ::Future: 'static, - ::TunnelFuture: 'static, T::Future: 'static, { fn send_request( From 2cc6b47fcf9c2768bb08e8b45208e2ecc132c18d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luka=20Zakraj=C5=A1ek?= Date: Tue, 16 Feb 2021 19:48:16 +0100 Subject: [PATCH 179/187] Use http-range library for HttpRange (#2003) --- actix-files/Cargo.toml | 1 + actix-files/src/range.rs | 87 +++++----------------------------------- 2 files changed, 10 insertions(+), 78 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 45fa18a69..08b7b36fc 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -25,6 +25,7 @@ bitflags = "1" bytes = "1" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +http-range = "0.1.4" derive_more = "0.99.5" log = "0.4" mime = "0.3" diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index 6718980cb..8d9fe9445 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -10,9 +10,6 @@ pub struct HttpRange { pub length: u64, } -const PREFIX: &str = "bytes="; -const PREFIX_LEN: usize = 6; - #[derive(Debug, Clone, Display, Error)] #[display(fmt = "Parse HTTP Range failed")] pub struct ParseRangeErr(#[error(not(source))] ()); @@ -23,82 +20,16 @@ impl HttpRange { /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). /// `size` is full size of response (file). pub fn parse(header: &str, size: u64) -> Result, ParseRangeErr> { - if header.is_empty() { - return Ok(Vec::new()); + match http_range::HttpRange::parse(header, size) { + Ok(ranges) => Ok(ranges + .iter() + .map(|range| HttpRange { + start: range.start, + length: range.length, + }) + .collect()), + Err(_) => Err(ParseRangeErr(())), } - if !header.starts_with(PREFIX) { - return Err(ParseRangeErr(())); - } - - let size_sig = size as i64; - let mut no_overlap = false; - - let all_ranges: Vec> = header[PREFIX_LEN..] - .split(',') - .map(|x| x.trim()) - .filter(|x| !x.is_empty()) - .map(|ra| { - let mut start_end_iter = ra.split('-'); - - let start_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); - let end_str = start_end_iter.next().ok_or(ParseRangeErr(()))?.trim(); - - if start_str.is_empty() { - // If no start is specified, end specifies the - // range start relative to the end of the file. - let mut length: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; - - if length > size_sig { - length = size_sig; - } - - Ok(Some(HttpRange { - start: (size_sig - length) as u64, - length: length as u64, - })) - } else { - let start: i64 = start_str.parse().map_err(|_| ParseRangeErr(()))?; - - if start < 0 { - return Err(ParseRangeErr(())); - } - if start >= size_sig { - no_overlap = true; - return Ok(None); - } - - let length = if end_str.is_empty() { - // If no end is specified, range extends to end of the file. - size_sig - start - } else { - let mut end: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; - - if start > end { - return Err(ParseRangeErr(())); - } - - if end >= size_sig { - end = size_sig - 1; - } - - end - start + 1 - }; - - Ok(Some(HttpRange { - start: start as u64, - length: length as u64, - })) - } - }) - .collect::>()?; - - let ranges: Vec = all_ranges.into_iter().filter_map(|x| x).collect(); - - if no_overlap && ranges.is_empty() { - return Err(ParseRangeErr(())); - } - - Ok(ranges) } } From dfa795ff9da9e7064239b9bb4ea55a90d60bfd11 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Feb 2021 03:18:31 -0800 Subject: [PATCH 180/187] return poll in poll_flush (#2005) --- actix-http/src/h1/dispatcher.rs | 37 +++++++++++++-------------------- 1 file changed, 14 insertions(+), 23 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index f7d7f32c3..839f75402 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -13,6 +13,7 @@ use actix_rt::time::{sleep_until, Instant, Sleep}; use actix_service::Service; use bitflags::bitflags; use bytes::{Buf, BytesMut}; +use futures_core::ready; use log::{error, trace}; use pin_project::pin_project; @@ -233,14 +234,10 @@ where } } - /// Flush stream - /// - /// true - got WouldBlock - /// false - didn't get WouldBlock fn poll_flush( self: Pin<&mut Self>, cx: &mut Context<'_>, - ) -> Result { + ) -> Poll> { let InnerDispatcherProj { io, write_buf, .. } = self.project(); let mut io = Pin::new(io.as_mut().unwrap()); @@ -248,19 +245,18 @@ where let mut written = 0; while written < len { - match io.as_mut().poll_write(cx, &write_buf[written..]) { - Poll::Ready(Ok(0)) => { - return Err(DispatchError::Io(io::Error::new( + match io.as_mut().poll_write(cx, &write_buf[written..])? { + Poll::Ready(0) => { + return Poll::Ready(Err(io::Error::new( io::ErrorKind::WriteZero, "", ))) } - Poll::Ready(Ok(n)) => written += n, + Poll::Ready(n) => written += n, Poll::Pending => { write_buf.advance(written); - return Ok(true); + return Poll::Pending; } - Poll::Ready(Err(err)) => return Err(DispatchError::Io(err)), } } @@ -268,9 +264,7 @@ where write_buf.clear(); // flush the io and check if get blocked. - let blocked = io.poll_flush(cx)?.is_pending(); - - Ok(blocked) + io.poll_flush(cx) } fn send_response( @@ -841,14 +835,11 @@ where if inner.flags.contains(Flags::WRITE_DISCONNECT) { Poll::Ready(Ok(())) } else { - // flush buffer and wait on block. - if inner.as_mut().poll_flush(cx)? { - Poll::Pending - } else { - Pin::new(inner.project().io.as_mut().unwrap()) - .poll_shutdown(cx) - .map_err(DispatchError::from) - } + // flush buffer and wait on blocked. + ready!(inner.as_mut().poll_flush(cx))?; + Pin::new(inner.project().io.as_mut().unwrap()) + .poll_shutdown(cx) + .map_err(DispatchError::from) } } else { // read from io stream and fill read buffer. @@ -888,7 +879,7 @@ where // // TODO: what? is WouldBlock good or bad? // want to find a reference for this macOS behavior - if inner.as_mut().poll_flush(cx)? || !drain { + if inner.as_mut().poll_flush(cx)?.is_pending() || !drain { break; } } From 5efea652e342479d6d0a20afc58bd880a691a712 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Feb 2021 03:55:11 -0800 Subject: [PATCH 181/187] add ClientResponse::timeout (#1931) --- awc/CHANGES.md | 4 ++ awc/src/response.rs | 131 ++++++++++++++++++++++++++++++++------- awc/src/sender.rs | 32 +++++----- awc/tests/test_client.rs | 75 +++++++++++++++++++++- 4 files changed, 202 insertions(+), 40 deletions(-) diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 9224f414d..c67f65560 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -1,9 +1,13 @@ # Changes ## Unreleased - 2021-xx-xx +### Added +* `ClientResponse::timeout` for set the timeout of collecting response body. [#1931] + ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] +[#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 diff --git a/awc/src/response.rs b/awc/src/response.rs index cf687329d..514b8a90b 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -1,20 +1,22 @@ -use std::fmt; -use std::future::Future; -use std::marker::PhantomData; -use std::pin::Pin; -use std::task::{Context, Poll}; use std::{ cell::{Ref, RefMut}, - mem, + fmt, + future::Future, + io, + marker::PhantomData, + pin::Pin, + task::{Context, Poll}, + time::{Duration, Instant}, }; +use actix_http::{ + error::PayloadError, + http::{header, HeaderMap, StatusCode, Version}, + Extensions, HttpMessage, Payload, PayloadStream, ResponseHead, +}; +use actix_rt::time::{sleep, Sleep}; use bytes::{Bytes, BytesMut}; use futures_core::{ready, Stream}; - -use actix_http::error::PayloadError; -use actix_http::http::header; -use actix_http::http::{HeaderMap, StatusCode, Version}; -use actix_http::{Extensions, HttpMessage, Payload, PayloadStream, ResponseHead}; use serde::de::DeserializeOwned; #[cfg(feature = "cookies")] @@ -26,6 +28,38 @@ use crate::error::JsonPayloadError; pub struct ClientResponse { pub(crate) head: ResponseHead, pub(crate) payload: Payload, + pub(crate) timeout: ResponseTimeout, +} + +/// helper enum with reusable sleep passed from `SendClientResponse`. +/// See `ClientResponse::_timeout` for reason. +pub(crate) enum ResponseTimeout { + Disabled(Option>>), + Enabled(Pin>), +} + +impl Default for ResponseTimeout { + fn default() -> Self { + Self::Disabled(None) + } +} + +impl ResponseTimeout { + fn poll_timeout(&mut self, cx: &mut Context<'_>) -> Result<(), PayloadError> { + match *self { + Self::Enabled(ref mut timeout) => { + if timeout.as_mut().poll(cx).is_ready() { + Err(PayloadError::Io(io::Error::new( + io::ErrorKind::TimedOut, + "Response Payload IO timed out", + ))) + } else { + Ok(()) + } + } + Self::Disabled(_) => Ok(()), + } + } } impl HttpMessage for ClientResponse { @@ -35,6 +69,10 @@ impl HttpMessage for ClientResponse { &self.head.headers } + fn take_payload(&mut self) -> Payload { + std::mem::replace(&mut self.payload, Payload::None) + } + fn extensions(&self) -> Ref<'_, Extensions> { self.head.extensions() } @@ -43,10 +81,6 @@ impl HttpMessage for ClientResponse { self.head.extensions_mut() } - fn take_payload(&mut self) -> Payload { - mem::replace(&mut self.payload, Payload::None) - } - /// Load request cookies. #[cfg(feature = "cookies")] fn cookies(&self) -> Result>>, CookieParseError> { @@ -69,7 +103,11 @@ impl HttpMessage for ClientResponse { impl ClientResponse { /// Create new Request instance pub(crate) fn new(head: ResponseHead, payload: Payload) -> Self { - ClientResponse { head, payload } + ClientResponse { + head, + payload, + timeout: ResponseTimeout::default(), + } } #[inline] @@ -105,8 +143,43 @@ impl ClientResponse { ClientResponse { payload, head: self.head, + timeout: self.timeout, } } + + /// Set a timeout duration for [`ClientResponse`](self::ClientResponse). + /// + /// This duration covers the duration of processing the response body stream + /// and would end it as timeout error when deadline met. + /// + /// Disabled by default. + pub fn timeout(self, dur: Duration) -> Self { + let timeout = match self.timeout { + ResponseTimeout::Disabled(Some(mut timeout)) + | ResponseTimeout::Enabled(mut timeout) => match Instant::now().checked_add(dur) { + Some(deadline) => { + timeout.as_mut().reset(deadline.into()); + ResponseTimeout::Enabled(timeout) + } + None => ResponseTimeout::Enabled(Box::pin(sleep(dur))), + }, + _ => ResponseTimeout::Enabled(Box::pin(sleep(dur))), + }; + + Self { + payload: self.payload, + head: self.head, + timeout, + } + } + + /// This method does not enable timeout. It's used to pass the boxed `Sleep` from + /// `SendClientRequest` and reuse it's heap allocation together with it's slot in + /// timer wheel. + pub(crate) fn _timeout(mut self, timeout: Option>>) -> Self { + self.timeout = ResponseTimeout::Disabled(timeout); + self + } } impl ClientResponse @@ -137,7 +210,10 @@ where type Item = Result; fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - Pin::new(&mut self.get_mut().payload).poll_next(cx) + let this = self.get_mut(); + this.timeout.poll_timeout(cx)?; + + Pin::new(&mut this.payload).poll_next(cx) } } @@ -156,6 +232,7 @@ impl fmt::Debug for ClientResponse { pub struct MessageBody { length: Option, err: Option, + timeout: ResponseTimeout, fut: Option>, } @@ -181,6 +258,7 @@ where MessageBody { length: len, err: None, + timeout: std::mem::take(&mut res.timeout), fut: Some(ReadBody::new(res.take_payload(), 262_144)), } } @@ -198,6 +276,7 @@ where fut: None, err: Some(e), length: None, + timeout: ResponseTimeout::default(), } } } @@ -221,6 +300,8 @@ where } } + this.timeout.poll_timeout(cx)?; + Pin::new(&mut this.fut.as_mut().unwrap()).poll(cx) } } @@ -234,6 +315,7 @@ where pub struct JsonBody { length: Option, err: Option, + timeout: ResponseTimeout, fut: Option>, _phantom: PhantomData, } @@ -244,9 +326,9 @@ where U: DeserializeOwned, { /// Create `JsonBody` for request. - pub fn new(req: &mut ClientResponse) -> Self { + pub fn new(res: &mut ClientResponse) -> Self { // check content-type - let json = if let Ok(Some(mime)) = req.mime_type() { + let json = if let Ok(Some(mime)) = res.mime_type() { mime.subtype() == mime::JSON || mime.suffix() == Some(mime::JSON) } else { false @@ -255,13 +337,15 @@ where return JsonBody { length: None, fut: None, + timeout: ResponseTimeout::default(), err: Some(JsonPayloadError::ContentType), _phantom: PhantomData, }; } let mut len = None; - if let Some(l) = req.headers().get(&header::CONTENT_LENGTH) { + + if let Some(l) = res.headers().get(&header::CONTENT_LENGTH) { if let Ok(s) = l.to_str() { if let Ok(l) = s.parse::() { len = Some(l) @@ -272,7 +356,8 @@ where JsonBody { length: len, err: None, - fut: Some(ReadBody::new(req.take_payload(), 65536)), + timeout: std::mem::take(&mut res.timeout), + fut: Some(ReadBody::new(res.take_payload(), 65536)), _phantom: PhantomData, } } @@ -311,6 +396,10 @@ where } } + self.timeout + .poll_timeout(cx) + .map_err(JsonPayloadError::Payload)?; + let body = ready!(Pin::new(&mut self.get_mut().fut.as_mut().unwrap()).poll(cx))?; Poll::Ready(serde_json::from_slice::(&body).map_err(JsonPayloadError::from)) } diff --git a/awc/src/sender.rs b/awc/src/sender.rs index a72b129f8..6bac401c5 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -18,15 +18,11 @@ use actix_http::{ use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; -use futures_core::Stream; +use futures_core::{ready, Stream}; use serde::Serialize; #[cfg(feature = "compress")] -use actix_http::encoding::Decoder; -#[cfg(feature = "compress")] -use actix_http::http::header::ContentEncoding; -#[cfg(feature = "compress")] -use actix_http::{Payload, PayloadStream}; +use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; use crate::response::ClientResponse; @@ -61,7 +57,6 @@ impl From for SendRequestError { pub enum SendClientRequest { Fut( Pin>>>, - // FIXME: use a pinned Sleep instead of box. Option>>, bool, ), @@ -88,15 +83,14 @@ impl Future for SendClientRequest { match this { SendClientRequest::Fut(send, delay, response_decompress) => { - if delay.is_some() { - match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {} - _ => return Poll::Ready(Err(SendRequestError::Timeout)), + if let Some(delay) = delay { + if delay.as_mut().poll(cx).is_ready() { + return Poll::Ready(Err(SendRequestError::Timeout)); } } - let res = futures_core::ready!(Pin::new(send).poll(cx)).map(|res| { - res.map_body(|head, payload| { + let res = ready!(send.as_mut().poll(cx)).map(|res| { + res._timeout(delay.take()).map_body(|head, payload| { if *response_decompress { Payload::Stream(Decoder::from_headers(payload, &head.headers)) } else { @@ -123,13 +117,15 @@ impl Future for SendClientRequest { let this = self.get_mut(); match this { SendClientRequest::Fut(send, delay, _) => { - if delay.is_some() { - match Pin::new(delay.as_mut().unwrap()).poll(cx) { - Poll::Pending => {} - _ => return Poll::Ready(Err(SendRequestError::Timeout)), + if let Some(delay) = delay { + if delay.as_mut().poll(cx).is_ready() { + return Poll::Ready(Err(SendRequestError::Timeout)); } } - Pin::new(send).poll(cx) + + send.as_mut() + .poll(cx) + .map_ok(|res| res._timeout(delay.take())) } SendClientRequest::Err(ref mut e) => match e.take() { Some(e) => Poll::Ready(Err(e)), diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index 7e74d226e..bcbaf3f41 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -24,7 +24,7 @@ use actix_web::{ middleware::Compress, test, web, App, Error, HttpMessage, HttpRequest, HttpResponse, }; -use awc::error::SendRequestError; +use awc::error::{JsonPayloadError, PayloadError, SendRequestError}; const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ Hello World Hello World Hello World Hello World Hello World \ @@ -157,6 +157,79 @@ async fn test_timeout_override() { } } +#[actix_rt::test] +async fn test_response_timeout() { + use futures_util::stream::{once, StreamExt}; + + let srv = test::start(|| { + App::new().service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>( + HttpResponse::Ok() + .content_type("application/json") + .streaming(Box::pin(once(async { + actix_rt::time::sleep(Duration::from_millis(200)).await; + Ok::<_, Error>(Bytes::from(STR)) + }))), + ) + }))) + }); + + let client = awc::Client::new(); + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(500)) + .body() + .await + .unwrap(); + assert_eq!(std::str::from_utf8(res.as_ref()).unwrap(), STR); + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(100)) + .next() + .await + .unwrap(); + match res { + Err(PayloadError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::TimedOut), + _ => panic!("Response error type is not matched"), + } + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(100)) + .body() + .await; + match res { + Err(PayloadError::Io(e)) => assert_eq!(e.kind(), std::io::ErrorKind::TimedOut), + _ => panic!("Response error type is not matched"), + } + + let res = client + .get(srv.url("/")) + .send() + .await + .unwrap() + .timeout(Duration::from_millis(100)) + .json::>() + .await; + match res { + Err(JsonPayloadError::Payload(PayloadError::Io(e))) => { + assert_eq!(e.kind(), std::io::ErrorKind::TimedOut) + } + _ => panic!("Response error type is not matched"), + } +} + #[actix_rt::test] async fn test_connection_reuse() { let num = Arc::new(AtomicUsize::new(0)); From dfd9dc40ea27da155a4464c8cc18db256ae25f64 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Wed, 17 Feb 2021 09:10:46 -0800 Subject: [PATCH 182/187] remove awc::connect::connect trait. (#2004) --- awc/src/builder.rs | 8 +-- awc/src/connect.rs | 132 +++++++++++++++++++++++++++------------------ awc/src/lib.rs | 8 +-- awc/src/sender.rs | 42 ++++++++------- awc/src/ws.rs | 14 +++-- 5 files changed, 120 insertions(+), 84 deletions(-) diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 3d1613c66..b5e8bf5bf 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -7,7 +7,7 @@ use actix_http::client::{Connect as HttpConnect, ConnectError, Connection, Conne use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName}; use actix_service::Service; -use crate::connect::{Connect, ConnectorWrapper}; +use crate::connect::{ConnectService, ConnectorWrapper}; use crate::{Client, ClientConfig}; /// An HTTP Client builder @@ -23,7 +23,7 @@ pub struct ClientBuilder { conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option>, + connector: Option, } impl Default for ClientBuilder { @@ -54,7 +54,7 @@ impl ClientBuilder { T::Response: Connection, T::Future: 'static, { - self.connector = Some(Box::new(ConnectorWrapper(connector))); + self.connector = Some(Box::new(ConnectorWrapper::new(connector))); self } @@ -181,7 +181,7 @@ impl ClientBuilder { if let Some(val) = self.stream_window_size { connector = connector.initial_window_size(val) }; - Box::new(ConnectorWrapper(connector.finish())) as _ + Box::new(ConnectorWrapper::new(connector.finish())) as _ }; let config = ClientConfig { headers: self.headers, diff --git a/awc/src/connect.rs b/awc/src/connect.rs index 9a2ded195..97af2d1cc 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -16,74 +16,100 @@ use futures_core::future::LocalBoxFuture; use crate::response::ClientResponse; -pub(crate) struct ConnectorWrapper(pub T); - -type TunnelResponse = (ResponseHead, Framed); - -pub(crate) trait Connect { - fn send_request( - &self, - head: RequestHeadType, - body: Body, - addr: Option, - ) -> LocalBoxFuture<'static, Result>; - - /// Send request, returns Response and Framed - fn open_tunnel( - &self, - head: RequestHead, - addr: Option, - ) -> LocalBoxFuture<'static, Result>; +pub(crate) struct ConnectorWrapper { + connector: T, } -impl Connect for ConnectorWrapper +impl ConnectorWrapper { + pub(crate) fn new(connector: T) -> Self { + Self { connector } + } +} + +pub type ConnectService = Box< + dyn Service< + ConnectRequest, + Response = ConnectResponse, + Error = SendRequestError, + Future = LocalBoxFuture<'static, Result>, + >, +>; + +pub enum ConnectRequest { + Client(RequestHeadType, Body, Option), + Tunnel(RequestHead, Option), +} + +pub enum ConnectResponse { + Client(ClientResponse), + Tunnel(ResponseHead, Framed), +} + +impl ConnectResponse { + pub fn into_client_response(self) -> ClientResponse { + match self { + ConnectResponse::Client(res) => res, + _ => panic!( + "ClientResponse only reachable with ConnectResponse::ClientResponse variant" + ), + } + } + + pub fn into_tunnel_response(self) -> (ResponseHead, Framed) { + match self { + ConnectResponse::Tunnel(head, framed) => (head, framed), + _ => panic!( + "TunnelResponse only reachable with ConnectResponse::TunnelResponse variant" + ), + } + } +} + +impl Service for ConnectorWrapper where T: Service, T::Response: Connection, ::Io: 'static, T::Future: 'static, { - fn send_request( - &self, - head: RequestHeadType, - body: Body, - addr: Option, - ) -> LocalBoxFuture<'static, Result> { + type Response = ConnectResponse; + type Error = SendRequestError; + type Future = LocalBoxFuture<'static, Result>; + + actix_service::forward_ready!(connector); + + fn call(&self, req: ConnectRequest) -> Self::Future { // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.as_ref().uri.clone(), - addr, - }); + let fut = match req { + ConnectRequest::Client(ref head, .., addr) => self.connector.call(ClientConnect { + uri: head.as_ref().uri.clone(), + addr, + }), + ConnectRequest::Tunnel(ref head, addr) => self.connector.call(ClientConnect { + uri: head.uri.clone(), + addr, + }), + }; Box::pin(async move { let connection = fut.await?; - // send request - let (head, payload) = connection.send_request(head, body).await?; + match req { + ConnectRequest::Client(head, body, ..) => { + // send request + let (head, payload) = connection.send_request(head, body).await?; - Ok(ClientResponse::new(head, payload)) - }) - } + Ok(ConnectResponse::Client(ClientResponse::new(head, payload))) + } + ConnectRequest::Tunnel(head, ..) => { + // send request + let (head, framed) = + connection.open_tunnel(RequestHeadType::from(head)).await?; - fn open_tunnel( - &self, - head: RequestHead, - addr: Option, - ) -> LocalBoxFuture<'static, Result> { - // connect to the host - let fut = self.0.call(ClientConnect { - uri: head.uri.clone(), - addr, - }); - - Box::pin(async move { - let connection = fut.await?; - - // send request - let (head, framed) = connection.open_tunnel(RequestHeadType::from(head)).await?; - - let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); - Ok((head, framed)) + let framed = framed.into_map_io(|io| BoxedSocket(Box::new(Socket(io)))); + Ok(ConnectResponse::Tunnel(head, framed)) + } + } }) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index e50c19c8c..1e27ed9ab 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -115,13 +115,13 @@ pub mod test; pub mod ws; pub use self::builder::ClientBuilder; -pub use self::connect::BoxedSocket; +pub use self::connect::{BoxedSocket, ConnectRequest, ConnectResponse, ConnectService}; pub use self::frozen::{FrozenClientRequest, FrozenSendBuilder}; pub use self::request::ClientRequest; pub use self::response::{ClientResponse, JsonBody, MessageBody}; pub use self::sender::SendClientRequest; -use self::connect::{Connect, ConnectorWrapper}; +use self::connect::ConnectorWrapper; /// An asynchronous HTTP and WebSocket client. /// @@ -146,7 +146,7 @@ use self::connect::{Connect, ConnectorWrapper}; pub struct Client(Rc); pub(crate) struct ClientConfig { - pub(crate) connector: Box, + pub(crate) connector: ConnectService, pub(crate) headers: HeaderMap, pub(crate) timeout: Option, } @@ -154,7 +154,7 @@ pub(crate) struct ClientConfig { impl Default for Client { fn default() -> Self { Client(Rc::new(ClientConfig { - connector: Box::new(ConnectorWrapper(Connector::new().finish())), + connector: Box::new(ConnectorWrapper::new(Connector::new().finish())), headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), })) diff --git a/awc/src/sender.rs b/awc/src/sender.rs index 6bac401c5..1170c69a0 100644 --- a/awc/src/sender.rs +++ b/awc/src/sender.rs @@ -18,12 +18,13 @@ use actix_http::{ use actix_rt::time::{sleep, Sleep}; use bytes::Bytes; use derive_more::From; -use futures_core::{ready, Stream}; +use futures_core::Stream; use serde::Serialize; #[cfg(feature = "compress")] use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream}; +use crate::connect::{ConnectRequest, ConnectResponse}; use crate::error::{FreezeRequestError, InvalidUrl, SendRequestError}; use crate::response::ClientResponse; use crate::ClientConfig; @@ -56,7 +57,8 @@ impl From for SendRequestError { #[must_use = "futures do nothing unless polled"] pub enum SendClientRequest { Fut( - Pin>>>, + Pin>>>, + // FIXME: use a pinned Sleep instead of box. Option>>, bool, ), @@ -65,7 +67,7 @@ pub enum SendClientRequest { impl SendClientRequest { pub(crate) fn new( - send: Pin>>>, + send: Pin>>>, response_decompress: bool, timeout: Option, ) -> SendClientRequest { @@ -89,14 +91,19 @@ impl Future for SendClientRequest { } } - let res = ready!(send.as_mut().poll(cx)).map(|res| { - res._timeout(delay.take()).map_body(|head, payload| { - if *response_decompress { - Payload::Stream(Decoder::from_headers(payload, &head.headers)) - } else { - Payload::Stream(Decoder::new(payload, ContentEncoding::Identity)) - } - }) + let res = futures_core::ready!(send.as_mut().poll(cx)).map(|res| { + res.into_client_response()._timeout(delay.take()).map_body( + |head, payload| { + if *response_decompress { + Payload::Stream(Decoder::from_headers(payload, &head.headers)) + } else { + Payload::Stream(Decoder::new( + payload, + ContentEncoding::Identity, + )) + } + }, + ) }); Poll::Ready(res) @@ -122,10 +129,9 @@ impl Future for SendClientRequest { return Poll::Ready(Err(SendRequestError::Timeout)); } } - send.as_mut() .poll(cx) - .map_ok(|res| res._timeout(delay.take())) + .map_ok(|res| res.into_client_response()._timeout(delay.take())) } SendClientRequest::Err(ref mut e) => match e.take() { Some(e) => Poll::Ready(Err(e)), @@ -177,19 +183,19 @@ impl RequestSender { where B: Into, { - let fut = match self { + let req = match self { RequestSender::Owned(head) => { - config - .connector - .send_request(RequestHeadType::Owned(head), body.into(), addr) + ConnectRequest::Client(RequestHeadType::Owned(head), body.into(), addr) } - RequestSender::Rc(head, extra_headers) => config.connector.send_request( + RequestSender::Rc(head, extra_headers) => ConnectRequest::Client( RequestHeadType::Rc(head, extra_headers), body.into(), addr, ), }; + let fut = config.connector.call(req); + SendClientRequest::new(fut, response_decompress, timeout.or(config.timeout)) } diff --git a/awc/src/ws.rs b/awc/src/ws.rs index d5528595d..5f4570963 100644 --- a/awc/src/ws.rs +++ b/awc/src/ws.rs @@ -36,10 +36,11 @@ use actix_codec::Framed; use actix_http::cookie::{Cookie, CookieJar}; use actix_http::{ws, Payload, RequestHead}; use actix_rt::time::timeout; +use actix_service::Service; pub use actix_http::ws::{CloseCode, CloseReason, Codec, Frame, Message}; -use crate::connect::BoxedSocket; +use crate::connect::{BoxedSocket, ConnectRequest}; use crate::error::{InvalidUrl, SendRequestError, WsClientError}; use crate::http::header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION}; use crate::http::{ConnectionType, Error as HttpError, Method, StatusCode, Uri, Version}; @@ -327,18 +328,21 @@ impl WebsocketsRequest { let max_size = self.max_size; let server_mode = self.server_mode; - let fut = self.config.connector.open_tunnel(head, self.addr); + let req = ConnectRequest::Tunnel(head, self.addr); + + let fut = self.config.connector.call(req); // set request timeout - let (head, framed) = if let Some(to) = self.config.timeout { + let res = if let Some(to) = self.config.timeout { timeout(to, fut) .await - .map_err(|_| SendRequestError::Timeout) - .and_then(|res| res)? + .map_err(|_| SendRequestError::Timeout)?? } else { fut.await? }; + let (head, framed) = res.into_tunnel_response(); + // verify response if head.status != StatusCode::SWITCHING_PROTOCOLS { return Err(WsClientError::InvalidResponseStatus(head.status)); From f62a982a511151640c052bef21656c2596b5f886 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Feb 2021 02:38:27 -0800 Subject: [PATCH 183/187] simplify the match on h1 message type (#2006) --- actix-http/src/h1/dispatcher.rs | 42 +++++++++++++++++++++++---------- 1 file changed, 30 insertions(+), 12 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 839f75402..6df579c0a 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -510,25 +510,43 @@ where match msg { Message::Item(mut req) => { - let pl = this.codec.message_type(); req.head_mut().peer_addr = *this.peer_addr; // merge on_connect_ext data into request extensions this.on_connect_data.merge_into(&mut req); - if pl == MessageType::Stream && this.flow.upgrade.is_some() { - this.messages.push_back(DispatcherMessage::Upgrade(req)); - break; - } - if pl == MessageType::Payload || pl == MessageType::Stream { - let (ps, pl) = Payload::create(false); - let (req1, _) = - req.replace_payload(crate::Payload::H1(pl)); - req = req1; - *this.payload = Some(ps); + match this.codec.message_type() { + // Request is upgradable. add upgrade message and break. + // everything remain in read buffer would be handed to + // upgraded Request. + MessageType::Stream if this.flow.upgrade.is_some() => { + this.messages + .push_back(DispatcherMessage::Upgrade(req)); + break; + } + + // Request is not upgradable. + MessageType::Payload | MessageType::Stream => { + /* + PayloadSender and Payload are smart pointers share the + same state. + PayloadSender is attached to dispatcher and used to sink + new chunked request data to state. + Payload is attached to Request and passed to Service::call + where the state can be collected and consumed. + */ + let (ps, pl) = Payload::create(false); + let (req1, _) = + req.replace_payload(crate::Payload::H1(pl)); + req = req1; + *this.payload = Some(ps); + } + + // Request has no payload. + MessageType::None => {} } - // handle request early + // handle request early when no future in InnerDispatcher state. if this.state.is_empty() { self.as_mut().handle_request(req, cx)?; this = self.as_mut().project(); From 1838d9cd0f8a3e7da0aebdb022cee3907ba43d67 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Feb 2021 03:24:10 -0800 Subject: [PATCH 184/187] remove unused method. reduce leaf future type (#2009) --- actix-http/src/client/connection.rs | 33 ++------ actix-http/src/client/connector.rs | 127 ++++++++++------------------ 2 files changed, 54 insertions(+), 106 deletions(-) diff --git a/actix-http/src/client/connection.rs b/actix-http/src/client/connection.rs index 707d5551b..778083a1c 100644 --- a/actix-http/src/client/connection.rs +++ b/actix-http/src/client/connection.rs @@ -16,7 +16,7 @@ use crate::message::{RequestHeadType, ResponseHead}; use crate::payload::Payload; use super::error::SendRequestError; -use super::pool::{Acquired, Protocol}; +use super::pool::Acquired; use super::{h1proto, h2proto}; pub(crate) enum ConnectionType { @@ -49,7 +49,7 @@ impl H2Connection { } } -// wake up waker when drop +// cancel spawned connection task on drop. impl Drop for H2Connection { fn drop(&mut self) { self.handle.abort(); @@ -74,8 +74,6 @@ impl DerefMut for H2Connection { pub trait Connection { type Io: AsyncRead + AsyncWrite + Unpin; - fn protocol(&self) -> Protocol; - /// Send request and body fn send_request>( self, @@ -154,14 +152,6 @@ where { type Io = T; - fn protocol(&self) -> Protocol { - match self.io { - Some(ConnectionType::H1(_)) => Protocol::Http1, - Some(ConnectionType::H2(_)) => Protocol::Http2, - None => Protocol::Http1, - } - } - fn send_request>( mut self, head: H, @@ -210,7 +200,7 @@ where } #[allow(dead_code)] -pub(crate) enum EitherConnection +pub(crate) enum EitherIoConnection where A: AsyncRead + AsyncWrite + Unpin + 'static, B: AsyncRead + AsyncWrite + Unpin + 'static, @@ -219,28 +209,21 @@ where B(IoConnection), } -impl Connection for EitherConnection +impl Connection for EitherIoConnection where A: AsyncRead + AsyncWrite + Unpin + 'static, B: AsyncRead + AsyncWrite + Unpin + 'static, { type Io = EitherIo; - fn protocol(&self) -> Protocol { - match self { - EitherConnection::A(con) => con.protocol(), - EitherConnection::B(con) => con.protocol(), - } - } - fn send_request>( self, head: H, body: RB, ) -> LocalBoxFuture<'static, Result<(ResponseHead, Payload), SendRequestError>> { match self { - EitherConnection::A(con) => con.send_request(head, body), - EitherConnection::B(con) => con.send_request(head, body), + EitherIoConnection::A(con) => con.send_request(head, body), + EitherIoConnection::B(con) => con.send_request(head, body), } } @@ -253,11 +236,11 @@ where Result<(ResponseHead, Framed), SendRequestError>, > { match self { - EitherConnection::A(con) => Box::pin(async { + EitherIoConnection::A(con) => Box::pin(async { let (head, framed) = con.open_tunnel(head).await?; Ok((head, framed.into_map_io(EitherIo::A))) }), - EitherConnection::B(con) => Box::pin(async { + EitherIoConnection::B(con) => Box::pin(async { let (head, framed) = con.open_tunnel(head).await?; Ok((head, framed.into_map_io(EitherIo::B))) }), diff --git a/actix-http/src/client/connector.rs b/actix-http/src/client/connector.rs index 3bf424d49..65536f257 100644 --- a/actix-http/src/client/connector.rs +++ b/actix-http/src/client/connector.rs @@ -356,7 +356,7 @@ where mod connect_impl { use std::task::{Context, Poll}; - use futures_util::future::{err, Either, Ready}; + use futures_core::future::LocalBoxFuture; use super::*; use crate::client::connection::IoConnection; @@ -388,10 +388,7 @@ mod connect_impl { { type Response = IoConnection; type Error = ConnectError; - type Future = Either< - as Service>::Future, - Ready, ConnectError>>, - >; + type Future = LocalBoxFuture<'static, Result, ConnectError>>; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) @@ -400,9 +397,9 @@ mod connect_impl { fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { Some("https") | Some("wss") => { - Either::Right(err(ConnectError::SslIsNotSupported)) + Box::pin(async { Err(ConnectError::SslIsNotSupported) }) } - _ => Either::Left(self.tcp_pool.call(req)), + _ => self.tcp_pool.call(req), } } } @@ -411,33 +408,29 @@ mod connect_impl { #[cfg(any(feature = "openssl", feature = "rustls"))] mod connect_impl { use std::future::Future; - use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; - use futures_core::ready; - use futures_util::future::Either; - use super::*; - use crate::client::connection::EitherConnection; + use crate::client::connection::EitherIoConnection; - pub(crate) struct InnerConnector + pub(crate) struct InnerConnector where + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service, - T2: Service, { - pub(crate) tcp_pool: ConnectionPool, - pub(crate) ssl_pool: ConnectionPool, + pub(crate) tcp_pool: ConnectionPool, + pub(crate) ssl_pool: ConnectionPool, } - impl Clone for InnerConnector + impl Clone for InnerConnector where + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service + 'static, - T2: Service + 'static, { fn clone(&self) -> Self { InnerConnector { @@ -447,19 +440,16 @@ mod connect_impl { } } - impl Service for InnerConnector + impl Service for InnerConnector where + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T1: Service + 'static, - T2: Service + 'static, { - type Response = EitherConnection; + type Response = EitherIoConnection; type Error = ConnectError; - type Future = Either< - InnerConnectorResponseA, - InnerConnectorResponseB, - >; + type Future = InnerConnectorResponse; fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self.tcp_pool.poll_ready(cx) @@ -467,69 +457,44 @@ mod connect_impl { fn call(&self, req: Connect) -> Self::Future { match req.uri.scheme_str() { - Some("https") | Some("wss") => Either::Right(InnerConnectorResponseB { - fut: self.ssl_pool.call(req), - _phantom: PhantomData, - }), - _ => Either::Left(InnerConnectorResponseA { - fut: self.tcp_pool.call(req), - _phantom: PhantomData, - }), + Some("https") | Some("wss") => { + InnerConnectorResponse::Io2(self.ssl_pool.call(req)) + } + _ => InnerConnectorResponse::Io1(self.tcp_pool.call(req)), } } } - #[pin_project::pin_project] - pub(crate) struct InnerConnectorResponseA + #[pin_project::pin_project(project = InnerConnectorProj)] + pub(crate) enum InnerConnectorResponse where - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - #[pin] - fut: as Service>::Future, - _phantom: PhantomData, - } - - impl Future for InnerConnectorResponseA - where - T: Service + 'static, + S1: Service + 'static, + S2: Service + 'static, Io1: AsyncRead + AsyncWrite + Unpin + 'static, Io2: AsyncRead + AsyncWrite + Unpin + 'static, { - type Output = Result, ConnectError>; + Io1(#[pin] as Service>::Future), + Io2(#[pin] as Service>::Future), + } + + impl Future for InnerConnectorResponse + where + S1: Service + 'static, + S2: Service + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + 'static, + Io2: AsyncRead + AsyncWrite + Unpin + 'static, + { + type Output = Result, ConnectError>; fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready( - ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) - .map(EitherConnection::A), - ) - } - } - - #[pin_project::pin_project] - pub(crate) struct InnerConnectorResponseB - where - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - T: Service + 'static, - { - #[pin] - fut: as Service>::Future, - _phantom: PhantomData, - } - - impl Future for InnerConnectorResponseB - where - T: Service + 'static, - Io1: AsyncRead + AsyncWrite + Unpin + 'static, - Io2: AsyncRead + AsyncWrite + Unpin + 'static, - { - type Output = Result, ConnectError>; - - fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { - Poll::Ready( - ready!(Pin::new(&mut self.get_mut().fut).poll(cx)) - .map(EitherConnection::B), - ) + match self.project() { + InnerConnectorProj::Io1(fut) => { + fut.poll(cx).map_ok(EitherIoConnection::A) + } + InnerConnectorProj::Io2(fut) => { + fut.poll(cx).map_ok(EitherIoConnection::B) + } + } } } } From 946cccaa1a2ace8d997723399da4d5e0b43457e3 Mon Sep 17 00:00:00 2001 From: fakeshadow <24548779@qq.com> Date: Thu, 18 Feb 2021 04:30:09 -0800 Subject: [PATCH 185/187] refactor awc::ClientBuilder (#2008) --- actix-http-test/src/lib.rs | 2 - actix-http/src/client/mod.rs | 4 ++ awc/CHANGES.md | 7 +- awc/src/builder.rs | 115 ++++++++++++++++---------------- awc/src/lib.rs | 18 ++++- awc/tests/test_client.rs | 7 +- awc/tests/test_connector.rs | 2 +- awc/tests/test_rustls_client.rs | 2 +- awc/tests/test_ssl_client.rs | 2 +- src/test.rs | 2 - tests/test_httpserver.rs | 3 +- 11 files changed, 91 insertions(+), 73 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index df5774998..8de07c8d3 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -96,14 +96,12 @@ pub async fn test_server_with_addr>( .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) .ssl(builder.build()) - .finish() } #[cfg(not(feature = "openssl"))] { Connector::new() .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) - .finish() } }; diff --git a/actix-http/src/client/mod.rs b/actix-http/src/client/mod.rs index 9c7f632ea..5f5e57edb 100644 --- a/actix-http/src/client/mod.rs +++ b/actix-http/src/client/mod.rs @@ -10,6 +10,10 @@ mod h1proto; mod h2proto; mod pool; +pub use actix_tls::connect::{ + Connect as TcpConnect, ConnectError as TcpConnectError, Connection as TcpConnection, +}; + pub use self::connection::Connection; pub use self::connector::Connector; pub use self::error::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; diff --git a/awc/CHANGES.md b/awc/CHANGES.md index c67f65560..e6ead2cc8 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -6,10 +6,15 @@ ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] +* `ClientBuilder::connector` method would take `actix_http::client::Connector` type. [#2008] + +### Removed +* `ClientBuilder::default` function [#2008] +* `ClientBuilder::disable_redirects` and `ClientBuilder::max_redirects` method [#2008] [#1931]: https://github.com/actix/actix-web/pull/1931 [#1981]: https://github.com/actix/actix-web/pull/1981 - +[#2008]: https://github.com/actix/actix-web/pull/2008 ## 3.0.0-beta.2 - 2021-02-10 ### Added diff --git a/awc/src/builder.rs b/awc/src/builder.rs index b5e8bf5bf..4495b39fd 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -3,59 +3,80 @@ use std::fmt; use std::rc::Rc; use std::time::Duration; -use actix_http::client::{Connect as HttpConnect, ConnectError, Connection, Connector}; -use actix_http::http::{self, header, Error as HttpError, HeaderMap, HeaderName}; +use actix_codec::{AsyncRead, AsyncWrite}; +use actix_http::{ + client::{Connector, TcpConnect, TcpConnectError, TcpConnection}, + http::{self, header, Error as HttpError, HeaderMap, HeaderName, Uri}, +}; +use actix_rt::net::TcpStream; use actix_service::Service; -use crate::connect::{ConnectService, ConnectorWrapper}; +use crate::connect::ConnectorWrapper; use crate::{Client, ClientConfig}; /// An HTTP Client builder /// /// This type can be used to construct an instance of `Client` through a /// builder-like pattern. -pub struct ClientBuilder { +pub struct ClientBuilder { default_headers: bool, - allow_redirects: bool, - max_redirects: usize, max_http_version: Option, stream_window_size: Option, conn_window_size: Option, headers: HeaderMap, timeout: Option, - connector: Option, -} - -impl Default for ClientBuilder { - fn default() -> Self { - Self::new() - } + connector: Connector, } impl ClientBuilder { - pub fn new() -> Self { + #[allow(clippy::new_ret_no_self)] + pub fn new() -> ClientBuilder< + impl Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone, + TcpStream, + > { ClientBuilder { default_headers: true, - allow_redirects: true, - max_redirects: 10, headers: HeaderMap::new(), timeout: Some(Duration::from_secs(5)), - connector: None, + connector: Connector::new(), max_http_version: None, stream_window_size: None, conn_window_size: None, } } +} +impl ClientBuilder +where + S: Service, Response = TcpConnection, Error = TcpConnectError> + + Clone + + 'static, + Io: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, +{ /// Use custom connector service. - pub fn connector(mut self, connector: T) -> Self + pub fn connector(self, connector: Connector) -> ClientBuilder where - T: Service + 'static, - T::Response: Connection, - T::Future: 'static, + S1: Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone + + 'static, + Io1: AsyncRead + AsyncWrite + Unpin + fmt::Debug + 'static, { - self.connector = Some(Box::new(ConnectorWrapper::new(connector))); - self + ClientBuilder { + default_headers: self.default_headers, + headers: self.headers, + timeout: self.timeout, + connector, + max_http_version: self.max_http_version, + stream_window_size: self.stream_window_size, + conn_window_size: self.conn_window_size, + } } /// Set request timeout @@ -73,14 +94,6 @@ impl ClientBuilder { self } - /// Do not follow redirects. - /// - /// Redirects are allowed by default. - pub fn disable_redirects(mut self) -> Self { - self.allow_redirects = false; - self - } - /// Maximum supported HTTP major version. /// /// Supported versions are HTTP/1.1 and HTTP/2. @@ -107,14 +120,6 @@ impl ClientBuilder { self } - /// Set max number of redirects. - /// - /// Max redirects is set to 10 by default. - pub fn max_redirects(mut self, num: usize) -> Self { - self.max_redirects = num; - self - } - /// Do not add default request headers. /// By default `Date` and `User-Agent` headers are set. pub fn no_default_headers(mut self) -> Self { @@ -144,9 +149,9 @@ impl ClientBuilder { } /// Set client wide HTTP basic authorization header - pub fn basic_auth(self, username: U, password: Option<&str>) -> Self + pub fn basic_auth(self, username: N, password: Option<&str>) -> Self where - U: fmt::Display, + N: fmt::Display, { let auth = match password { Some(password) => format!("{}:{}", username, password), @@ -168,26 +173,24 @@ impl ClientBuilder { /// Finish build process and create `Client` instance. pub fn finish(self) -> Client { - let connector = if let Some(connector) = self.connector { - connector - } else { - let mut connector = Connector::new(); - if let Some(val) = self.max_http_version { - connector = connector.max_http_version(val) - }; - if let Some(val) = self.conn_window_size { - connector = connector.initial_connection_window_size(val) - }; - if let Some(val) = self.stream_window_size { - connector = connector.initial_window_size(val) - }; - Box::new(ConnectorWrapper::new(connector.finish())) as _ + let mut connector = self.connector; + + if let Some(val) = self.max_http_version { + connector = connector.max_http_version(val); }; + if let Some(val) = self.conn_window_size { + connector = connector.initial_connection_window_size(val) + }; + if let Some(val) = self.stream_window_size { + connector = connector.initial_window_size(val) + }; + let config = ClientConfig { headers: self.headers, timeout: self.timeout, - connector, + connector: Box::new(ConnectorWrapper::new(connector.finish())) as _, }; + Client(Rc::new(config)) } } diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 1e27ed9ab..66ff55402 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -101,8 +101,13 @@ use std::time::Duration; pub use actix_http::cookie; pub use actix_http::{client::Connector, http}; -use actix_http::http::{Error as HttpError, HeaderMap, Method, Uri}; -use actix_http::RequestHead; +use actix_http::{ + client::{TcpConnect, TcpConnectError, TcpConnection}, + http::{Error as HttpError, HeaderMap, Method, Uri}, + RequestHead, +}; +use actix_rt::net::TcpStream; +use actix_service::Service; mod builder; mod connect; @@ -169,7 +174,14 @@ impl Client { /// Create `Client` builder. /// This function is equivalent of `ClientBuilder::new()`. - pub fn builder() -> ClientBuilder { + pub fn builder() -> ClientBuilder< + impl Service< + TcpConnect, + Response = TcpConnection, + Error = TcpConnectError, + > + Clone, + TcpStream, + > { ClientBuilder::new() } diff --git a/awc/tests/test_client.rs b/awc/tests/test_client.rs index bcbaf3f41..a41a8dac3 100644 --- a/awc/tests/test_client.rs +++ b/awc/tests/test_client.rs @@ -120,8 +120,7 @@ async fn test_timeout() { let connector = awc::Connector::new() .connector(actix_tls::connect::default_connector()) - .timeout(Duration::from_secs(15)) - .finish(); + .timeout(Duration::from_secs(15)); let client = awc::Client::builder() .connector(connector) @@ -368,7 +367,7 @@ async fn test_connection_wait_queue() { .await; let client = awc::Client::builder() - .connector(awc::Connector::new().limit(1).finish()) + .connector(awc::Connector::new().limit(1)) .finish(); // req 1 @@ -417,7 +416,7 @@ async fn test_connection_wait_queue_force_close() { .await; let client = awc::Client::builder() - .connector(awc::Connector::new().limit(1).finish()) + .connector(awc::Connector::new().limit(1)) .finish(); // req 1 diff --git a/awc/tests/test_connector.rs b/awc/tests/test_connector.rs index fd725506d..632f68b72 100644 --- a/awc/tests/test_connector.rs +++ b/awc/tests/test_connector.rs @@ -58,7 +58,7 @@ async fn test_connection_window_size() { .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); let client = awc::Client::builder() - .connector(awc::Connector::new().ssl(builder.build()).finish()) + .connector(awc::Connector::new().ssl(builder.build())) .initial_window_size(100) .initial_connection_window_size(100) .finish(); diff --git a/awc/tests/test_rustls_client.rs b/awc/tests/test_rustls_client.rs index a928715a8..464edfe89 100644 --- a/awc/tests/test_rustls_client.rs +++ b/awc/tests/test_rustls_client.rs @@ -82,7 +82,7 @@ async fn test_connection_reuse_h2() { .set_certificate_verifier(Arc::new(danger::NoCertificateVerification)); let client = awc::Client::builder() - .connector(awc::Connector::new().rustls(Arc::new(config)).finish()) + .connector(awc::Connector::new().rustls(Arc::new(config))) .finish(); // req 1 diff --git a/awc/tests/test_ssl_client.rs b/awc/tests/test_ssl_client.rs index 08aa125cd..3079aaf5e 100644 --- a/awc/tests/test_ssl_client.rs +++ b/awc/tests/test_ssl_client.rs @@ -72,7 +72,7 @@ async fn test_connection_reuse_h2() { .map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); let client = awc::Client::builder() - .connector(awc::Connector::new().ssl(builder.build()).finish()) + .connector(awc::Connector::new().ssl(builder.build())) .finish(); // req 1 diff --git a/src/test.rs b/src/test.rs index 2ec4252b1..dd2426fec 100644 --- a/src/test.rs +++ b/src/test.rs @@ -761,14 +761,12 @@ where .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) .ssl(builder.build()) - .finish() } #[cfg(not(feature = "openssl"))] { Connector::new() .conn_lifetime(time::Duration::from_secs(0)) .timeout(time::Duration::from_millis(30000)) - .finish() } }; diff --git a/tests/test_httpserver.rs b/tests/test_httpserver.rs index 3aa1d36b0..043159376 100644 --- a/tests/test_httpserver.rs +++ b/tests/test_httpserver.rs @@ -137,8 +137,7 @@ async fn test_start_ssl() { .connector( awc::Connector::new() .ssl(builder.build()) - .timeout(Duration::from_millis(100)) - .finish(), + .timeout(Duration::from_millis(100)), ) .finish(); From 3b93c62e2309865dd8d4b9f8a0b53785c8011569 Mon Sep 17 00:00:00 2001 From: Richard Janis Goldschmidt Date: Thu, 18 Feb 2021 16:20:20 +0100 Subject: [PATCH 186/187] Fix Json extractor to be 32kB by default (#2010) --- CHANGES.md | 3 +++ src/types/json.rs | 4 ++-- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 743f5066b..7cb03c30c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -3,8 +3,11 @@ ## Unreleased - 2021-xx-xx ### Changed * Feature `cookies` is now optional and enabled by default. [#1981] +* `JsonBody::new` returns a default limit of 32kB to be consistent with `JsonConfig` and the + default behaviour of the `web::Json` extractor. [#2010] [#1981]: https://github.com/actix/actix-web/pull/1981 +[#2010]: https://github.com/actix/actix-web/pull/2010 ## 4.0.0-beta.3 - 2021-02-10 diff --git a/src/types/json.rs b/src/types/json.rs index 28960402a..31ff680f4 100644 --- a/src/types/json.rs +++ b/src/types/json.rs @@ -345,7 +345,7 @@ where let payload = payload.take(); JsonBody::Body { - limit: 262_144, + limit: 32_768, length, payload, buf: BytesMut::with_capacity(8192), @@ -353,7 +353,7 @@ where } } - /// Set maximum accepted payload size. The default limit is 256kB. + /// Set maximum accepted payload size. The default limit is 32kB. pub fn limit(self, limit: usize) -> Self { match self { JsonBody::Body { From 83365058cead13682124c769e1198419911bca22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?D=C3=A1vid=20Szab=C3=B3?= Date: Thu, 18 Feb 2021 22:56:24 +0100 Subject: [PATCH 187/187] Fix HTTP client link (#2011) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index cc7c4cd52..bf68e7961 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ * Static assets * SSL support using OpenSSL or Rustls * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/)) -* Includes an async [HTTP client](https://actix.rs/actix-web/actix_web/client/index.html) +* Includes an async [HTTP client](https://docs.rs/actix-web/latest/actix_web/client/index.html) * Runs on stable Rust 1.46+ ## Documentation

{ self.head().method == Method::CONNECT } - /// Peer socket address + /// Peer socket address. /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. + /// Peer address is the directly connected peer's socket address. If a proxy is used in front of + /// the Actix Web server, then it would be address of this proxy. + /// + /// Will only return None when called in unit tests. #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 113ace221..16d4a03d7 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -32,13 +32,13 @@ pub struct Response { } impl Response { - /// Create http response builder with specific status. + /// Create HTTP response builder with specific status. #[inline] pub fn build(status: StatusCode) -> ResponseBuilder { ResponseBuilder::new(status) } - /// Create http response builder + /// Create HTTP response builder #[inline] pub fn build_from>(source: T) -> ResponseBuilder { source.into() @@ -97,7 +97,7 @@ impl Response { } #[inline] - /// Mutable reference to a http message part of the response + /// Mutable reference to a HTTP message part of the response pub fn head_mut(&mut self) -> &mut ResponseHead { &mut *self.head } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index f0121db97..fee26dcc3 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -432,7 +432,7 @@ where } } -/// `Service` implementation for http transport +/// `Service` implementation for HTTP transport pub struct HttpServiceHandler where S: Service, diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs index 73bbe6208..188516f83 100644 --- a/actix-http/tests/test_openssl.rs +++ b/actix-http/tests/test_openssl.rs @@ -7,7 +7,7 @@ use std::io; use actix_http::error::{ErrorBadRequest, PayloadError}; use actix_http::http::header::{self, HeaderName, HeaderValue}; use actix_http::http::{Method, StatusCode, Version}; -use actix_http::httpmessage::HttpMessage; +use actix_http::HttpMessage; use actix_http::{body, Error, HttpService, Request, Response}; use actix_http_test::test_server; use actix_service::{fn_service, ServiceFactoryExt}; diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs index b4ef74406..910fa81f2 100644 --- a/actix-http/tests/test_server.rs +++ b/actix-http/tests/test_server.rs @@ -10,7 +10,7 @@ use futures_util::future::{self, err, ok, ready, FutureExt}; use futures_util::stream::{once, StreamExt}; use regex::Regex; -use actix_http::httpmessage::HttpMessage; +use actix_http::HttpMessage; use actix_http::{ body, error, http, http::header, Error, HttpService, KeepAlive, Request, Response, }; diff --git a/actix-web-actors/src/context.rs b/actix-web-actors/src/context.rs index afe17cd21..c452025af 100644 --- a/actix-web-actors/src/context.rs +++ b/actix-web-actors/src/context.rs @@ -15,7 +15,7 @@ use bytes::Bytes; use futures_core::Stream; use tokio::sync::oneshot::Sender; -/// Execution context for http actors +/// Execution context for HTTP actors pub struct HttpContext where A: Actor>, diff --git a/awc/src/builder.rs b/awc/src/builder.rs index 39eb24c39..94ffb8a71 100644 --- a/awc/src/builder.rs +++ b/awc/src/builder.rs @@ -82,8 +82,9 @@ impl ClientBuilder { self } - /// Maximum supported http major version - /// Supported versions http/1.1, http/2 + /// Maximum supported HTTP major version. + /// + /// Supported versions are HTTP/1.1 and HTTP/2. pub fn max_http_version(mut self, val: http::Version) -> Self { self.max_http_version = Some(val); self diff --git a/awc/src/lib.rs b/awc/src/lib.rs index a92125b5a..bd52f7ab1 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -134,7 +134,7 @@ use self::connect::{Connect, ConnectorWrapper}; /// /// let res = client.get("http://www.rust-lang.org") // <- Create request builder /// .insert_header(("User-Agent", "Actix-web")) -/// .send() // <- Send http request +/// .send() // <- Send HTTP request /// .await; // <- send request and wait for response /// /// println!("Response: {:?}", res); diff --git a/awc/src/request.rs b/awc/src/request.rs index c87df9b3b..3ec43f3e5 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -42,10 +42,10 @@ cfg_if::cfg_if! { /// let response = awc::Client::new() /// .get("http://www.rust-lang.org") // <- Create request builder /// .insert_header(("User-Agent", "Actix-web")) -/// .send() // <- Send http request +/// .send() // <- Send HTTP request /// .await; /// -/// response.and_then(|response| { // <- server http response +/// response.and_then(|response| { // <- server HTTP response /// println!("Response: {:?}", response); /// Ok(()) /// }); @@ -219,7 +219,7 @@ impl ClientRequest { } /// Force close connection instead of returning it back to connections pool. - /// This setting affect only http/1 connections. + /// This setting affect only HTTP/1 connections. #[inline] pub fn force_close(mut self) -> Self { self.head.set_connection_type(ConnectionType::Close); diff --git a/awc/src/response.rs b/awc/src/response.rs index c3e7d71ce..f5161f9c1 100644 --- a/awc/src/response.rs +++ b/awc/src/response.rs @@ -109,7 +109,7 @@ impl ClientResponse where S: Stream>, { - /// Loads http response's body. + /// Loads HTTP response's body. pub fn body(&mut self) -> MessageBody { MessageBody::new(self) } @@ -151,7 +151,7 @@ impl fmt::Debug for ClientResponse { } } -/// Future that resolves to a complete http message body. +/// Future that resolves to a complete HTTP message body. pub struct MessageBody { length: Option, err: Option, diff --git a/src/app.rs b/src/app.rs index 40362e0d3..123076fc8 100644 --- a/src/app.rs +++ b/src/app.rs @@ -72,7 +72,7 @@ where /// Set application data. Application data could be accessed /// by using `Data` extractor where `T` is data type. /// - /// **Note**: http server accepts an application factory rather than + /// **Note**: HTTP server accepts an application factory rather than /// an application instance. Http server constructs an application /// instance for each thread, thus application data must be constructed /// multiple times. If you want to share data between different @@ -207,7 +207,7 @@ where ) } - /// Register http service. + /// Register HTTP service. /// /// Http service is any type that implements `HttpServiceFactory` trait. /// diff --git a/src/data.rs b/src/data.rs index 12a1f5cf8..b3f3d643c 100644 --- a/src/data.rs +++ b/src/data.rs @@ -27,7 +27,7 @@ pub(crate) type FnDataFactory = /// /// Application data can be accessed by using `Data` extractor where `T` is data type. /// -/// **Note**: http server accepts an application factory rather than an application instance. HTTP +/// **Note**: HTTP server accepts an application factory rather than an application instance. HTTP /// server constructs an application instance for each thread, thus application data must be /// constructed multiple times. If you want to share data between different threads, a shareable /// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send` diff --git a/src/guard.rs b/src/guard.rs index ba0cbea85..5d0de58c2 100644 --- a/src/guard.rs +++ b/src/guard.rs @@ -176,7 +176,7 @@ impl Guard for NotGuard { } } -/// Http method guard +/// HTTP method guard. #[doc(hidden)] pub struct MethodGuard(http::Method); @@ -186,52 +186,52 @@ impl Guard for MethodGuard { } } -/// Guard to match *GET* http method +/// Guard to match *GET* HTTP method. pub fn Get() -> MethodGuard { MethodGuard(http::Method::GET) } -/// Predicate to match *POST* http method +/// Predicate to match *POST* HTTP method. pub fn Post() -> MethodGuard { MethodGuard(http::Method::POST) } -/// Predicate to match *PUT* http method +/// Predicate to match *PUT* HTTP method. pub fn Put() -> MethodGuard { MethodGuard(http::Method::PUT) } -/// Predicate to match *DELETE* http method +/// Predicate to match *DELETE* HTTP method. pub fn Delete() -> MethodGuard { MethodGuard(http::Method::DELETE) } -/// Predicate to match *HEAD* http method +/// Predicate to match *HEAD* HTTP method. pub fn Head() -> MethodGuard { MethodGuard(http::Method::HEAD) } -/// Predicate to match *OPTIONS* http method +/// Predicate to match *OPTIONS* HTTP method. pub fn Options() -> MethodGuard { MethodGuard(http::Method::OPTIONS) } -/// Predicate to match *CONNECT* http method +/// Predicate to match *CONNECT* HTTP method. pub fn Connect() -> MethodGuard { MethodGuard(http::Method::CONNECT) } -/// Predicate to match *PATCH* http method +/// Predicate to match *PATCH* HTTP method. pub fn Patch() -> MethodGuard { MethodGuard(http::Method::PATCH) } -/// Predicate to match *TRACE* http method +/// Predicate to match *TRACE* HTTP method. pub fn Trace() -> MethodGuard { MethodGuard(http::Method::TRACE) } -/// Predicate to match specified http method +/// Predicate to match specified HTTP method. pub fn Method(method: http::Method) -> MethodGuard { MethodGuard(method) } diff --git a/src/request.rs b/src/request.rs index 9a5e43185..3f4eb1038 100644 --- a/src/request.rs +++ b/src/request.rs @@ -62,7 +62,7 @@ impl HttpRequest { } /// This method returns mutable reference to the request head. - /// panics if multiple references of http request exists. + /// panics if multiple references of HTTP request exists. #[inline] pub(crate) fn head_mut(&mut self) -> &mut RequestHead { &mut Rc::get_mut(&mut self.inner).unwrap().head @@ -202,12 +202,14 @@ impl HttpRequest { &self.app_state().rmap() } - /// Peer socket address + /// Peer socket address. /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. + /// Peer address is the directly connected peer's socket address. If a proxy is used in front of + /// the Actix Web server, then it would be address of this proxy. /// /// To get client connection information `.connection_info()` should be used. + /// + /// Will only return None when called in unit tests. #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr diff --git a/src/server.rs b/src/server.rs index 59d589439..69318e08d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -40,7 +40,7 @@ struct Config { /// An HTTP Server. /// -/// Create new http server with application factory. +/// Create new HTTP server with application factory. /// /// ```rust,no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; @@ -86,7 +86,7 @@ where S::Service: 'static, B: MessageBody + 'static, { - /// Create new http server with application factory + /// Create new HTTP server with application factory pub fn new(factory: F) -> Self { HttpServer { factory, @@ -131,8 +131,7 @@ where /// Set number of workers to start. /// - /// By default http server uses number of available logical cpu as threads - /// count. + /// By default, server uses number of available logical CPU as thread count. pub fn workers(mut self, num: usize) -> Self { self.builder = self.builder.workers(num); self @@ -257,7 +256,7 @@ where /// Get addresses of bound sockets and the scheme for it. /// /// This is useful when the server is bound from different sources - /// with some sockets listening on http and some listening on https + /// with some sockets listening on HTTP and some listening on HTTPS /// and the user should be presented with an enumeration of which /// socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { @@ -610,7 +609,7 @@ where { /// Start listening for incoming connections. /// - /// This method starts number of http workers in separate threads. + /// This method starts number of HTTP workers in separate threads. /// For each address this method starts separate thread which does /// `accept()` in a loop. /// diff --git a/src/service.rs b/src/service.rs index db0ec602a..a7fb12848 100644 --- a/src/service.rs +++ b/src/service.rs @@ -164,12 +164,14 @@ impl ServiceRequest { } } - /// Peer socket address + /// Peer socket address. /// - /// Peer address is actual socket address, if proxy is used in front of - /// actix http server, then peer address would be address of this proxy. + /// Peer address is the directly connected peer's socket address. If a proxy is used in front of + /// the Actix Web server, then it would be address of this proxy. /// /// To get client connection information `ConnectionInfo` should be used. + /// + /// Will only return None when called in unit tests. #[inline] pub fn peer_addr(&self) -> Option { self.head().peer_addr diff --git a/src/test.rs b/src/test.rs index 03113bd4a..c03f078ef 100644 --- a/src/test.rs +++ b/src/test.rs @@ -851,13 +851,13 @@ impl TestServerConfig { } } - /// Start http/1.1 server only + /// Start HTTP/1.1 server only pub fn h1(mut self) -> Self { self.tp = HttpVer::Http1; self } - /// Start http/2 server only + /// Start HTTP/2 server only pub fn h2(mut self) -> Self { self.tp = HttpVer::Http2; self @@ -956,7 +956,7 @@ impl TestServer { self.client.options(self.url(path.as_ref()).as_str()) } - /// Connect to test http server + /// Connect to test HTTP server pub fn request>(&self, method: Method, path: S) -> ClientRequest { self.client.request(method, path.as_ref()) } @@ -990,7 +990,7 @@ impl TestServer { self.ws_at("/").await } - /// Gracefully stop http server + /// Gracefully stop HTTP server pub async fn stop(self) { self.server.stop(true).await; self.system.stop(); @@ -1006,7 +1006,7 @@ impl Drop for TestServer { #[cfg(test)] mod tests { - use actix_http::httpmessage::HttpMessage; + use actix_http::HttpMessage; use serde::{Deserialize, Serialize}; use std::time::SystemTime; From c1af5089b92ade8df4b105e5e34ec18576a7417a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 22:58:35 +0000 Subject: [PATCH 160/187] add 431 and 451 status codes --- actix-http/src/h1/codec.rs | 2 +- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 36 +++++++++---------- actix-http/src/header/common/if_range.rs | 2 +- actix-http/src/header/mod.rs | 2 +- actix-http/src/http_codes.rs | 8 +++++ .../src/{httpmessage.rs => http_message.rs} | 0 actix-http/src/lib.rs | 6 ++-- actix-http/src/request.rs | 2 +- actix-http/src/response.rs | 3 +- 10 files changed, 35 insertions(+), 28 deletions(-) rename actix-http/src/{httpmessage.rs => http_message.rs} (100%) diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index d5035df26..4aeb9f120 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -199,7 +199,7 @@ mod tests { use http::Method; use super::*; - use crate::httpmessage::HttpMessage; + use crate::HttpMessage; #[test] fn test_http_request_chunked_payload_and_next_message() { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 122a815d5..074616929 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -652,7 +652,7 @@ mod tests { use super::*; use crate::error::ParseError; use crate::http::header::{HeaderName, SET_COOKIE}; - use crate::httpmessage::HttpMessage; + use crate::HttpMessage; impl PayloadType { fn unwrap(self) -> PayloadDecoder { diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 8d451fdad..7ee2ee258 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -382,8 +382,8 @@ where } // send service call error as response - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); + Poll::Ready(Err(err)) => { + let res: Response = err.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } @@ -421,8 +421,8 @@ where continue 'res; } - Poll::Ready(Some(Err(e))) => { - return Err(DispatchError::Service(e)) + Poll::Ready(Some(Err(err))) => { + return Err(DispatchError::Service(err)) } Poll::Pending => return Ok(PollResponse::DoNothing), @@ -443,8 +443,8 @@ where this.state.set(State::ServiceCall(fut)); } // send expect error as response - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); + Poll::Ready(Err(err)) => { + let res: Response = err.into().into(); let (res, body) = res.replace_body(()); self.as_mut().send_response(res, body.into_body())?; } @@ -492,9 +492,9 @@ where // future is error. send response and return a result. On success // to notify the dispatcher a new state is set and the outer loop // should be continue. - Poll::Ready(Err(e)) => { - let e = e.into(); - let res: Response = e.into(); + Poll::Ready(Err(err)) => { + let err = err.into(); + let res: Response = err.into(); let (res, body) = res.replace_body(()); return self.send_response(res, body.into_body()); } @@ -512,9 +512,9 @@ where } // see the comment on ExpectCall state branch's Pending. Poll::Pending => Ok(()), - // see the comment on ExpectCall state branch's Ready(Err(e)). - Poll::Ready(Err(e)) => { - let res: Response = e.into().into(); + // see the comment on ExpectCall state branch's Ready(Err(err)). + Poll::Ready(Err(err)) => { + let res: Response = err.into().into(); let (res, body) = res.replace_body(()); self.send_response(res, body.into_body()) } @@ -606,25 +606,25 @@ where // decode is partial and buffer is not full yet. // break and wait for more read. Ok(None) => break, - Err(ParseError::Io(e)) => { + Err(ParseError::Io(err)) => { self.as_mut().client_disconnected(); this = self.as_mut().project(); - *this.error = Some(DispatchError::Io(e)); + *this.error = Some(DispatchError::Io(err)); break; } Err(ParseError::TooLarge) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); } - // Requests overflow buffer size should be responded with 413 + // Requests overflow buffer size should be responded with 431 this.messages.push_back(DispatcherMessage::Error( - Response::PayloadTooLarge().finish().drop_body(), + Response::RequestHeaderFieldsTooLarge().finish().drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); *this.error = Some(ParseError::TooLarge.into()); break; } - Err(e) => { + Err(err) => { if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); } @@ -634,7 +634,7 @@ where Response::BadRequest().finish().drop_body(), )); this.flags.insert(Flags::READ_DISCONNECT); - *this.error = Some(e.into()); + *this.error = Some(err.into()); break; } } diff --git a/actix-http/src/header/common/if_range.rs b/actix-http/src/header/common/if_range.rs index 1513b7a44..0a5749505 100644 --- a/actix-http/src/header/common/if_range.rs +++ b/actix-http/src/header/common/if_range.rs @@ -5,7 +5,7 @@ use crate::header::{ self, from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue, InvalidHeaderValue, Writer, }; -use crate::httpmessage::HttpMessage; +use crate::HttpMessage; /// `If-Range` header, defined in [RFC7233](http://tools.ietf.org/html/rfc7233#section-3.2) /// diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs index 9543d43b6..1100a959d 100644 --- a/actix-http/src/header/mod.rs +++ b/actix-http/src/header/mod.rs @@ -9,7 +9,7 @@ use percent_encoding::{AsciiSet, CONTROLS}; pub use http::header::*; use crate::error::ParseError; -use crate::httpmessage::HttpMessage; +use crate::HttpMessage; mod as_name; mod into_pair; diff --git a/actix-http/src/http_codes.rs b/actix-http/src/http_codes.rs index f421d8e23..688a08be5 100644 --- a/actix-http/src/http_codes.rs +++ b/actix-http/src/http_codes.rs @@ -67,6 +67,14 @@ impl Response { static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED); static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY); static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS); + static_resp!( + RequestHeaderFieldsTooLarge, + StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE + ); + static_resp!( + UnavailableForLegalReasons, + StatusCode::UNAVAILABLE_FOR_LEGAL_REASONS + ); static_resp!(InternalServerError, StatusCode::INTERNAL_SERVER_ERROR); static_resp!(NotImplemented, StatusCode::NOT_IMPLEMENTED); diff --git a/actix-http/src/httpmessage.rs b/actix-http/src/http_message.rs similarity index 100% rename from actix-http/src/httpmessage.rs rename to actix-http/src/http_message.rs diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 15866a3d8..a55aaadbf 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -1,6 +1,6 @@ //! HTTP primitives for the Actix ecosystem. -#![deny(rust_2018_idioms)] +#![deny(rust_2018_idioms, nonstandard_style)] #![allow( clippy::type_complexity, clippy::too_many_arguments, @@ -26,7 +26,7 @@ mod extensions; mod header; mod helpers; mod http_codes; -mod httpmessage; +mod http_message; mod message; mod payload; mod request; @@ -45,7 +45,7 @@ pub use self::builder::HttpServiceBuilder; pub use self::config::{KeepAlive, ServiceConfig}; pub use self::error::{Error, ResponseError, Result}; pub use self::extensions::Extensions; -pub use self::httpmessage::HttpMessage; +pub use self::http_message::HttpMessage; pub use self::message::{Message, RequestHead, RequestHeadType, ResponseHead}; pub use self::payload::{Payload, PayloadStream}; pub use self::request::Request; diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 1ac2f33d6..18c6c8889 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -9,7 +9,7 @@ use http::{header, Method, Uri, Version}; use crate::extensions::Extensions; use crate::header::HeaderMap; -use crate::httpmessage::HttpMessage; +use crate::HttpMessage; use crate::message::{Message, RequestHead}; use crate::payload::{Payload, PayloadStream}; diff --git a/actix-http/src/response.rs b/actix-http/src/response.rs index 16d4a03d7..763243a63 100644 --- a/actix-http/src/response.rs +++ b/actix-http/src/response.rs @@ -867,6 +867,7 @@ mod tests { use super::*; use crate::body::Body; use crate::http::header::{HeaderValue, CONTENT_TYPE, COOKIE, SET_COOKIE}; + use crate::HttpMessage; #[test] fn test_debug() { @@ -880,8 +881,6 @@ mod tests { #[test] fn test_response_cookies() { - use crate::httpmessage::HttpMessage; - let req = crate::test::TestRequest::default() .append_header((COOKIE, "cookie1=value1")) .append_header((COOKIE, "cookie2=value2")) From 31d9ed81c5bdd7af96b2648851485426fd481c75 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 11 Feb 2021 23:03:17 +0000 Subject: [PATCH 161/187] change rustfmt line width to 96 --- actix-files/src/chunked.rs | 14 +- actix-files/src/directory.rs | 4 +- actix-files/src/files.rs | 12 +- actix-files/src/lib.rs | 23 ++- actix-files/src/named.rs | 15 +- actix-files/src/range.rs | 6 +- actix-files/src/service.rs | 7 +- actix-files/tests/encoding.rs | 7 +- actix-http-test/src/lib.rs | 9 +- actix-http/src/request.rs | 2 +- actix-multipart/src/server.rs | 53 ++----- actix-web-actors/src/context.rs | 18 +-- actix-web-actors/src/ws.rs | 38 ++--- actix-web-actors/tests/test_ws.rs | 10 +- actix-web-codegen/tests/test_macro.rs | 4 +- awc/src/connect.rs | 8 +- awc/src/error.rs | 4 +- awc/src/request.rs | 11 +- awc/src/response.rs | 15 +- awc/src/sender.rs | 39 ++--- awc/src/ws.rs | 8 +- awc/tests/test_client.rs | 8 +- awc/tests/test_rustls_client.rs | 3 +- awc/tests/test_ssl_client.rs | 3 +- benches/server.rs | 5 +- examples/basic.rs | 8 +- examples/uds.rs | 8 +- rustfmt.toml | 2 +- src/app.rs | 71 +++++---- src/app_service.rs | 19 ++- src/config.rs | 35 ++--- src/data.rs | 52 +++---- src/error.rs | 4 +- src/lib.rs | 8 +- src/middleware/compat.rs | 4 +- src/middleware/condition.rs | 12 +- src/middleware/default_headers.rs | 3 +- src/middleware/err_handlers.rs | 6 +- src/middleware/logger.rs | 36 ++--- src/request.rs | 30 ++-- src/resource.rs | 46 +++--- src/responder.rs | 12 +- src/route.rs | 14 +- src/scope.rs | 70 +++++---- src/server.rs | 200 +++++++++++--------------- src/service.rs | 29 ++-- src/test.rs | 78 ++++------ src/types/either.rs | 30 ++-- src/types/form.rs | 10 +- src/types/json.rs | 4 +- src/types/path.rs | 15 +- src/types/payload.rs | 13 +- src/types/query.rs | 3 +- src/types/readlines.rs | 5 +- tests/test_httpserver.rs | 3 +- tests/test_server.rs | 63 +++----- 56 files changed, 463 insertions(+), 756 deletions(-) diff --git a/actix-files/src/chunked.rs b/actix-files/src/chunked.rs index 2a62b1d26..f639848c9 100644 --- a/actix-files/src/chunked.rs +++ b/actix-files/src/chunked.rs @@ -49,10 +49,7 @@ impl fmt::Debug for ChunkedReadFile { impl Stream for ChunkedReadFile { type Item = Result; - fn poll_next( - mut self: Pin<&mut Self>, - cx: &mut Context<'_>, - ) -> Poll> { + fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let this = self.as_mut().get_mut(); match this.state { ChunkedReadFileState::File(ref mut file) => { @@ -68,16 +65,13 @@ impl Stream for ChunkedReadFile { .expect("ChunkedReadFile polled after completion"); let fut = spawn_blocking(move || { - let max_bytes = - cmp::min(size.saturating_sub(counter), 65_536) as usize; + let max_bytes = cmp::min(size.saturating_sub(counter), 65_536) as usize; let mut buf = Vec::with_capacity(max_bytes); file.seek(io::SeekFrom::Start(offset))?; - let n_bytes = file - .by_ref() - .take(max_bytes as u64) - .read_to_end(&mut buf)?; + let n_bytes = + file.by_ref().take(max_bytes as u64).read_to_end(&mut buf)?; if n_bytes == 0 { return Err(io::ErrorKind::UnexpectedEof.into()); diff --git a/actix-files/src/directory.rs b/actix-files/src/directory.rs index 1103dd6a7..80e0c98d0 100644 --- a/actix-files/src/directory.rs +++ b/actix-files/src/directory.rs @@ -66,9 +66,7 @@ pub(crate) fn directory_listing( if dir.is_visible(&entry) { let entry = entry.unwrap(); let p = match entry.path().strip_prefix(&dir.path) { - Ok(p) if cfg!(windows) => { - base.join(p).to_string_lossy().replace("\\", "/") - } + Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"), Ok(p) => base.join(p).to_string_lossy().into_owned(), Err(_) => continue, }; diff --git a/actix-files/src/files.rs b/actix-files/src/files.rs index 0cf0a91ba..6f8b28bbf 100644 --- a/actix-files/src/files.rs +++ b/actix-files/src/files.rs @@ -2,9 +2,7 @@ use std::{cell::RefCell, fmt, io, path::PathBuf, rc::Rc}; use actix_service::{boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt}; use actix_web::{ - dev::{ - AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse, - }, + dev::{AppService, HttpServiceFactory, ResourceDef, ServiceRequest, ServiceResponse}, error::Error, guard::Guard, http::header::DispositionType, @@ -13,8 +11,8 @@ use actix_web::{ use futures_util::future::{ok, FutureExt, LocalBoxFuture}; use crate::{ - directory_listing, named, Directory, DirectoryRenderer, FilesService, - HttpNewService, MimeOverride, + directory_listing, named, Directory, DirectoryRenderer, FilesService, HttpNewService, + MimeOverride, }; /// Static files handling service. @@ -129,8 +127,8 @@ impl Files { /// Set custom directory renderer pub fn files_listing_renderer(mut self, f: F) -> Self where - for<'r, 's> F: Fn(&'r Directory, &'s HttpRequest) -> Result - + 'static, + for<'r, 's> F: + Fn(&'r Directory, &'s HttpRequest) -> Result + 'static, { self.renderer = Rc::new(f); self diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index b2f6384a8..04dd9f07f 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -98,8 +98,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_without_if_none_match() { let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() .insert_header((header::IF_MODIFIED_SINCE, since)) @@ -123,8 +122,7 @@ mod tests { #[actix_rt::test] async fn test_if_modified_since_with_if_none_match() { let file = NamedFile::open("Cargo.toml").unwrap(); - let since = - header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); + let since = header::HttpDate::from(SystemTime::now().add(Duration::from_secs(60))); let req = TestRequest::default() .insert_header((header::IF_NONE_MATCH, "miss_etag")) @@ -212,8 +210,7 @@ mod tests { #[actix_rt::test] async fn test_named_file_non_ascii_file_name() { let mut file = - NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml") - .unwrap(); + NamedFile::from_file(File::open("Cargo.toml").unwrap(), "貨物.toml").unwrap(); { file.file(); let _f: &File = &file; @@ -605,10 +602,9 @@ mod tests { #[actix_rt::test] async fn test_static_files() { - let srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; + let srv = + test::init_service(App::new().service(Files::new("/", ".").show_files_listing())) + .await; let req = TestRequest::with_uri("/missing").to_request(); let resp = test::call_service(&srv, req).await; @@ -620,10 +616,9 @@ mod tests { let resp = test::call_service(&srv, req).await; assert_eq!(resp.status(), StatusCode::NOT_FOUND); - let srv = test::init_service( - App::new().service(Files::new("/", ".").show_files_listing()), - ) - .await; + let srv = + test::init_service(App::new().service(Files::new("/", ".").show_files_listing())) + .await; let req = TestRequest::with_uri("/tests").to_request(); let resp = test::call_service(&srv, req).await; assert_eq!( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6fa3f7c6c..a688b2e6c 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -11,8 +11,7 @@ use actix_web::{ dev::{BodyEncoding, SizedStream}, http::{ header::{ - self, Charset, ContentDisposition, DispositionParam, DispositionType, - ExtendedValue, + self, Charset, ContentDisposition, DispositionParam, DispositionType, ExtendedValue, }, ContentEncoding, StatusCode, }, @@ -395,18 +394,10 @@ impl NamedFile { resp.encoding(ContentEncoding::Identity); resp.insert_header(( header::CONTENT_RANGE, - format!( - "bytes {}-{}/{}", - offset, - offset + length - 1, - self.md.len() - ), + format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), )); } else { - resp.insert_header(( - header::CONTENT_RANGE, - format!("bytes */{}", length), - )); + resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length))); return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); }; } else { diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index e420ce414..6718980cb 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -46,8 +46,7 @@ impl HttpRange { if start_str.is_empty() { // If no start is specified, end specifies the // range start relative to the end of the file. - let mut length: i64 = - end_str.parse().map_err(|_| ParseRangeErr(()))?; + let mut length: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; if length > size_sig { length = size_sig; @@ -72,8 +71,7 @@ impl HttpRange { // If no end is specified, range extends to end of the file. size_sig - start } else { - let mut end: i64 = - end_str.parse().map_err(|_| ParseRangeErr(()))?; + let mut end: i64 = end_str.parse().map_err(|_| ParseRangeErr(()))?; if start > end { return Err(ParseRangeErr(())); diff --git a/actix-files/src/service.rs b/actix-files/src/service.rs index a9822f486..14eea6ebc 100644 --- a/actix-files/src/service.rs +++ b/actix-files/src/service.rs @@ -11,8 +11,8 @@ use actix_web::{ use futures_util::future::{ok, Either, LocalBoxFuture, Ready}; use crate::{ - named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, - NamedFile, PathBufWrap, + named, Directory, DirectoryRenderer, FilesError, HttpService, MimeOverride, NamedFile, + PathBufWrap, }; /// Assembled file serving service. @@ -138,8 +138,7 @@ impl Service for FilesService { match NamedFile::open(path) { Ok(mut named_file) => { if let Some(ref mime_override) = self.mime_override { - let new_disposition = - mime_override(&named_file.content_type.type_()); + let new_disposition = mime_override(&named_file.content_type.type_()); named_file.content_disposition.disposition = new_disposition; } named_file.flags = self.file_flags; diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index 6cfa3a7f7..d21d4f8fd 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -23,10 +23,9 @@ async fn test_utf8_file_contents() { ); // prefer UTF-8 encoding - let srv = test::init_service( - App::new().service(Files::new("/", "./tests").prefer_utf8(true)), - ) - .await; + let srv = + test::init_service(App::new().service(Files::new("/", "./tests").prefer_utf8(true))) + .await; let req = TestRequest::with_uri("/utf8.txt").to_request(); let res = test::call_service(&srv, req).await; diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index f5c97262a..89cb0777f 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -120,8 +120,7 @@ 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(); + let socket = Socket::new(Domain::ipv4(), Type::stream(), Some(Protocol::tcp())).unwrap(); socket.bind(&addr.into()).unwrap(); socket.set_reuse_address(true).unwrap(); let tcp = socket.into_tcp_listener(); @@ -248,8 +247,7 @@ impl TestServer { pub async fn ws_at( &mut self, path: &str, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { let url = self.url(path); let connect = self.client.ws(url).connect(); connect.await.map(|(_, framed)| framed) @@ -258,8 +256,7 @@ impl TestServer { /// Connect to a websocket server pub async fn ws( &mut self, - ) -> Result, awc::error::WsClientError> - { + ) -> Result, awc::error::WsClientError> { self.ws_at("/").await } diff --git a/actix-http/src/request.rs b/actix-http/src/request.rs index 18c6c8889..197ec11c6 100644 --- a/actix-http/src/request.rs +++ b/actix-http/src/request.rs @@ -9,9 +9,9 @@ use http::{header, Method, Uri, Version}; use crate::extensions::Extensions; use crate::header::HeaderMap; -use crate::HttpMessage; use crate::message::{Message, RequestHead}; use crate::payload::{Payload, PayloadStream}; +use crate::HttpMessage; /// Request pub struct Request