From e7a05f98925dcd8461845b67fa5769b24aa88961 Mon Sep 17 00:00:00 2001 From: Daze Date: Tue, 1 Mar 2022 05:47:08 +0545 Subject: [PATCH 01/30] fix(docs): TestRequest example fixed (#2643) Co-authored-by: Rob Ede --- actix-web/src/test/test_request.rs | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index fc42253d7..a368d873f 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -24,10 +24,10 @@ use crate::cookie::{Cookie, CookieJar}; /// /// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern. /// You can generate various types of request via TestRequest's methods: -/// * `TestRequest::to_request` creates `actix_http::Request` instance. -/// * `TestRequest::to_srv_request` creates `ServiceRequest` instance, which is used for testing middlewares and chain adapters. -/// * `TestRequest::to_srv_response` creates `ServiceResponse` instance. -/// * `TestRequest::to_http_request` creates `HttpRequest` instance, which is used for testing handlers. +/// - [`TestRequest::to_request`] creates an [`actix_http::Request`](Request). +/// - [`TestRequest::to_srv_request`] creates a [`ServiceRequest`], which is used for testing middlewares and chain adapters. +/// - [`TestRequest::to_srv_response`] creates a [`ServiceResponse`]. +/// - [`TestRequest::to_http_request`] creates an [`HttpRequest`], which is used for testing handlers. /// /// ``` /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; @@ -42,15 +42,17 @@ use crate::cookie::{Cookie, CookieJar}; /// } /// /// #[actix_web::test] +/// # // force rustdoc to display the correct thing and also compile check the test +/// # async fn _test() {} /// async fn test_index() { -/// let req = test::TestRequest::default().insert_header("content-type", "text/plain") +/// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext()) /// .to_http_request(); /// -/// let resp = index(req).await.unwrap(); +/// let resp = index(req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request(); -/// let resp = index(req).await.unwrap(); +/// let resp = index(req).await; /// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); /// } /// ``` From 25c067327890b9095962b992cee60455ab47e13f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Mar 2022 02:20:48 +0000 Subject: [PATCH 02/30] Update MIGRATION-4.0.md --- actix-web/MIGRATION-4.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index e5f597f3c..7192d0bc6 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -29,7 +29,7 @@ Headings marked with :warning: are **breaking behavioral changes**. They will pr - [Server Must Be Polled :warning:](#server-must-be-polled-warning) - [Guards API](#guards-api) - [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) -- [`#[actix_web::main]` and `#[tokio::main]`](#actixwebmain-and-tokiomain) +- [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain) - [`web::block`](#webblock) ## MSRV From 3f03af1c5928e1c4ea0cfab4d2ddfc7043b571f1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Mar 2022 03:25:30 +0000 Subject: [PATCH 03/30] clippy --- actix-web/src/info.rs | 2 +- actix-web/src/rmap.rs | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/actix-web/src/info.rs b/actix-web/src/info.rs index ce1ef97c6..77b98110e 100644 --- a/actix-web/src/info.rs +++ b/actix-web/src/info.rs @@ -159,7 +159,7 @@ impl ConnectionInfo { pub fn realip_remote_addr(&self) -> Option<&str> { self.realip_remote_addr .as_deref() - .or_else(|| self.peer_addr.as_deref()) + .or(self.peer_addr.as_deref()) } /// Returns serialized IP address of the peer connection. diff --git a/actix-web/src/rmap.rs b/actix-web/src/rmap.rs index 932f7acde..6a1a187b2 100644 --- a/actix-web/src/rmap.rs +++ b/actix-web/src/rmap.rs @@ -151,7 +151,7 @@ impl ResourceMap { .char_indices() .filter_map(|(i, c)| (c == '/').then(|| i)) .nth(2) - .unwrap_or_else(|| path.len()); + .unwrap_or(path.len()); ( Cow::Borrowed(&path[..third_slash_index]), From 56e5c19b85d7bb58cf619b2145dff058d950f3ca Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 2 Mar 2022 17:53:47 +0000 Subject: [PATCH 04/30] add actix 0.13 support (#2675) --- actix-web-actors/CHANGES.md | 6 ++++++ actix-web-actors/Cargo.toml | 10 +++++----- actix-web-actors/README.md | 4 ++-- 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 07ca6a130..b4844bfa6 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -3,6 +3,12 @@ ## Unreleased - 2021-xx-xx +## 4.1.0 - 2022-03-02 +- Add support for `actix` version `0.13`. [#2675] + +[#2675]: https://github.com/actix/actix-web/pull/2675 + + ## 4.0.0 - 2022-02-25 - No significant changes since `4.0.0-beta.12`. diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 7cc53d63d..225326565 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-web-actors" -version = "4.0.0" +version = "4.1.0" authors = ["Nikolay Kim "] description = "Actix actors support for Actix Web" keywords = ["actix", "http", "web", "framework", "async"] @@ -14,16 +14,16 @@ name = "actix_web_actors" path = "src/lib.rs" [dependencies] -actix = { version = "0.12.0", default-features = false } +actix = { version = ">=0.12, <0.14", default-features = false } actix-codec = "0.5" -actix-http = "3.0.0" -actix-web = { version = "4.0.0", default-features = false } +actix-http = "3" +actix-web = { version = "4", default-features = false } bytes = "1" bytestring = "1" futures-core = { version = "0.3.7", default-features = false } pin-project-lite = "0.2" -tokio = { version = "1.8.4", features = ["sync"] } +tokio = { version = "1.13.1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" diff --git a/actix-web-actors/README.md b/actix-web-actors/README.md index 473a78ad9..357154a86 100644 --- a/actix-web-actors/README.md +++ b/actix-web-actors/README.md @@ -3,11 +3,11 @@ > Actix actors support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) -[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0)](https://docs.rs/actix-web-actors/4.0.0) +[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.1.0)](https://docs.rs/actix-web-actors/4.1.0) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
-[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0) +[![dependency status](https://deps.rs/crate/actix-web-actors/4.1.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.1.0) [![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 955c3ac0c4124f3807d0ac7be647668ea831cecc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Cl=C3=A9ment=20Nerma?= Date: Thu, 3 Mar 2022 01:29:59 +0100 Subject: [PATCH 05/30] Add support for audio files streaming (#2645) --- actix-files/CHANGES.md | 3 +++ actix-files/src/named.rs | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 3f8e2a823..4d4c790e8 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2021-xx-xx +- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645] + +[#2645]: https://github.com/actix/actix-web/pull/2645 ## 0.6.0 - 2022-02-25 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index a307c6385..6f3c6e1c8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -128,7 +128,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, + mime::IMAGE | mime::TEXT | mime::AUDIO | mime::VIDEO => DispositionType::Inline, mime::APPLICATION => match ct.subtype() { mime::JAVASCRIPT | mime::JSON => DispositionType::Inline, name if name == "wasm" => DispositionType::Inline, From 49cd303c3b6dfa8e8575c8161e94fd045852ef1b Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Mar 2022 03:12:38 +0000 Subject: [PATCH 06/30] fix dispatcher panic when conbining pipelining and keepalive fixes #2678 --- actix-http/src/h1/dispatcher.rs | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index f029fb108..648cf14d7 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -375,8 +375,6 @@ where DispatchError::Io(err) })?; - this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive()); - Ok(size) } @@ -459,7 +457,12 @@ where } // all messages are dealt with - None => return Ok(PollResponse::DoNothing), + None => { + // start keep-alive if last request allowed it + this.flags.set(Flags::KEEP_ALIVE, this.codec.keep_alive()); + + return Ok(PollResponse::DoNothing); + } }, StateProj::ServiceCall { fut } => { @@ -757,6 +760,7 @@ where let mut updated = false; + // decode from read buf as many full requests as possible loop { match this.codec.decode(this.read_buf) { Ok(Some(msg)) => { From da4c849f6221be0c3a551da6a4a7570ef693b0f3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 4 Mar 2022 03:16:02 +0000 Subject: [PATCH 07/30] prepare actix-http release 3.0.1 --- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index d73e8522f..c45a179dc 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,11 @@ ## Unreleased - 2021-xx-xx +## 3.0.1 - 2022-03-04 +- Fix panic in H1 dispatcher when pipelining is used with keep-alive. [#2678] + +[#2678]: https://github.com/actix/actix-web/issues/2678 + ## 3.0.0 - 2022-02-25 ### Dependencies - Updated `actix-*` to Tokio v1-based versions. [#1813] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 751b820e8..3f223d80d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.0" +version = "3.0.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 8d414a0fc..aaff7b6f1 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)](https://docs.rs/actix-http/3.0.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.1)](https://docs.rs/actix-http/3.0.1) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.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/status.svg)](https://deps.rs/crate/actix-http/3.0.0) +[![dependency status](https://deps.rs/crate/actix-http/3.0.1/status.svg)](https://deps.rs/crate/actix-http/3.0.1) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 0fa4d999d92a04cd2a9ddef10486fb8bb92700bc Mon Sep 17 00:00:00 2001 From: Santiago Date: Sat, 5 Mar 2022 23:24:21 +0100 Subject: [PATCH 08/30] fix(actix-http): encode correctly camel case header with n+2 hyphens (#2683) Co-authored-by: Rob Ede --- actix-http/CHANGES.md | 8 ++++++-- actix-http/src/h1/encoder.rs | 7 ++++++- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index c45a179dc..dc5ff4a85 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2021-xx-xx +### Fixed +- Encode correctly camel case header with n+2 hyphens [#2683] + +[#2683]: https://github.com/actix/actix-web/issues/2683 ## 3.0.1 - 2022-03-04 @@ -750,10 +754,10 @@ - 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 + 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 + 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 diff --git a/actix-http/src/h1/encoder.rs b/actix-http/src/h1/encoder.rs index ba98f4641..21cfd75c4 100644 --- a/actix-http/src/h1/encoder.rs +++ b/actix-http/src/h1/encoder.rs @@ -517,6 +517,7 @@ unsafe fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) { if let Some(c @ b'a'..=b'z') = iter.next() { buffer[index] = c & 0b1101_1111; } + index += 1; } index += 1; @@ -528,7 +529,7 @@ mod tests { use std::rc::Rc; use bytes::Bytes; - use http::header::AUTHORIZATION; + use http::header::{AUTHORIZATION, UPGRADE_INSECURE_REQUESTS}; use super::*; use crate::{ @@ -559,6 +560,9 @@ mod tests { head.headers .insert(CONTENT_TYPE, HeaderValue::from_static("plain/text")); + head.headers + .insert(UPGRADE_INSECURE_REQUESTS, HeaderValue::from_static("1")); + let mut head = RequestHeadType::Owned(head); let _ = head.encode_headers( @@ -574,6 +578,7 @@ mod tests { assert!(data.contains("Connection: close\r\n")); assert!(data.contains("Content-Type: plain/text\r\n")); assert!(data.contains("Date: date\r\n")); + assert!(data.contains("Upgrade-Insecure-Requests: 1\r\n")); let _ = head.encode_headers( &mut bytes, From 62fbd225bc5c36fd682389910ff5e13bd44e8c58 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 5 Mar 2022 22:26:19 +0000 Subject: [PATCH 09/30] prepare actix-http release 3.0.2 --- actix-http/CHANGES.md | 5 ++++- actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index dc5ff4a85..7be5dccff 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,8 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx + + +## 3.0.2 - 2022-03-05 ### Fixed -- Encode correctly camel case header with n+2 hyphens [#2683] +- Fix encoding camel-case header names with more than one hyphen. [#2683] [#2683]: https://github.com/actix/actix-web/issues/2683 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 3f223d80d..b365ff182 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.1" +version = "3.0.2" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index aaff7b6f1..3a2483191 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.1)](https://docs.rs/actix-http/3.0.1) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.2)](https://docs.rs/actix-http/3.0.2) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.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.1/status.svg)](https://deps.rs/crate/actix-http/3.0.1) +[![dependency status](https://deps.rs/crate/actix-http/3.0.2/status.svg)](https://deps.rs/crate/actix-http/3.0.2) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From 8c2fad31647abea48dbbc790983f6cebde4eb2f9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 5 Mar 2022 23:15:33 +0000 Subject: [PATCH 10/30] align hello-world examples --- actix-web/README.md | 17 +++++++++-------- actix-web/src/lib.rs | 19 ++++++++++--------- awc/Cargo.toml | 2 +- awc/src/lib.rs | 2 +- awc/src/responses/response.rs | 2 +- 5 files changed, 22 insertions(+), 20 deletions(-) diff --git a/actix-web/README.md b/actix-web/README.md index d0abb3aae..957fb47b8 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -56,18 +56,19 @@ Code: ```rust use actix_web::{get, web, App, HttpServer, Responder}; -#[get("/{id}/{name}/index.html")] -async fn index(params: web::Path<(u32, String)>) -> impl Responder { - let (id, name) = params.into_inner(); - format!("Hello {}! id:{}", name, id) +#[get("/hello/{name}")] +async fn greet(name: web::Path) -> impl Responder { + format!("Hello {name}!") } #[actix_web::main] // or #[tokio::main] async fn main() -> std::io::Result<()> { - HttpServer::new(|| App::new().service(index)) - .bind(("127.0.0.1", 8080))? - .run() - .await + HttpServer::new(|| { + App::new().service(greet) + }) + .bind(("127.0.0.1", 8080))? + .run() + .await } ``` diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 34bee7529..4eab24cec 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -4,18 +4,19 @@ //! ```no_run //! use actix_web::{get, web, App, HttpServer, Responder}; //! -//! #[get("/{id}/{name}/index.html")] -//! async fn index(path: web::Path<(u32, String)>) -> impl Responder { -//! let (id, name) = path.into_inner(); -//! format!("Hello {}! id:{}", name, id) +//! #[get("/hello/{name}")] +//! async fn greet(name: web::Path) -> impl Responder { +//! format!("Hello {}!", name) //! } //! -//! #[actix_web::main] +//! #[actix_web::main] // or #[tokio::main] //! async fn main() -> std::io::Result<()> { -//! HttpServer::new(|| App::new().service(index)) -//! .bind("127.0.0.1:8080")? -//! .run() -//! .await +//! HttpServer::new(|| { +//! App::new().service(greet) +//! }) +//! .bind(("127.0.0.1", 8080))? +//! .run() +//! .await //! } //! ``` //! diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 40d9d34b6..f86aa5543 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -5,7 +5,7 @@ authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", ] -description = "Async HTTP and WebSocket client library built on the Actix ecosystem" +description = "Async HTTP and WebSocket client library" keywords = ["actix", "http", "framework", "async", "web"] categories = [ "network-programming", diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 970ca2d92..3f5e25330 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,4 +1,4 @@ -//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem. +//! `awc` is an asynchronous HTTP and WebSocket client library. //! //! # Making a GET request //! ```no_run diff --git a/awc/src/responses/response.rs b/awc/src/responses/response.rs index 4e6a05f0f..c7c0a6362 100644 --- a/awc/src/responses/response.rs +++ b/awc/src/responses/response.rs @@ -160,7 +160,7 @@ where /// /// # Errors /// `Future` implementation returns error if: - /// - content length is greater than [limit](JsonBody::limit) (default: 2 MiB) + /// - content length is greater than [limit](ResponseBody::limit) (default: 2 MiB) /// /// # Examples /// ```no_run From 03456b8a33a4550b94785a7612e16d715755cd00 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 5 Mar 2022 23:43:31 +0000 Subject: [PATCH 11/30] update actix-web-in-http example --- actix-http/examples/actix-web.rs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/examples/actix-web.rs b/actix-http/examples/actix-web.rs index f8226507f..449e5899b 100644 --- a/actix-http/examples/actix-web.rs +++ b/actix-http/examples/actix-web.rs @@ -18,7 +18,8 @@ async fn main() -> std::io::Result<()> { HttpService::build() // pass the app to service builder // map_config is used to map App's configuration to ServiceBuilder - .finish(map_config(app, |_| AppConfig::default())) + // h1 will configure server to only use HTTP/1.1 + .h1(map_config(app, |_| AppConfig::default())) .tcp() })? .run() From 87f627cd5d33fe71833c24803174dcec5806fea2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 7 Mar 2022 16:48:04 +0000 Subject: [PATCH 12/30] improve servicerequest docs --- actix-http/src/responses/builder.rs | 4 +-- actix-web/src/service.rs | 50 ++++++++++++++--------------- 2 files changed, 26 insertions(+), 28 deletions(-) diff --git a/actix-http/src/responses/builder.rs b/actix-http/src/responses/builder.rs index 4a67423b1..063af92da 100644 --- a/actix-http/src/responses/builder.rs +++ b/actix-http/src/responses/builder.rs @@ -144,7 +144,7 @@ impl ResponseBuilder { self } - /// Set connection type to Upgrade + /// Set connection type to `Upgrade`. #[inline] pub fn upgrade(&mut self, value: V) -> &mut Self where @@ -161,7 +161,7 @@ impl ResponseBuilder { self } - /// Force close connection, even if it is marked as keep-alive + /// Force-close connection, even if it is marked as keep-alive. #[inline] pub fn force_close(&mut self) -> &mut Self { if let Some(parts) = self.inner() { diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs index 3843abcf8..426e9d62b 100644 --- a/actix-web/src/service.rs +++ b/actix-web/src/service.rs @@ -78,18 +78,18 @@ pub struct ServiceRequest { } impl ServiceRequest { - /// Construct service request + /// Construct `ServiceRequest` from parts. pub(crate) fn new(req: HttpRequest, payload: Payload) -> Self { Self { req, payload } } - /// Deconstruct request into parts + /// Deconstruct `ServiceRequest` into inner parts. #[inline] pub fn into_parts(self) -> (HttpRequest, Payload) { (self.req, self.payload) } - /// Get mutable access to inner `HttpRequest` and `Payload` + /// Returns mutable accessors to inner parts. #[inline] pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) { (&mut self.req, &mut self.payload) @@ -105,9 +105,7 @@ impl ServiceRequest { Self { req, payload } } - /// Construct request from request. - /// - /// The returned `ServiceRequest` would have no payload. + /// Construct `ServiceRequest` with no payload from given `HttpRequest`. #[inline] pub fn from_request(req: HttpRequest) -> Self { ServiceRequest { @@ -116,63 +114,63 @@ impl ServiceRequest { } } - /// Create service response + /// Create `ServiceResponse` from this request and given response. #[inline] pub fn into_response>>(self, res: R) -> ServiceResponse { let res = HttpResponse::from(res.into()); ServiceResponse::new(self.req, res) } - /// Create service response for error + /// Create `ServiceResponse` from this request and given error. #[inline] pub fn error_response>(self, err: E) -> ServiceResponse { let res = HttpResponse::from_error(err.into()); ServiceResponse::new(self.req, res) } - /// This method returns reference to the request head + /// Returns a reference to the request head. #[inline] pub fn head(&self) -> &RequestHead { self.req.head() } - /// This method returns reference to the request head + /// Returns a mutable reference to the request head. #[inline] pub fn head_mut(&mut self) -> &mut RequestHead { self.req.head_mut() } - /// Request's uri. + /// Returns the request URI. #[inline] pub fn uri(&self) -> &Uri { &self.head().uri } - /// Read the Request method. + /// Returns the request method. #[inline] pub fn method(&self) -> &Method { &self.head().method } - /// Read the Request Version. + /// Returns the request version. #[inline] pub fn version(&self) -> Version { self.head().version } + /// Returns a reference to request headers. #[inline] - /// Returns request's headers. pub fn headers(&self) -> &HeaderMap { &self.head().headers } + /// Returns a mutable reference to request headers. #[inline] - /// Returns mutable request's headers. pub fn headers_mut(&mut self) -> &mut HeaderMap { &mut self.head_mut().headers } - /// The target path of this Request. + /// Returns request path. #[inline] pub fn path(&self) -> &str { self.head().uri.path() @@ -184,7 +182,7 @@ impl ServiceRequest { self.req.query_string() } - /// Peer socket address. + /// Returns peer's socket address. /// /// 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. @@ -197,24 +195,23 @@ impl ServiceRequest { self.head().peer_addr } - /// Get *ConnectionInfo* for the current request. + /// Returns a reference to connection info. #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { self.req.connection_info() } - /// Returns a reference to the Path parameters. + /// Returns reference to the Path parameters. /// - /// Params is a container for URL parameters. - /// A variable segment is specified in the form `{identifier}`, - /// where the identifier can be used later in a request handler to - /// access the matched value for that segment. + /// Params is a container for URL parameters. A variable segment is specified in the form + /// `{identifier}`, where the identifier can be used later in a request handler to access the + /// matched value for that segment. #[inline] pub fn match_info(&self) -> &Path { self.req.match_info() } - /// Returns a mutable reference to the Path parameters. + /// Returns a mutable reference to the path match information. #[inline] pub fn match_info_mut(&mut self) -> &mut Path { self.req.match_info_mut() @@ -232,13 +229,13 @@ impl ServiceRequest { self.req.match_pattern() } - /// Get a reference to a `ResourceMap` of current application. + /// Returns a reference to the application's resource map. #[inline] pub fn resource_map(&self) -> &ResourceMap { self.req.resource_map() } - /// Service configuration + /// Returns a reference to the application's configuration. #[inline] pub fn app_config(&self) -> &AppConfig { self.req.app_config() @@ -262,6 +259,7 @@ impl ServiceRequest { self.req.conn_data() } + /// Return request cookies. #[cfg(feature = "cookies")] #[inline] pub fn cookies(&self) -> Result>>, CookieParseError> { From 8ddb24b49b0148f12524ec9cb3ff9ff67bfce743 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Tue, 8 Mar 2022 16:51:40 +0000 Subject: [PATCH 13/30] prepare awc release 3.0.0 (#2684) --- actix-http-test/Cargo.toml | 10 +-- actix-http/CHANGES.md | 7 +++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 +- actix-http/src/h1/decoder.rs | 22 +++++-- actix-multipart/Cargo.toml | 2 +- actix-test/Cargo.toml | 10 +-- actix-web-actors/Cargo.toml | 2 +- actix-web/CHANGES.md | 2 +- actix-web/Cargo.toml | 10 +-- awc/CHANGES.md | 98 +++++++++++++++++++++++++++++ awc/Cargo.toml | 14 ++--- awc/README.md | 4 +- awc/src/client/connector.rs | 7 ++- awc/src/client/h1proto.rs | 10 ++- awc/src/connect.rs | 29 +++++++++ awc/src/lib.rs | 55 +++++++++-------- awc/src/middleware/redirect.rs | 109 +++++++++++++++++++++++++-------- awc/src/request.rs | 2 +- 19 files changed, 306 insertions(+), 93 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index e2a2bcc3d..6f7563ffa 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -29,13 +29,13 @@ default = [] openssl = ["tls-openssl", "awc/openssl"] [dependencies] -actix-service = "2.0.0" +actix-service = "2" actix-codec = "0.5" actix-tls = "3" -actix-utils = "3.0.0" +actix-utils = "3" actix-rt = "2.2" actix-server = "2" -awc = { version = "3.0.0-beta.21", default-features = false } +awc = { version = "3", default-features = false } base64 = "0.13" bytes = "1" @@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true } tokio = { version = "1.8.4", features = ["sync"] } [dev-dependencies] -actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } -actix-http = "3.0.0" +actix-web = { version = "4", default-features = false, features = ["cookies"] } +actix-http = "3" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7be5dccff..ab7f1e332 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,13 @@ ## Unreleased - 2021-xx-xx +## 3.0.3 - 2022-03-08 +### Fixed +- Allow spaces between header name and colon when parsing responses. [#2684] + +[#2684]: https://github.com/actix/actix-web/issues/2684 + + ## 3.0.2 - 2022-03-05 ### Fixed - Fix encoding camel-case header names with more than one hyphen. [#2683] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index b365ff182..7006d92d7 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.2" +version = "3.0.3" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index 3a2483191..afe445ebd 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.2)](https://docs.rs/actix-http/3.0.2) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.3)](https://docs.rs/actix-http/3.0.3) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.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.2/status.svg)](https://deps.rs/crate/actix-http/3.0.2) +[![dependency status](https://deps.rs/crate/actix-http/3.0.3/status.svg)](https://deps.rs/crate/actix-http/3.0.3) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 17b9b695c..0e444756e 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -293,22 +293,35 @@ impl MessageType for ResponseHead { let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY; let (len, ver, status, h_len) = { - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY; + // SAFETY: + // Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the + // type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which + // do not require initialization. + let mut parsed = unsafe { + MaybeUninit::<[MaybeUninit>; MAX_HEADERS]>::uninit() + .assume_init() + }; - let mut res = httparse::Response::new(&mut parsed); - match res.parse(src)? { + let mut res = httparse::Response::new(&mut []); + + let mut config = httparse::ParserConfig::default(); + config.allow_spaces_after_header_name_in_responses(true); + + match config.parse_response_with_uninit_headers(&mut res, src, &mut parsed)? { httparse::Status::Complete(len) => { let version = if res.version.unwrap() == 1 { Version::HTTP_11 } else { Version::HTTP_10 }; + let status = StatusCode::from_u16(res.code.unwrap()) .map_err(|_| ParseError::Status)?; HeaderIndex::record(src, res.headers, &mut headers); (len, version, status, res.headers.len()) } + httparse::Status::Partial => { return if src.len() >= MAX_BUFFER_SIZE { error!("MAX_BUFFER_SIZE unprocessed data reached, closing"); @@ -360,9 +373,6 @@ pub(crate) const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex { pub(crate) const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS]; -pub(crate) const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] = - [httparse::EMPTY_HEADER; MAX_HEADERS]; - impl HeaderIndex { pub(crate) fn record( bytes: &[u8], diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 450a57fa9..e93e22941 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -14,7 +14,7 @@ name = "actix_multipart" path = "src/lib.rs" [dependencies] -actix-utils = "3.0.0" +actix-utils = "3" actix-web = { version = "4.0.0", default-features = false } bytes = "1" diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index af4aff56a..9938be67d 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" -actix-http = "3.0.0" +actix-http = "3" actix-http-test = "3.0.0-beta.13" actix-rt = "2.1" -actix-service = "2.0.0" -actix-utils = "3.0.0" -actix-web = { version = "4.0.0", default-features = false, features = ["cookies"] } -awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] } +actix-service = "2" +actix-utils = "3" +actix-web = { version = "4", default-features = false, features = ["cookies"] } +awc = { version = "3", default-features = false, features = ["cookies"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-util = { version = "0.3.7", default-features = false, features = [] } diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 225326565..c939f6ab5 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -28,7 +28,7 @@ tokio = { version = "1.13.1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" actix-test = "0.1.0-beta.13" -awc = { version = "3.0.0-beta.21", default-features = false } +awc = { version = "3", default-features = false } env_logger = "0.9" futures-util = { version = "0.3.7", default-features = false } diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index bf5caee86..2461cb3a1 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -15,7 +15,7 @@ - Updated `cookie` to `0.16`. [#2555] - Updated `language-tags` to `0.3`. - Updated `rand` to `0.8`. -- Updated `rustls` to `0.20.0`. [#2414] +- Updated `rustls` to `0.20`. [#2414] - Updated `tokio` to `1`. ### Added diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 6e453026a..7bbeec64d 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -71,9 +71,9 @@ actix-service = "2" actix-utils = "3" actix-tls = { version = "3", default-features = false, optional = true } -actix-http = { version = "3.0.0", features = ["http2", "ws"] } -actix-router = "0.5.0" -actix-web-codegen = { version = "4.0.0", optional = true } +actix-http = { version = "3", features = ["http2", "ws"] } +actix-router = "0.5" +actix-web-codegen = { version = "4", optional = true } ahash = "0.7" bytes = "1" @@ -100,9 +100,9 @@ time = { version = "0.3", default-features = false, features = ["formatting"] } url = "2.1" [dev-dependencies] -actix-files = "0.6.0" +actix-files = "0.6" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } -awc = { version = "3.0.0-beta.21", features = ["openssl"] } +awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/awc/CHANGES.md b/awc/CHANGES.md index 3fd59512a..ebc0dbe61 100644 --- a/awc/CHANGES.md +++ b/awc/CHANGES.md @@ -3,6 +3,103 @@ ## Unreleased - 2021-xx-xx +## 3.0.0 - 2022-03-07 +### Dependencies +- Updated `actix-*` to Tokio v1-based versions. [#1813] +- Updated `bytes` to `1.0`. [#1813] +- Updated `cookie` to `0.16`. [#2555] +- Updated `rand` to `0.8`. +- Updated `rustls` to `0.20`. [#2414] +- Updated `tokio` to `1`. + +### Added +- `trust-dns` crate feature to enable `trust-dns-resolver` as client DNS resolver; disabled by default. [#1969] +- `cookies` crate feature; enabled by default. [#2619] +- `compress-brotli` crate feature; enabled by default. [#2250] +- `compress-gzip` crate feature; enabled by default. [#2250] +- `compress-zstd` crate feature; enabled by default. [#2250] +- `client::Connector::handshake_timeout()` for customizing TLS connection handshake timeout. [#2081] +- `client::ConnectorService` as `client::Connector::finish` method's return type [#2081] +- `client::ConnectionIo` trait alias [#2081] +- `Client::headers()` to get default mut reference of `HeaderMap` of client object. [#2114] +- `ClientResponse::timeout()` for set the timeout of collecting response body. [#1931] +- `ClientBuilder::local_address()` for binding to a local IP address for this client. [#2024] +- `ClientRequest::insert_header()` method which allows using typed and untyped headers. [#1869] +- `ClientRequest::append_header()` method which allows using typed and untyped headers. [#1869] +- `ClientBuilder::add_default_header()` (and deprecate `ClientBuilder::header()`). [#2510] + +### Changed +- `client::Connector` type now only has one generic type for `actix_service::Service`. [#2063] +- `client::error::ConnectError` Resolver variant contains `Box` type. [#1905] +- `client::ConnectorConfig` default timeout changed to 5 seconds. [#1905] +- `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] +- Fix http/https encoding when enabling `compress` feature. [#2116] +- Rename `TestResponse::{header => append_header, set => insert_header}`. These methods now take a `TryIntoHeaderPair`. [#2094] +- `ClientBuilder::connector()` method now takes `Connector` type. [#2008] +- Basic auth now accepts blank passwords as an empty string instead of an `Option`. [#2050] +- Relax default timeout for `Connector` to 5 seconds (up from 1 second). [#1905] +- `*::send_json()` and `*::send_form()` methods now receive `impl Serialize`. [#2553] +- `FrozenClientRequest::extra_header()` now uses receives an `impl TryIntoHeaderPair`. [#2553] +- Rename `Connector::{ssl => openssl}()`. [#2503] +- `ClientRequest::send_body` now takes an `impl MessageBody`. [#2546] +- Rename `MessageBody => ResponseBody` to avoid conflicts with `MessageBody` trait. [#2546] +- Minimum supported Rust version (MSRV) is now 1.54. + +### Fixed +- Send headers along with redirected requests. [#2310] +- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503] +- Remove unnecessary `Unpin` bounds on `*::send_stream`. [#2553] +- `impl Future` for `ResponseBody` no longer requires the body type be `Unpin`. [#2546] +- `impl Future` for `JsonBody` no longer requires the body type be `Unpin`. [#2546] +- `impl Stream` for `ClientResponse` no longer requires the body type be `Unpin`. [#2546] + +### Removed +- `compress` crate feature. [#2250] +- `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] +- Deprecated methods on `ClientRequest`: `if_true`, `if_some`. [#2148] +- `ClientBuilder::default` function [#2008] + +### Security +- `cookie` upgrade addresses [`RUSTSEC-2020-0071`]. + +[`RUSTSEC-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html + +[#1813]: https://github.com/actix/actix-web/pull/1813 +[#1869]: https://github.com/actix/actix-web/pull/1869 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1905]: https://github.com/actix/actix-web/pull/1905 +[#1931]: https://github.com/actix/actix-web/pull/1931 +[#1969]: https://github.com/actix/actix-web/pull/1969 +[#1969]: https://github.com/actix/actix-web/pull/1969 +[#1981]: https://github.com/actix/actix-web/pull/1981 +[#2008]: https://github.com/actix/actix-web/pull/2008 +[#2024]: https://github.com/actix/actix-web/pull/2024 +[#2050]: https://github.com/actix/actix-web/pull/2050 +[#2063]: https://github.com/actix/actix-web/pull/2063 +[#2081]: https://github.com/actix/actix-web/pull/2081 +[#2081]: https://github.com/actix/actix-web/pull/2081 +[#2094]: https://github.com/actix/actix-web/pull/2094 +[#2114]: https://github.com/actix/actix-web/pull/2114 +[#2116]: https://github.com/actix/actix-web/pull/2116 +[#2148]: https://github.com/actix/actix-web/pull/2148 +[#2250]: https://github.com/actix/actix-web/pull/2250 +[#2310]: https://github.com/actix/actix-web/pull/2310 +[#2414]: https://github.com/actix/actix-web/pull/2414 +[#2425]: https://github.com/actix/actix-web/pull/2425 +[#2474]: https://github.com/actix/actix-web/pull/2474 +[#2503]: https://github.com/actix/actix-web/pull/2503 +[#2510]: https://github.com/actix/actix-web/pull/2510 +[#2546]: https://github.com/actix/actix-web/pull/2546 +[#2553]: https://github.com/actix/actix-web/pull/2553 +[#2555]: https://github.com/actix/actix-web/pull/2555 + + +
+3.0.0 Pre-Releases + ## 3.0.0-beta.21 - 2022-02-16 - No significant changes since `3.0.0-beta.20`. @@ -170,6 +267,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 +
## 2.0.3 - 2020-11-29 ### Fixed diff --git a/awc/Cargo.toml b/awc/Cargo.toml index f86aa5543..9dd29e4b7 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "awc" -version = "3.0.0-beta.21" +version = "3.0.0" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -59,11 +59,11 @@ dangerous-h2c = [] [dependencies] actix-codec = "0.5" -actix-service = "2.0.0" -actix-http = { version = "3.0.0", features = ["http2", "ws"] } +actix-service = "2" +actix-http = { version = "3", features = ["http2", "ws"] } actix-rt = { version = "2.1", default-features = false } actix-tls = { version = "3", features = ["connect", "uri"] } -actix-utils = "3.0.0" +actix-utils = "3" ahash = "0.7" base64 = "0.13" @@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features trust-dns-resolver = { version = "0.20.0", optional = true } [dev-dependencies] -actix-http = { version = "3.0.0", features = ["openssl"] } +actix-http = { version = "3", features = ["openssl"] } actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } actix-server = "2" actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } -actix-utils = "3.0.0" -actix-web = { version = "4.0.0", features = ["openssl"] } +actix-utils = "3" +actix-web = { version = "4", features = ["openssl"] } brotli = "3.3.3" const-str = "0.3" diff --git a/awc/README.md b/awc/README.md index 417647e62..db70f7332 100644 --- a/awc/README.md +++ b/awc/README.md @@ -3,9 +3,9 @@ > Async HTTP and WebSocket client library. [![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) -[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.21)](https://docs.rs/awc/3.0.0-beta.21) +[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0)](https://docs.rs/awc/3.0.0) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) -[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.21/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.21) +[![Dependency Status](https://deps.rs/crate/awc/3.0.0/status.svg)](https://deps.rs/crate/awc/3.0.0) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) ## Documentation & Resources diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs index 26c62b924..51d6e180b 100644 --- a/awc/src/client/connector.rs +++ b/awc/src/client/connector.rs @@ -246,7 +246,12 @@ where /// /// The default limit size is 100. pub fn limit(mut self, limit: usize) -> Self { - self.config.limit = limit; + if limit == 0 { + self.config.limit = u32::MAX as usize; + } else { + self.config.limit = limit; + } + self } diff --git a/awc/src/client/h1proto.rs b/awc/src/client/h1proto.rs index 4f6a87ac5..8738c2f7f 100644 --- a/awc/src/client/h1proto.rs +++ b/awc/src/client/h1proto.rs @@ -83,12 +83,12 @@ where false }; - framed.send((head, body.size()).into()).await?; - let mut pin_framed = Pin::new(&mut framed); // special handle for EXPECT request. let (do_send, mut res_head) = if is_expect { + pin_framed.send((head, body.size()).into()).await?; + let head = poll_fn(|cx| pin_framed.as_mut().poll_next(cx)) .await .ok_or(ConnectError::Disconnected)??; @@ -97,13 +97,17 @@ where // and current head would be used as final response head. (head.status == StatusCode::CONTINUE, Some(head)) } else { + pin_framed.feed((head, body.size()).into()).await?; + (true, None) }; if do_send { // send request body match body.size() { - BodySize::None | BodySize::Sized(0) => {} + BodySize::None | BodySize::Sized(0) => { + poll_fn(|cx| pin_framed.as_mut().flush(cx)).await?; + } _ => send_body(body, pin_framed.as_mut()).await?, }; diff --git a/awc/src/connect.rs b/awc/src/connect.rs index f93014a67..be1ea0fee 100644 --- a/awc/src/connect.rs +++ b/awc/src/connect.rs @@ -30,17 +30,35 @@ pub type BoxConnectorService = Rc< pub type BoxedSocket = Box; +/// Combined HTTP and WebSocket request type received by connection service. pub enum ConnectRequest { + /// Standard HTTP request. + /// + /// Contains the request head, body type, and optional pre-resolved socket address. Client(RequestHeadType, AnyBody, Option), + + /// Tunnel used by WebSocket connection requests. + /// + /// Contains the request head and optional pre-resolved socket address. Tunnel(RequestHead, Option), } +/// Combined HTTP response & WebSocket tunnel type returned from connection service. pub enum ConnectResponse { + /// Standard HTTP response. Client(ClientResponse), + + /// Tunnel used for WebSocket communication. + /// + /// Contains response head and framed HTTP/1.1 codec. Tunnel(ResponseHead, Framed), } impl ConnectResponse { + /// Unwraps type into HTTP response. + /// + /// # Panics + /// Panics if enum variant is not `Client`. pub fn into_client_response(self) -> ClientResponse { match self { ConnectResponse::Client(res) => res, @@ -50,6 +68,10 @@ impl ConnectResponse { } } + /// Unwraps type into WebSocket tunnel response. + /// + /// # Panics + /// Panics if enum variant is not `Tunnel`. pub fn into_tunnel_response(self) -> (ResponseHead, Framed) { match self { ConnectResponse::Tunnel(head, framed) => (head, framed), @@ -136,30 +158,37 @@ where ConnectRequestProj::Connection { fut, req } => { let connection = ready!(fut.poll(cx))?; let req = req.take().unwrap(); + match req { ConnectRequest::Client(head, body, ..) => { // send request let fut = ConnectRequestFuture::Client { fut: connection.send_request(head, body), }; + self.set(fut); } + ConnectRequest::Tunnel(head, ..) => { // send request let fut = ConnectRequestFuture::Tunnel { fut: connection.open_tunnel(RequestHeadType::from(head)), }; + self.set(fut); } } + self.poll(cx) } + ConnectRequestProj::Client { fut } => { let (head, payload) = ready!(fut.as_mut().poll(cx))?; Poll::Ready(Ok(ConnectResponse::Client(ClientResponse::new( head, payload, )))) } + ConnectRequestProj::Tunnel { fut } => { let (head, framed) = ready!(fut.as_mut().poll(cx))?; let framed = framed.into_map_io(|io| Box::new(io) as _); diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 3f5e25330..8d6ea759a 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -1,22 +1,25 @@ //! `awc` is an asynchronous HTTP and WebSocket client library. //! -//! # Making a GET request +//! # `GET` Requests //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { +//! // create client //! let mut client = awc::Client::default(); -//! let response = client.get("http://www.rust-lang.org") // <- Create request builder -//! .insert_header(("User-Agent", "Actix-web")) -//! .send() // <- Send http request -//! .await?; //! -//! println!("Response: {:?}", response); +//! // construct request +//! let req = client.get("http://www.rust-lang.org") +//! .insert_header(("User-Agent", "awc/3.0")); +//! +//! // send request and await response +//! let res = req.send().await?; +//! println!("Response: {:?}", res); //! # Ok(()) //! # } //! ``` //! -//! # Making POST requests -//! ## Raw body contents +//! # `POST` Requests +//! ## Raw Body //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), awc::error::SendRequestError> { @@ -28,20 +31,6 @@ //! # } //! ``` //! -//! ## Forms -//! ```no_run -//! # #[actix_rt::main] -//! # async fn main() -> Result<(), awc::error::SendRequestError> { -//! let params = [("foo", "bar"), ("baz", "quux")]; -//! -//! let mut client = awc::Client::default(); -//! let response = client.post("http://httpbin.org/post") -//! .send_form(¶ms) -//! .await?; -//! # Ok(()) -//! # } -//! ``` -//! //! ## JSON //! ```no_run //! # #[actix_rt::main] @@ -59,6 +48,20 @@ //! # } //! ``` //! +//! ## URL Encoded Form +//! ```no_run +//! # #[actix_rt::main] +//! # async fn main() -> Result<(), awc::error::SendRequestError> { +//! let params = [("foo", "bar"), ("baz", "quux")]; +//! +//! let mut client = awc::Client::default(); +//! let response = client.post("http://httpbin.org/post") +//! .send_form(¶ms) +//! .await?; +//! # Ok(()) +//! # } +//! ``` +//! //! # Response Compression //! All [official][iana-encodings] and common content encoding codecs are supported, optionally. //! @@ -76,11 +79,12 @@ //! //! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding //! -//! # WebSocket support +//! # WebSockets //! ```no_run //! # #[actix_rt::main] //! # async fn main() -> Result<(), Box> { -//! use futures_util::{sink::SinkExt, stream::StreamExt}; +//! use futures_util::{sink::SinkExt as _, stream::StreamExt as _}; +//! //! let (_resp, mut connection) = awc::Client::new() //! .ws("ws://echo.websocket.org") //! .connect() @@ -89,8 +93,9 @@ //! connection //! .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())); +//! assert_eq!(response, awc::ws::Frame::Text("Echo".into())); //! # Ok(()) //! # } //! ``` diff --git a/awc/src/middleware/redirect.rs b/awc/src/middleware/redirect.rs index ac6690471..d48822168 100644 --- a/awc/src/middleware/redirect.rs +++ b/awc/src/middleware/redirect.rs @@ -161,7 +161,8 @@ where | StatusCode::SEE_OTHER | StatusCode::TEMPORARY_REDIRECT | StatusCode::PERMANENT_REDIRECT - if *max_redirect_times > 0 => + if *max_redirect_times > 0 + && res.headers().contains_key(header::LOCATION) => { let reuse_body = res.head().status == StatusCode::TEMPORARY_REDIRECT || res.head().status == StatusCode::PERMANENT_REDIRECT; @@ -245,26 +246,32 @@ where } fn build_next_uri(res: &ClientResponse, prev_uri: &Uri) -> Result { - let uri = res - .headers() - .get(header::LOCATION) - .map(|value| { - // try to parse the location to a full uri - let uri = Uri::try_from(value.as_bytes()) - .map_err(|e| SendRequestError::Url(InvalidUrl::HttpError(e.into())))?; - if uri.scheme().is_none() || uri.authority().is_none() { - let uri = Uri::builder() - .scheme(prev_uri.scheme().cloned().unwrap()) - .authority(prev_uri.authority().cloned().unwrap()) - .path_and_query(value.as_bytes()) - .build()?; - Ok::<_, SendRequestError>(uri) - } else { - Ok(uri) - } - }) - // TODO: this error type is wrong. - .ok_or(SendRequestError::Url(InvalidUrl::MissingScheme))??; + // responses without this header are not processed + let location = res.headers().get(header::LOCATION).unwrap(); + + // try to parse the location and resolve to a full URI but fall back to default if it fails + let uri = Uri::try_from(location.as_bytes()).unwrap_or_else(|_| Uri::default()); + + let uri = if uri.scheme().is_none() || uri.authority().is_none() { + let builder = Uri::builder() + .scheme(prev_uri.scheme().cloned().unwrap()) + .authority(prev_uri.authority().cloned().unwrap()); + + // when scheme or authority is missing treat the location value as path and query + // recover error where location does not have leading slash + let path = if location.as_bytes().starts_with(b"/") { + location.as_bytes().to_owned() + } else { + [b"/", location.as_bytes()].concat() + }; + + builder + .path_and_query(path) + .build() + .map_err(|err| SendRequestError::Url(InvalidUrl::HttpError(err)))? + } else { + uri + }; Ok(uri) } @@ -287,10 +294,13 @@ mod tests { use actix_web::{web, App, Error, HttpRequest, HttpResponse}; use super::*; - use crate::{http::header::HeaderValue, ClientBuilder}; + use crate::{ + http::{header::HeaderValue, StatusCode}, + ClientBuilder, + }; #[actix_rt::test] - async fn test_basic_redirect() { + async fn basic_redirect() { let client = ClientBuilder::new() .disable_redirects() .wrap(Redirect::new().max_redirect_times(10)) @@ -315,6 +325,44 @@ mod tests { assert_eq!(res.status().as_u16(), 400); } + #[actix_rt::test] + async fn redirect_relative_without_leading_slash() { + let client = ClientBuilder::new().finish(); + + let srv = actix_test::start(|| { + App::new() + .service(web::resource("/").route(web::to(|| async { + HttpResponse::Found() + .insert_header(("location", "abc/")) + .finish() + }))) + .service( + web::resource("/abc/") + .route(web::to(|| async { HttpResponse::Accepted().finish() })), + ) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status(), StatusCode::ACCEPTED); + } + + #[actix_rt::test] + async fn redirect_without_location() { + let client = ClientBuilder::new() + .disable_redirects() + .wrap(Redirect::new().max_redirect_times(10)) + .finish(); + + let srv = actix_test::start(|| { + App::new().service(web::resource("/").route(web::to(|| async { + Ok::<_, Error>(HttpResponse::Found().finish()) + }))) + }); + + let res = client.get(srv.url("/")).send().await.unwrap(); + assert_eq!(res.status(), StatusCode::FOUND); + } + #[actix_rt::test] async fn test_redirect_limit() { let client = ClientBuilder::new() @@ -328,14 +376,14 @@ mod tests { .service(web::resource("/").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Found() - .append_header(("location", "/test")) + .insert_header(("location", "/test")) .finish(), ) }))) .service(web::resource("/test").route(web::to(|| async { Ok::<_, Error>( HttpResponse::Found() - .append_header(("location", "/test2")) + .insert_header(("location", "/test2")) .finish(), ) }))) @@ -345,8 +393,15 @@ mod tests { }); let res = client.get(srv.url("/")).send().await.unwrap(); - - assert_eq!(res.status().as_u16(), 302); + assert_eq!(res.status(), StatusCode::FOUND); + assert_eq!( + res.headers() + .get(header::LOCATION) + .unwrap() + .to_str() + .unwrap(), + "/test2" + ); } #[actix_rt::test] diff --git a/awc/src/request.rs b/awc/src/request.rs index 8bcf1ee01..102db3c16 100644 --- a/awc/src/request.rs +++ b/awc/src/request.rs @@ -505,7 +505,7 @@ impl fmt::Debug for ClientRequest { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { writeln!( f, - "\nClientRequest {:?} {}:{}", + "\nClientRequest {:?} {} {}", self.head.version, self.head.method, self.head.uri )?; writeln!(f, " headers:")?; From be986d96b387f9a040904a6385e9500a4eb5bb8f Mon Sep 17 00:00:00 2001 From: Dylan DPC <99973273+Dylan-DPC@users.noreply.github.com> Date: Tue, 8 Mar 2022 18:42:42 +0100 Subject: [PATCH 14/30] bump `regex` requirement to `1.5.5` due to security advisory (#2687) --- actix-web/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 7bbeec64d..093c000b4 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -90,7 +90,7 @@ once_cell = "1.5" log = "0.4" mime = "0.3" pin-project-lite = "0.2.7" -regex = "1.4" +regex = "1.5.5" serde = "1.0" serde_json = "1.0" serde_urlencoded = "0.7" From dce943851802ea792a3fd233110f011b7b7a1d6a Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Mar 2022 18:11:06 +0000 Subject: [PATCH 15/30] document with ws feature --- actix-http/Cargo.toml | 2 +- actix-http/src/payload.rs | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7006d92d7..8ac3465a2 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -20,7 +20,7 @@ edition = "2018" [package.metadata.docs.rs] # features that docs.rs will build with -features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] +features = ["http2", "ws", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"] [lib] name = "actix_http" diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index 33d9ec6f5..ee0128af4 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -13,7 +13,8 @@ use crate::error::PayloadError; /// A boxed payload stream. pub type BoxedPayloadStream = Pin>>>; -#[deprecated(since = "4.0.0", note = "Renamed to `BoxedPayloadStream`.")] +#[doc(hidden)] +#[deprecated(since = "3.0.0", note = "Renamed to `BoxedPayloadStream`.")] pub type PayloadStream = BoxedPayloadStream; #[cfg(not(feature = "http2"))] From 5611b98c0d46537bcb330774c74f814cde6bad31 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 9 Mar 2022 18:13:39 +0000 Subject: [PATCH 16/30] prepare actix-http release 3.0.4 --- actix-http/CHANGES.md | 5 +++++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index ab7f1e332..71132c6b2 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -3,6 +3,11 @@ ## Unreleased - 2021-xx-xx +## 3.0.4 - 2022-03-09 +### Fixed +- Document on docs.rs with `ws` feature enabled. + + ## 3.0.3 - 2022-03-08 ### Fixed - Allow spaces between header name and colon when parsing responses. [#2684] diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 8ac3465a2..7c9284836 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.0.3" +version = "3.0.4" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index afe445ebd..bf0b7c824 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.3)](https://docs.rs/actix-http/3.0.3) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.4)](https://docs.rs/actix-http/3.0.4) [![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.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.3/status.svg)](https://deps.rs/crate/actix-http/3.0.3) +[![dependency status](https://deps.rs/crate/actix-http/3.0.4/status.svg)](https://deps.rs/crate/actix-http/3.0.4) [![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From a35804b89f5b08b11304d4fa3e4ca37c9a4f6627 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 01:05:03 +0000 Subject: [PATCH 17/30] update files tokio-uring to 0.3 --- actix-files/CHANGES.md | 3 ++- actix-files/Cargo.toml | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 4d4c790e8..2fdc7ba34 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,7 +1,8 @@ # Changes ## Unreleased - 2021-xx-xx -- Add support for streaming audio files by setting the `content-disposition` header `inline` instead of `attachement`. [#2645] +- Update `tokio-uring` dependency to `0.3`. +- Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index e7e6aea23..8f856c109 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -39,10 +39,12 @@ mime_guess = "2.0.1" percent-encoding = "2.1" pin-project-lite = "0.2.7" +# experimental-io-uring tokio-uring = { version = "0.2", optional = true, features = ["bytes"] } +actix-server = "2.1" # ensure matching tokio-uring versions [dev-dependencies] -actix-rt = "2.2" +actix-rt = "2.7" actix-test = "0.1.0-beta.13" actix-web = "4.0.0" tempfile = "3.2" From 1fd90f0b1098bf6b6bf7219b74e7b8adb29c851f Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 01:29:26 +0000 Subject: [PATCH 18/30] Implement getters for named file fields (#2689) Co-authored-by: Janis Goldschmidt --- actix-files/CHANGES.md | 2 ++ actix-files/src/named.rs | 55 +++++++++++++++++++++++++++++++++++----- 2 files changed, 51 insertions(+), 6 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index 2fdc7ba34..7e99c2ae1 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,9 +1,11 @@ # Changes ## Unreleased - 2021-xx-xx +- Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] - Update `tokio-uring` dependency to `0.3`. - Audio files now use `Content-Disposition: inline` instead of `attachment`. [#2645] +[#2021]: https://github.com/actix/actix-web/pull/2021 [#2645]: https://github.com/actix/actix-web/pull/2645 diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 6f3c6e1c8..7ab29e5c8 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -23,6 +23,7 @@ use actix_web::{ use bitflags::bitflags; use derive_more::{Deref, DerefMut}; use futures_core::future::LocalBoxFuture; +use mime::Mime; use mime_guess::from_path; use crate::{encoding::equiv_utf8_text, range::HttpRange}; @@ -76,8 +77,8 @@ pub struct NamedFile { pub(crate) md: Metadata, pub(crate) flags: Flags, pub(crate) status_code: StatusCode, - pub(crate) content_type: mime::Mime, - pub(crate) content_disposition: header::ContentDisposition, + pub(crate) content_type: Mime, + pub(crate) content_disposition: ContentDisposition, pub(crate) encoding: Option, } @@ -238,13 +239,13 @@ impl NamedFile { Self::from_file(file, path) } - /// Returns reference to the underlying `File` object. + /// Returns reference to the underlying file object. #[inline] pub fn file(&self) -> &File { &self.file } - /// Retrieve the path of this file. + /// Returns the filesystem path to this file. /// /// # Examples /// ``` @@ -262,6 +263,48 @@ impl NamedFile { self.path.as_path() } + /// Returns the time the file was last modified. + /// + /// Returns `None` only on unsupported platforms; see [`std::fs::Metadata::modified()`]. + /// Therefore, it is usually safe to unwrap this. + #[inline] + pub fn modified(&self) -> Option { + self.modified + } + + /// Returns the filesystem metadata associated with this file. + #[inline] + pub fn metadata(&self) -> &Metadata { + &self.md + } + + /// Returns the `Content-Type` header that will be used when serving this file. + #[inline] + pub fn content_type(&self) -> &Mime { + &self.content_type + } + + /// Returns the `Content-Disposition` that will be used when serving this file. + #[inline] + pub fn content_disposition(&self) -> &ContentDisposition { + &self.content_disposition + } + + /// Returns the `Content-Encoding` that will be used when serving this file. + /// + /// A return value of `None` indicates that the content is not already using a compressed + /// representation and may be subject to compression downstream. + #[inline] + pub fn content_encoding(&self) -> Option { + self.encoding + } + + /// Returns the status code for serving this file. + #[inline] + pub fn status_code(&self) -> &StatusCode { + &self.status_code + } + /// Set response **Status Code** pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; @@ -271,7 +314,7 @@ impl NamedFile { /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred /// from the filename extension. #[inline] - pub fn set_content_type(mut self, mime_type: mime::Mime) -> Self { + pub fn set_content_type(mut self, mime_type: Mime) -> Self { self.content_type = mime_type; self } @@ -284,7 +327,7 @@ impl NamedFile { /// filename is taken from the path provided in the `open` method after converting it to UTF-8 /// (using `to_string_lossy`). #[inline] - pub fn set_content_disposition(mut self, cd: header::ContentDisposition) -> Self { + pub fn set_content_disposition(mut self, cd: ContentDisposition) -> Self { self.content_disposition = cd; self.flags.insert(Flags::CONTENT_DISPOSITION); self From 745e738955e7b1572f970fb25dfb4de9bb61b985 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 02:36:57 +0000 Subject: [PATCH 19/30] fix negative impl assertion on 1.60+ see https://github.com/rust-lang/rust/issues/94791 --- actix-files/src/named.rs | 5 ++--- actix-http/Cargo.toml | 1 + actix-http/src/h1/payload.rs | 3 +++ 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 7ab29e5c8..459670c3a 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -210,11 +210,10 @@ impl NamedFile { Self::from_file(file, path) } - #[allow(rustdoc::broken_intra_doc_links)] /// Attempts to open a file asynchronously in read-only mode. /// - /// When the `experimental-io-uring` crate feature is enabled, this will be async. - /// Otherwise, it will be just like [`open`][Self::open]. + /// When the `experimental-io-uring` crate feature is enabled, this will be async. Otherwise, it + /// will behave just like `open`. /// /// # Examples /// ``` diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7c9284836..a063bd1b9 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -110,6 +110,7 @@ memchr = "2.4" once_cell = "1.9" rcgen = "0.8" regex = "1.3" +rustversion = "1" rustls-pemfile = "0.2" serde = { version = "1.0", features = ["derive"] } serde_json = "1.0" diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index 4d031c15a..5a93e9051 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -263,7 +263,10 @@ mod tests { assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); assert_impl_all!(Inner: Unpin, Send, Sync); + #[rustversion::before(1.60)] assert_not_impl_any!(Inner: UnwindSafe, RefUnwindSafe); + #[rustversion::since(1.60)] + assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { From a03a2a0076d16dd80467b6c91697baabfeb66ddf Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 02:54:06 +0000 Subject: [PATCH 20/30] deprecate `NamedFile::set_status_code` --- actix-files/src/lib.rs | 27 +++++++++++++++++++++++++-- actix-files/src/named.rs | 17 ++++++----------- 2 files changed, 31 insertions(+), 13 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 41113f2ab..40327e5e8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -364,20 +364,43 @@ mod tests { ); } + #[allow(deprecated)] #[actix_rt::test] - async fn test_named_file_status_code_text() { - let mut file = NamedFile::open_async("Cargo.toml") + async fn status_code_customize_same_output() { + let file1 = NamedFile::open_async("Cargo.toml") .await .unwrap() .set_status_code(StatusCode::NOT_FOUND); + + let file2 = NamedFile::open_async("Cargo.toml") + .await + .unwrap() + .customize() + .with_status(StatusCode::NOT_FOUND); + + let req = TestRequest::default().to_http_request(); + let res1 = file1.respond_to(&req); + let res2 = file2.respond_to(&req); + + assert_eq!(res1.status(), StatusCode::NOT_FOUND); + assert_eq!(res2.status(), StatusCode::NOT_FOUND); + } + + #[actix_rt::test] + async fn test_named_file_status_code_text() { + let mut file = NamedFile::open_async("Cargo.toml").await.unwrap(); + { file.file(); let _f: &File = &file; } + { let _f: &mut File = &mut file; } + let file = file.customize().with_status(StatusCode::NOT_FOUND); + let req = TestRequest::default().to_http_request(); let resp = file.respond_to(&req); assert_eq!( diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 459670c3a..5580e6f7e 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -298,20 +298,15 @@ impl NamedFile { self.encoding } - /// Returns the status code for serving this file. - #[inline] - pub fn status_code(&self) -> &StatusCode { - &self.status_code - } - - /// Set response **Status Code** + /// Set response status code. + #[deprecated(since = "0.7.0", note = "Prefer `Responder::customize()`.")] pub fn set_status_code(mut self, status: StatusCode) -> Self { self.status_code = status; self } - /// Set the MIME Content-Type for serving this file. By default the Content-Type is inferred - /// from the filename extension. + /// Sets the `Content-Type` header that will be used when serving this file. By default the + /// `Content-Type` is inferred from the filename extension. #[inline] pub fn set_content_type(mut self, mime_type: Mime) -> Self { self.content_type = mime_type; @@ -332,9 +327,9 @@ impl NamedFile { self } - /// Disable `Content-Disposition` header. + /// Disables `Content-Disposition` header. /// - /// By default Content-Disposition` header is enabled. + /// By default, the `Content-Disposition` header is sent. #[inline] pub fn disable_content_disposition(mut self) -> Self { self.flags.remove(Flags::CONTENT_DISPOSITION); From 80d222aa78717893d98e6f6982b16a7a8d7ee95c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 03:12:29 +0000 Subject: [PATCH 21/30] use tracing in actix-http --- actix-http/Cargo.toml | 4 +-- actix-http/README.md | 2 +- actix-http/examples/echo.rs | 3 +- actix-http/examples/hello-world.rs | 9 +++-- actix-http/examples/streaming-error.rs | 3 +- actix-http/examples/ws.rs | 9 ++--- actix-http/src/encoding/encoder.rs | 9 ++--- actix-http/src/h1/chunked.rs | 5 +-- actix-http/src/h1/decoder.rs | 2 +- actix-http/src/h1/dispatcher.rs | 49 +++++++++++++------------- actix-http/src/h1/service.rs | 9 ++--- actix-http/src/h1/timer.rs | 7 ++-- actix-http/src/h2/dispatcher.rs | 4 +-- actix-http/src/h2/service.rs | 4 +-- actix-http/src/service.rs | 11 +++--- actix-http/src/ws/codec.rs | 3 +- actix-http/src/ws/dispatcher.rs | 2 +- actix-http/src/ws/frame.rs | 2 +- actix-http/src/ws/proto.rs | 4 ++- actix-http/tests/test_rustls.rs | 2 ++ actix-web/src/server.rs | 6 ++-- 21 files changed, 80 insertions(+), 69 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index a063bd1b9..6d410e46f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -73,11 +73,11 @@ httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" language-tags = "0.3" -log = "0.4" mime = "0.3" percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } # http2 h2 = { version = "0.3.9", optional = true } @@ -121,7 +121,7 @@ tokio = { version = "1.8.4", features = ["net", "rt", "macros"] } [[example]] name = "ws" -required-features = ["rustls"] +required-features = ["ws", "rustls"] [[bench]] name = "write-camel-case" diff --git a/actix-http/README.md b/actix-http/README.md index bf0b7c824..14a7013db 100644 --- a/actix-http/README.md +++ b/actix-http/README.md @@ -25,7 +25,7 @@ use actix_http::{HttpService, Response}; use actix_server::Server; use futures_util::future; use http::header::HeaderValue; -use log::info; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { diff --git a/actix-http/examples/echo.rs b/actix-http/examples/echo.rs index 58de64530..ae6f00cce 100644 --- a/actix-http/examples/echo.rs +++ b/actix-http/examples/echo.rs @@ -5,6 +5,7 @@ use actix_server::Server; use bytes::BytesMut; use futures_util::StreamExt as _; use http::header::HeaderValue; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -22,7 +23,7 @@ async fn main() -> io::Result<()> { body.extend_from_slice(&item?); } - log::info!("request body: {:?}", body); + info!("request body: {:?}", body); let res = Response::build(StatusCode::OK) .insert_header(("x-head", HeaderValue::from_static("dummy value!"))) diff --git a/actix-http/examples/hello-world.rs b/actix-http/examples/hello-world.rs index 1a83d4d9c..c749cdd00 100644 --- a/actix-http/examples/hello-world.rs +++ b/actix-http/examples/hello-world.rs @@ -1,9 +1,8 @@ use std::{convert::Infallible, io, time::Duration}; -use actix_http::{ - header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode, -}; +use actix_http::{header::HeaderValue, HttpService, Request, Response, StatusCode}; use actix_server::Server; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -18,12 +17,12 @@ async fn main() -> io::Result<()> { ext.insert(42u32); }) .finish(|req: Request| async move { - log::info!("{:?}", req); + info!("{:?}", req); let mut res = Response::build(StatusCode::OK); res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); - let forty_two = req.extensions().get::().unwrap().to_string(); + let forty_two = req.conn_data::().unwrap().to_string(); res.insert_header(( "x-forty-two", HeaderValue::from_str(&forty_two).unwrap(), diff --git a/actix-http/examples/streaming-error.rs b/actix-http/examples/streaming-error.rs index 3988cbac2..8c8a249cb 100644 --- a/actix-http/examples/streaming-error.rs +++ b/actix-http/examples/streaming-error.rs @@ -12,6 +12,7 @@ use actix_http::{body::BodyStream, HttpService, Response}; use actix_server::Server; use async_stream::stream; use bytes::Bytes; +use tracing::info; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -21,7 +22,7 @@ async fn main() -> io::Result<()> { .bind("streaming-error", ("127.0.0.1", 8080), || { HttpService::build() .finish(|req| async move { - log::info!("{:?}", req); + info!("{:?}", req); let res = Response::ok(); Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! { diff --git a/actix-http/examples/ws.rs b/actix-http/examples/ws.rs index d70e43314..c4f0503cd 100644 --- a/actix-http/examples/ws.rs +++ b/actix-http/examples/ws.rs @@ -17,6 +17,7 @@ use actix_server::Server; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; use futures_core::{ready, Stream}; +use tracing::{info, trace}; #[actix_rt::main] async fn main() -> io::Result<()> { @@ -34,13 +35,13 @@ async fn main() -> io::Result<()> { } async fn handler(req: Request) -> Result>, Error> { - log::info!("handshaking"); + info!("handshaking"); let mut res = ws::handshake(req.head())?; // handshake will always fail under HTTP/2 - log::info!("responding"); - Ok(res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))?) + info!("responding"); + res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new()))) } struct Heartbeat { @@ -61,7 +62,7 @@ impl Stream for Heartbeat { type Item = Result; fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { - log::trace!("poll"); + trace!("poll"); ready!(self.as_mut().interval.poll_tick(cx)); diff --git a/actix-http/src/encoding/encoder.rs b/actix-http/src/encoding/encoder.rs index 0c81ffe1b..0bbb1c106 100644 --- a/actix-http/src/encoding/encoder.rs +++ b/actix-http/src/encoding/encoder.rs @@ -17,6 +17,7 @@ use pin_project_lite::pin_project; #[cfg(feature = "compress-gzip")] use flate2::write::{GzEncoder, ZlibEncoder}; +use tracing::trace; #[cfg(feature = "compress-zstd")] use zstd::stream::write::Encoder as ZstdEncoder; @@ -356,7 +357,7 @@ impl ContentEncoder { ContentEncoder::Brotli(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding br encoding: {}", err); + trace!("Error decoding br encoding: {}", err); Err(err) } }, @@ -365,7 +366,7 @@ impl ContentEncoder { ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding gzip encoding: {}", err); + trace!("Error decoding gzip encoding: {}", err); Err(err) } }, @@ -374,7 +375,7 @@ impl ContentEncoder { ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding deflate encoding: {}", err); + trace!("Error decoding deflate encoding: {}", err); Err(err) } }, @@ -383,7 +384,7 @@ impl ContentEncoder { ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) { Ok(_) => Ok(()), Err(err) => { - log::trace!("Error decoding ztsd encoding: {}", err); + trace!("Error decoding ztsd encoding: {}", err); Err(err) } }, diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index 7d0532fcd..c1dd4b283 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -1,6 +1,7 @@ use std::{io, task::Poll}; use bytes::{Buf as _, Bytes, BytesMut}; +use tracing::{debug, trace}; macro_rules! byte ( ($rdr:ident) => ({ @@ -76,7 +77,7 @@ impl ChunkedState { Poll::Ready(Ok(ChunkedState::Size)) } None => { - log::debug!("chunk size would overflow u64"); + debug!("chunk size would overflow u64"); Poll::Ready(Err(io::Error::new( io::ErrorKind::InvalidInput, "Invalid chunk size line: Size is too big", @@ -124,7 +125,7 @@ impl ChunkedState { rem: &mut u64, buf: &mut Option, ) -> Poll> { - log::trace!("Chunked read, remaining={:?}", rem); + trace!("Chunked read, remaining={:?}", rem); let len = rdr.len() as u64; if len == 0 { diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 0e444756e..a9443997e 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -6,7 +6,7 @@ use http::{ header::{self, HeaderName, HeaderValue}, Method, StatusCode, Uri, Version, }; -use log::{debug, error, trace}; +use tracing::{debug, error, trace}; use super::chunked::ChunkedState; use crate::{error::ParseError, header::HeaderMap, ConnectionType, Request, ResponseHead}; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 648cf14d7..dea8a4beb 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -15,6 +15,7 @@ use bitflags::bitflags; use bytes::{Buf, BytesMut}; use futures_core::ready; use pin_project_lite::pin_project; +use tracing::{debug, error, trace}; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -336,7 +337,7 @@ where while written < len { match io.as_mut().poll_write(cx, &write_buf[written..])? { Poll::Ready(0) => { - log::error!("write zero; closing"); + error!("write zero; closing"); return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, ""))); } @@ -568,7 +569,7 @@ where } StateProj::ExpectCall { fut } => { - log::trace!(" calling expect service"); + trace!(" calling expect service"); match fut.poll(cx) { // expect resolved. write continue to buffer and set InnerDispatcher state @@ -697,12 +698,12 @@ where let mut this = self.as_mut().project(); if can_not_read { - log::debug!("cannot read request payload"); + debug!("cannot read request payload"); if let Some(sender) = &this.payload { // ...maybe handler does not want to read any more payload... if let PayloadStatus::Dropped = sender.need_read(cx) { - log::debug!("handler dropped payload early; attempt to clean connection"); + debug!("handler dropped payload early; attempt to clean connection"); // ...in which case poll request payload a few times loop { match this.codec.decode(this.read_buf)? { @@ -716,7 +717,7 @@ where // connection is in clean state for next request Message::Chunk(None) => { - log::debug!("connection successfully cleaned"); + debug!("connection successfully cleaned"); // reset dispatcher state let _ = this.payload.take(); @@ -737,7 +738,7 @@ where // not enough info to decide if connection is going to be clean or not None => { - log::error!( + error!( "handler did not read whole payload and dispatcher could not \ drain read buf; return 500 and close connection" ); @@ -813,7 +814,7 @@ where if let Some(ref mut payload) = this.payload { payload.feed_data(chunk); } else { - log::error!("Internal server error: unexpected payload chunk"); + error!("Internal server error: unexpected payload chunk"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -827,7 +828,7 @@ where if let Some(mut payload) = this.payload.take() { payload.feed_eof(); } else { - log::error!("Internal server error: unexpected eof"); + error!("Internal server error: unexpected eof"); this.flags.insert(Flags::READ_DISCONNECT); this.messages.push_back(DispatcherMessage::Error( Response::internal_server_error().drop_body(), @@ -844,7 +845,7 @@ where Ok(None) => break, Err(ParseError::Io(err)) => { - log::trace!("I/O error: {}", &err); + trace!("I/O error: {}", &err); self.as_mut().client_disconnected(); this = self.as_mut().project(); *this.error = Some(DispatchError::Io(err)); @@ -852,7 +853,7 @@ where } Err(ParseError::TooLarge) => { - log::trace!("request head was too big; returning 431 response"); + trace!("request head was too big; returning 431 response"); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::Overflow); @@ -872,7 +873,7 @@ where } Err(err) => { - log::trace!("parse error {}", &err); + trace!("parse error {}", &err); if let Some(mut payload) = this.payload.take() { payload.set_error(PayloadError::EncodingCorrupted); @@ -903,7 +904,7 @@ where if timer.as_mut().poll(cx).is_ready() { // timeout on first request (slow request) return 408 - log::trace!( + trace!( "timed out on slow request; \ replying with 408 and closing connection" ); @@ -949,7 +950,7 @@ where // keep-alive timer has timed out if timer.as_mut().poll(cx).is_ready() { // no tasks at hand - log::trace!("timer timed out; closing connection"); + trace!("timer timed out; closing connection"); this.flags.insert(Flags::SHUTDOWN); if let Some(deadline) = this.config.client_disconnect_deadline() { @@ -979,7 +980,7 @@ where // timed-out during shutdown; drop connection if timer.as_mut().poll(cx).is_ready() { - log::trace!("timed-out during shutdown"); + trace!("timed-out during shutdown"); return Err(DispatchError::DisconnectTimeout); } } @@ -1138,12 +1139,12 @@ where match this.inner.project() { DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { - log::error!("Upgrade handler error: {}", err); + error!("Upgrade handler error: {}", err); DispatchError::Upgrade }), DispatcherStateProj::Normal { mut inner } => { - log::trace!("start flags: {:?}", &inner.flags); + trace!("start flags: {:?}", &inner.flags); trace_timer_states( "start", @@ -1250,7 +1251,7 @@ where // client is gone if inner.flags.contains(Flags::WRITE_DISCONNECT) { - log::trace!("client is gone; disconnecting"); + trace!("client is gone; disconnecting"); return Poll::Ready(Ok(())); } @@ -1259,14 +1260,14 @@ where // read half is closed; we do not process any responses if inner_p.flags.contains(Flags::READ_DISCONNECT) && state_is_none { - log::trace!("read half closed; start shutdown"); + trace!("read half closed; start shutdown"); inner_p.flags.insert(Flags::SHUTDOWN); } // keep-alive and stream errors if state_is_none && inner_p.write_buf.is_empty() { if let Some(err) = inner_p.error.take() { - log::error!("stream error: {}", &err); + error!("stream error: {}", &err); return Poll::Ready(Err(err)); } @@ -1295,7 +1296,7 @@ where Poll::Pending }; - log::trace!("end flags: {:?}", &inner.flags); + trace!("end flags: {:?}", &inner.flags); poll } @@ -1310,17 +1311,17 @@ fn trace_timer_states( ka_timer: &TimerState, shutdown_timer: &TimerState, ) { - log::trace!("{} timers:", label); + trace!("{} timers:", label); if head_timer.is_enabled() { - log::trace!(" head {}", &head_timer); + trace!(" head {}", &head_timer); } if ka_timer.is_enabled() { - log::trace!(" keep-alive {}", &ka_timer); + trace!(" keep-alive {}", &ka_timer); } if shutdown_timer.is_enabled() { - log::trace!(" shutdown {}", &shutdown_timer); + trace!(" shutdown {}", &shutdown_timer); } } diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index 43b7919a7..a791ea8c3 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -13,6 +13,7 @@ use actix_service::{ }; use actix_utils::future::ready; use futures_core::future::LocalBoxFuture; +use tracing::error; use crate::{ body::{BoxBody, MessageBody}, @@ -305,13 +306,13 @@ where Box::pin(async move { let expect = expect .await - .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; + .map_err(|e| error!("Init http expect service error: {:?}", e))?; let upgrade = match upgrade { Some(upgrade) => { let upgrade = upgrade .await - .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; + .map_err(|e| error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -319,7 +320,7 @@ where let service = service .await - .map_err(|e| log::error!("Init http service error: {:?}", e))?; + .map_err(|e| error!("Init http service error: {:?}", e))?; Ok(H1ServiceHandler::new( cfg, @@ -357,7 +358,7 @@ where fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self._poll_ready(cx).map_err(|err| { - log::error!("HTTP/1 service readiness error: {:?}", err); + error!("HTTP/1 service readiness error: {:?}", err); DispatchError::Service(err) }) } diff --git a/actix-http/src/h1/timer.rs b/actix-http/src/h1/timer.rs index bb69fdb80..0ea70a319 100644 --- a/actix-http/src/h1/timer.rs +++ b/actix-http/src/h1/timer.rs @@ -1,6 +1,7 @@ use std::{fmt, future::Future, pin::Pin, task::Context}; use actix_rt::time::{Instant, Sleep}; +use tracing::trace; #[derive(Debug)] pub(super) enum TimerState { @@ -24,7 +25,7 @@ impl TimerState { pub(super) fn set(&mut self, timer: Sleep, line: u32) { if matches!(self, Self::Disabled) { - log::trace!("setting disabled timer from line {}", line); + trace!("setting disabled timer from line {}", line); } *self = Self::Active { @@ -39,11 +40,11 @@ impl TimerState { pub(super) fn clear(&mut self, line: u32) { if matches!(self, Self::Disabled) { - log::trace!("trying to clear a disabled timer from line {}", line); + trace!("trying to clear a disabled timer from line {}", line); } if matches!(self, Self::Inactive) { - log::trace!("trying to clear an inactive timer from line {}", line); + trace!("trying to clear an inactive timer from line {}", line); } *self = Self::Inactive; diff --git a/actix-http/src/h2/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index ce1be537f..85516cccc 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -19,8 +19,8 @@ use h2::{ server::{Connection, SendResponse}, Ping, PingPong, }; -use log::{error, trace}; use pin_project_lite::pin_project; +use tracing::{error, trace, warn}; use crate::{ body::{BodySize, BoxBody, MessageBody}, @@ -143,7 +143,7 @@ where DispatchError::SendResponse(err) => { trace!("Error sending HTTP/2 response: {:?}", err) } - DispatchError::SendData(err) => log::warn!("{:?}", err), + DispatchError::SendData(err) => warn!("{:?}", err), DispatchError::ResponseBody(err) => { error!("Response payload stream error: {:?}", err) } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 653982d37..e526918c7 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -14,7 +14,7 @@ use actix_service::{ }; use actix_utils::future::ready; use futures_core::{future::LocalBoxFuture, ready}; -use log::error; +use tracing::{error, trace}; use crate::{ body::{BoxBody, MessageBody}, @@ -355,7 +355,7 @@ where } Err(err) => { - log::trace!("H2 handshake error: {}", err); + trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } }, diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index b220e55a4..f4fe625a3 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -15,6 +15,7 @@ use actix_service::{ }; use futures_core::{future::LocalBoxFuture, ready}; use pin_project_lite::pin_project; +use tracing::error; use crate::{ body::{BoxBody, MessageBody}, @@ -369,13 +370,13 @@ where Box::pin(async move { let expect = expect .await - .map_err(|e| log::error!("Init http expect service error: {:?}", e))?; + .map_err(|e| error!("Init http expect service error: {:?}", e))?; let upgrade = match upgrade { Some(upgrade) => { let upgrade = upgrade .await - .map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; + .map_err(|e| error!("Init http upgrade service error: {:?}", e))?; Some(upgrade) } None => None, @@ -383,7 +384,7 @@ where let service = service .await - .map_err(|e| log::error!("Init http service error: {:?}", e))?; + .map_err(|e| error!("Init http service error: {:?}", e))?; Ok(HttpServiceHandler::new( cfg, @@ -490,7 +491,7 @@ where fn poll_ready(&self, cx: &mut Context<'_>) -> Poll> { self._poll_ready(cx).map_err(|err| { - log::error!("HTTP service readiness error: {:?}", err); + error!("HTTP service readiness error: {:?}", err); DispatchError::Service(err) }) } @@ -666,7 +667,7 @@ where self.poll(cx) } Err(err) => { - log::trace!("H2 handshake error: {}", err); + tracing::trace!("H2 handshake error: {}", err); Poll::Ready(Err(err)) } } diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 6e7aa7c11..3aa325d6a 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -2,6 +2,7 @@ use actix_codec::{Decoder, Encoder}; use bitflags::bitflags; use bytes::{Bytes, BytesMut}; use bytestring::ByteString; +use tracing::error; use super::{ frame::Parser, @@ -253,7 +254,7 @@ impl Decoder for Codec { } } _ => { - log::error!("Unfinished fragment {:?}", opcode); + error!("Unfinished fragment {:?}", opcode); Err(ProtocolError::ContinuationFragment(opcode)) } }; diff --git a/actix-http/src/ws/dispatcher.rs b/actix-http/src/ws/dispatcher.rs index 4c7470d37..2f6b2363b 100644 --- a/actix-http/src/ws/dispatcher.rs +++ b/actix-http/src/ws/dispatcher.rs @@ -73,8 +73,8 @@ mod inner { use actix_service::{IntoService, Service}; use futures_core::stream::Stream; use local_channel::mpsc; - use log::debug; use pin_project_lite::pin_project; + use tracing::debug; use actix_codec::{AsyncRead, AsyncWrite, Decoder, Encoder, Framed}; diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index 78cef1046..17e34e2ba 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -1,7 +1,7 @@ use std::convert::TryFrom; use bytes::{Buf, BufMut, BytesMut}; -use log::debug; +use tracing::debug; use super::{ mask::apply_mask, diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs index 4227f221d..01fe9dd3c 100644 --- a/actix-http/src/ws/proto.rs +++ b/actix-http/src/ws/proto.rs @@ -3,6 +3,8 @@ use std::{ fmt, }; +use tracing::error; + /// Operation codes defined in [RFC 6455 §11.8]. /// /// [RFC 6455]: https://datatracker.ietf.org/doc/html/rfc6455#section-11.8 @@ -58,7 +60,7 @@ impl From for u8 { Ping => 9, Pong => 10, Bad => { - log::error!("Attempted to convert invalid opcode to u8. This is a bug."); + error!("Attempted to convert invalid opcode to u8. This is a bug."); 8 // if this somehow happens, a close frame will help us tear down quickly } } diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs index 8e59ec65d..550375296 100644 --- a/actix-http/tests/test_rustls.rs +++ b/actix-http/tests/test_rustls.rs @@ -212,6 +212,7 @@ async fn h2_content_length() { let value = HeaderValue::from_static("0"); { + #[allow(clippy::single_element_loop)] for &i in &[0] { let req = srv .request(Method::HEAD, srv.surl(&format!("/{}", i))) @@ -226,6 +227,7 @@ async fn h2_content_length() { // assert_eq!(response.headers().get(&header), None); } + #[allow(clippy::single_element_loop)] for &i in &[1] { let req = srv .request(Method::GET, srv.surl(&format!("/{}", i))) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index bdcfbf48a..99812600c 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -367,9 +367,7 @@ where .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) - }) + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) } else { svc }; @@ -555,7 +553,7 @@ where if let Some(handler) = on_connect_fn.clone() { svc = svc - .on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext)); + .on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)); } let fac = factory() From fe5279c77ae8c39792e5865a1478343ebf39b7ec Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 03:14:14 +0000 Subject: [PATCH 22/30] use tracing in actix-router --- actix-router/Cargo.toml | 2 +- actix-router/src/resource.rs | 12 +++++------- 2 files changed, 6 insertions(+), 8 deletions(-) diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 502109114..6fcef125d 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -23,9 +23,9 @@ default = ["http"] bytestring = ">=0.1.5, <2" firestorm = "0.5" http = { version = "0.2.3", optional = true } -log = "0.4" regex = "1.5" serde = "1" +tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.3", features = ["html_reports"] } diff --git a/actix-router/src/resource.rs b/actix-router/src/resource.rs index c616b467a..3d121f369 100644 --- a/actix-router/src/resource.rs +++ b/actix-router/src/resource.rs @@ -7,6 +7,7 @@ use std::{ use firestorm::{profile_fn, profile_method, profile_section}; use regex::{escape, Regex, RegexSet}; +use tracing::error; use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath}; @@ -714,10 +715,7 @@ impl ResourceDef { if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { - log::error!( - "Dynamic path match but not all segments found: {}", - name - ); + error!("Dynamic path match but not all segments found: {}", name); return false; } } @@ -744,7 +742,7 @@ impl ResourceDef { if let Some(m) = captures.name(name) { segments[no] = PathItem::Segment(m.start() as u16, m.end() as u16); } else { - log::error!("Dynamic path match but not all segments found: {}", name); + error!("Dynamic path match but not all segments found: {}", name); return false; } } @@ -1038,7 +1036,7 @@ impl ResourceDef { // tail segments in prefixes have no defined semantics #[cfg(not(test))] - log::warn!( + tracing::warn!( "Prefix resources should not have tail segments. \ Use `ResourceDef::new` constructor. \ This may become a panic in the future." @@ -1053,7 +1051,7 @@ impl ResourceDef { // unnamed tail segment #[cfg(not(test))] - log::warn!( + tracing::warn!( "Tail segments must have names. \ Consider `.../{{tail}}*`. \ This may become a panic in the future." From 592b40f914c0fc6fba6c7011edd9111b0e1258dd Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 15:03:55 +0000 Subject: [PATCH 23/30] move io-uring tests to own job --- .github/workflows/ci.yml | 32 +++++++++++++++++++++++++------- 1 file changed, 25 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f41aa972f..7bb911f79 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -81,19 +81,37 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features + - name: Clear the cargo caches + run: | + cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean + cargo-cache + + io-uring: + name: io-uring tests + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install Rust + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + 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.3.0 + - name: tests (io-uring) - if: matrix.target.os == 'ubuntu-latest' timeout-minutes: 60 run: > sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - - - name: Clear the cargo caches - run: | - cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean - cargo-cache + && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features" rustdoc: name: doc tests From 478b33b8a30d261a8fffb3fa9e1f5963a0595571 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 16:00:15 +0000 Subject: [PATCH 24/30] remove nightly io-uring job --- .github/workflows/ci-post-merge.yml | 55 ++++++++++++----------------- 1 file changed, 23 insertions(+), 32 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d37b2c107..2857eb51e 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -78,15 +78,6 @@ jobs: cargo test --lib --tests -p=actix-multipart --all-features cargo test --lib --tests -p=actix-web-actors --all-features - - name: tests (io-uring) - if: matrix.target.os == 'ubuntu-latest' - timeout-minutes: 60 - run: > - sudo bash -c "ulimit -Sl 512 - && ulimit -Hl 512 - && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=${{ matrix.version }} cargo test --lib --tests -p=actix-files --all-features" - - name: Clear the cargo caches run: | cargo install cargo-cache --version 0.6.3 --no-default-features --features ci-autoclean @@ -126,32 +117,32 @@ jobs: with: { command: ci-check-all-feature-powerset-linux } # job currently (1st Feb 2022) segfaults - # coverage: - # name: coverage - # runs-on: ubuntu-latest - # steps: - # - uses: actions/checkout@v2 + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 - # - name: Install stable - # uses: actions-rs/toolchain@v1 - # with: - # toolchain: stable-x86_64-unknown-linux-gnu - # profile: minimal - # override: true + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + 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.2.0 + - name: Generate Cargo.lock + uses: actions-rs/cargo@v1 + with: { command: generate-lockfile } + - name: Cache Dependencies + uses: Swatinem/rust-cache@v1.2.0 - # - name: Generate coverage file - # run: | - # cargo install cargo-tarpaulin --vers "^0.13" - # cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - # - name: Upload to Codecov - # uses: codecov/codecov-action@v1 - # with: { file: cobertura.xml } + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } nextest: name: nextest From 7b27493e4c660f41dcf3cac8a9b0580acf8df4e2 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 10 Mar 2022 16:17:49 +0000 Subject: [PATCH 25/30] move coverage to own workflow --- .github/workflows/ci-post-merge.yml | 28 ---------------------- .github/workflows/coverage.yml | 36 +++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 28 deletions(-) create mode 100644 .github/workflows/coverage.yml diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 2857eb51e..9fce98f4c 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -116,34 +116,6 @@ jobs: uses: actions-rs/cargo@v1 with: { command: ci-check-all-feature-powerset-linux } - # job currently (1st Feb 2022) segfaults - coverage: - name: coverage - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v2 - - - name: Install stable - uses: actions-rs/toolchain@v1 - with: - toolchain: stable-x86_64-unknown-linux-gnu - 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.2.0 - - - name: Generate coverage file - run: | - cargo install cargo-tarpaulin --vers "^0.13" - cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose - - name: Upload to Codecov - uses: codecov/codecov-action@v1 - with: { file: cobertura.xml } - nextest: name: nextest runs-on: ubuntu-latest diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml new file mode 100644 index 000000000..137a413d0 --- /dev/null +++ b/.github/workflows/coverage.yml @@ -0,0 +1,36 @@ +# disabled because `cargo tarpaulin` currently segfaults + +name: Coverage + +on: + push: + branches: [master] + +jobs: + # job currently (1st Feb 2022) segfaults + coverage: + name: coverage + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + + - name: Install stable + uses: actions-rs/toolchain@v1 + with: + toolchain: stable-x86_64-unknown-linux-gnu + 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.2.0 + + - name: Generate coverage file + run: | + cargo install cargo-tarpaulin --vers "^0.13" + cargo tarpaulin --workspace --features=rustls,openssl --out Xml --verbose + - name: Upload to Codecov + uses: codecov/codecov-action@v1 + with: { file: cobertura.xml } From c58f287044c844b55a8767757c4f38d77b489a15 Mon Sep 17 00:00:00 2001 From: nikstur <61635709+nikstur@users.noreply.github.com> Date: Sun, 20 Mar 2022 22:36:19 +0100 Subject: [PATCH 26/30] Removed random superfluous whitespace (#2705) --- actix-web/src/app.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs index d2df72714..18749d346 100644 --- a/actix-web/src/app.rs +++ b/actix-web/src/app.rs @@ -308,7 +308,7 @@ where /// Registers an app-wide middleware. /// - /// Registers middleware, in the form of a middleware compo nen t (type), that runs during + /// Registers middleware, in the form of a middleware component (type), that runs during /// inbound and/or outbound processing in the request life-cycle (request -> response), /// modifying request/response as necessary, across all requests managed by the `App`. /// From 09cffc093cd755d09a40a14f073f49015aa6e7ad Mon Sep 17 00:00:00 2001 From: mellowagain Date: Tue, 22 Mar 2022 16:30:06 +0100 Subject: [PATCH 27/30] Bump zstd to 0.11 (#2694) Co-authored-by: Rob Ede --- actix-http/Cargo.toml | 2 +- actix-web/Cargo.toml | 2 +- awc/Cargo.toml | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 6d410e46f..cd5d3f379 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -94,7 +94,7 @@ actix-tls = { version = "3", default-features = false, optional = true } # compress-* brotli = { version = "3.3.3", optional = true } flate2 = { version = "1.0.13", optional = true } -zstd = { version = "0.10", optional = true } +zstd = { version = "0.11", optional = true } [dev-dependencies] actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 093c000b4..7793fd8be 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -118,7 +118,7 @@ static_assertions = "1" tls-openssl = { package = "openssl", version = "0.10.9" } tls-rustls = { package = "rustls", version = "0.20.0" } tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.10" +zstd = "0.11" [[test]] name = "test_server" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9dd29e4b7..ba0fc14e3 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -110,7 +110,7 @@ static_assertions = "1.1" rcgen = "0.8" rustls-pemfile = "0.2" tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] } -zstd = "0.10" +zstd = "0.11" [[example]] name = "client" From e942d3e3b101cde08ccaa30f21020144be5522f8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 26 Mar 2022 13:26:08 +0000 Subject: [PATCH 28/30] update migration guide --- actix-web/MIGRATION-4.0.md | 22 ++++++++++++++++++++++ actix-web/src/data.rs | 7 ++----- actix-web/src/middleware/authors-guide.md | 3 +++ 3 files changed, 27 insertions(+), 5 deletions(-) diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 7192d0bc6..fbeae0680 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -31,6 +31,7 @@ Headings marked with :warning: are **breaking behavioral changes**. They will pr - [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously) - [`#[actix_web::main]` and `#[tokio::main]`](#actix_webmain-and-tokiomain) - [`web::block`](#webblock) +- ## MSRV @@ -483,3 +484,24 @@ The `web::block` helper has changed return type from roughly `async fn(fn() -> R - let n: u32 = web::block(|| Ok(123)).await?; + let n: u32 = web::block(|| Ok(123)).await??; ``` + +## `HttpResponse` as a `ResponseError` + +The implementation of `ResponseError` for `HttpResponse` has been removed. + +It was common in v3 to use `HttpResponse` as an error type in fallible handlers. The problem is that `HttpResponse` contains no knowledge or reference to the source error. Being able to guarantee that an "error" response actually contains an error reference makes middleware and other parts of Actix Web more effective. + +The error response builders in the `error` module were available in v3 but are now the best method for simple error responses without requiring you to implement the trait on your own custom error types. These builders can receive simple strings and third party errors that can not implement the `ResponseError` trait. + +A few common patterns are affected by this change: + +```diff +- Err(HttpResponse::InternalServerError().finish()) ++ Err(error::ErrorInternalServerError("reason")) + +- Err(HttpResponse::InternalServerError().body(third_party_error.to_string())) ++ Err(error::ErrorInternalServerError(err)) + +- .map_err(|err| HttpResponse::InternalServerError().finish())? ++ .map_err(error::ErrorInternalServerError)? +``` diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs index ce7b1fee6..a689d13e0 100644 --- a/actix-web/src/data.rs +++ b/actix-web/src/data.rs @@ -5,10 +5,7 @@ use actix_utils::future::{err, ok, Ready}; use futures_core::future::LocalBoxFuture; use serde::Serialize; -use crate::{ - dev::Payload, error::ErrorInternalServerError, extract::FromRequest, request::HttpRequest, - Error, -}; +use crate::{dev::Payload, error, Error, FromRequest, HttpRequest}; /// Data factory. pub(crate) trait DataFactory { @@ -160,7 +157,7 @@ impl FromRequest for Data { req.match_name().unwrap_or_else(|| req.path()) ); - err(ErrorInternalServerError( + err(error::ErrorInternalServerError( "Requested application data is not configured correctly. \ View/enable debug logs for more details.", )) diff --git a/actix-web/src/middleware/authors-guide.md b/actix-web/src/middleware/authors-guide.md index 344523a1a..a8d1edea4 100644 --- a/actix-web/src/middleware/authors-guide.md +++ b/actix-web/src/middleware/authors-guide.md @@ -11,3 +11,6 @@ ## Error Propagation ## When To (Not) Use Middleware + +## Author's References +- `EitherBody` + when is middleware appropriate: https://discord.com/channels/771444961383153695/952016890723729428 From 40048a581158053ee89ced4c7cc21804b828b271 Mon Sep 17 00:00:00 2001 From: Ali MJ Al-Nasrawy Date: Mon, 28 Mar 2022 23:58:35 +0300 Subject: [PATCH 29/30] rework actix_router::Quoter (#2709) Co-authored-by: Rob Ede --- actix-router/Cargo.toml | 5 + actix-router/benches/quoter.rs | 52 +++++++ actix-router/src/de.rs | 2 +- actix-router/src/quoter.rs | 268 ++++++++++++--------------------- actix-router/src/url.rs | 2 +- 5 files changed, 157 insertions(+), 172 deletions(-) create mode 100644 actix-router/benches/quoter.rs diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index 6fcef125d..76f39f631 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -32,7 +32,12 @@ criterion = { version = "0.3", features = ["html_reports"] } firestorm = { version = "0.5", features = ["enable_system_time"] } http = "0.2.5" serde = { version = "1", features = ["derive"] } +percent-encoding = "2.1" [[bench]] name = "router" harness = false + +[[bench]] +name = "quoter" +harness = false diff --git a/actix-router/benches/quoter.rs b/actix-router/benches/quoter.rs new file mode 100644 index 000000000..c18f1620e --- /dev/null +++ b/actix-router/benches/quoter.rs @@ -0,0 +1,52 @@ +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +use std::borrow::Cow; + +fn compare_quoters(c: &mut Criterion) { + let mut group = c.benchmark_group("Compare Quoters"); + + let quoter = actix_router::Quoter::new(b"", b""); + let path_quoted = (0..=0x7f) + .map(|c| format!("%{:02X}", c)) + .collect::(); + let path_unquoted = ('\u{00}'..='\u{7f}').collect::(); + + group.bench_function("quoter_unquoted", |b| { + b.iter(|| { + for _ in 0..10 { + black_box(quoter.requote(path_unquoted.as_bytes())); + } + }); + }); + + group.bench_function("percent_encode_unquoted", |b| { + b.iter(|| { + for _ in 0..10 { + let decode = percent_encoding::percent_decode(path_unquoted.as_bytes()); + black_box(Into::>::into(decode)); + } + }); + }); + + group.bench_function("quoter_quoted", |b| { + b.iter(|| { + for _ in 0..10 { + black_box(quoter.requote(path_quoted.as_bytes())); + } + }); + }); + + group.bench_function("percent_encode_quoted", |b| { + b.iter(|| { + for _ in 0..10 { + let decode = percent_encoding::percent_decode(path_quoted.as_bytes()); + black_box(Into::>::into(decode)); + } + }); + }); + + group.finish(); +} + +criterion_group!(benches, compare_quoters); +criterion_main!(benches); diff --git a/actix-router/src/de.rs b/actix-router/src/de.rs index efafd08db..55fcdc912 100644 --- a/actix-router/src/de.rs +++ b/actix-router/src/de.rs @@ -7,7 +7,7 @@ use crate::path::{Path, PathIter}; use crate::{Quoter, ResourcePath}; thread_local! { - static FULL_QUOTER: Quoter = Quoter::new(b"+/%", b""); + static FULL_QUOTER: Quoter = Quoter::new(b"", b""); } macro_rules! unsupported_type { diff --git a/actix-router/src/quoter.rs b/actix-router/src/quoter.rs index 8a1e99e1d..6c929d3ac 100644 --- a/actix-router/src/quoter.rs +++ b/actix-router/src/quoter.rs @@ -1,132 +1,89 @@ -#[allow(dead_code)] -const GEN_DELIMS: &[u8] = b":/?#[]@"; - -#[allow(dead_code)] -const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,"; - -#[allow(dead_code)] -const SUB_DELIMS: &[u8] = b"!$'()*,+?=;"; - -#[allow(dead_code)] -const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;"; - -#[allow(dead_code)] -const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~"; - -const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz - ABCDEFGHIJKLMNOPQRSTUVWXYZ - 1234567890 - -._~ - !$'()*,"; - -const QS: &[u8] = b"+&=;b"; - -/// A quoter +/// Partial percent-decoding. +/// +/// Performs percent-decoding on a slice but can selectively skip decoding certain sequences. +/// +/// # Examples +/// ``` +/// # use actix_router::Quoter; +/// // + is set as a protected character and will not be decoded... +/// let q = Quoter::new(&[], b"+"); +/// +/// // ...but the other encoded characters (like the hyphen below) will. +/// assert_eq!(q.requote(b"/a%2Db%2Bc").unwrap(), b"/a-b%2Bc"); +/// ``` pub struct Quoter { - /// Simple bit-map of safe values in the 0-127 ASCII range. - safe_table: [u8; 16], - /// Simple bit-map of protected values in the 0-127 ASCII range. - protected_table: [u8; 16], + protected_table: AsciiBitmap, } impl Quoter { - pub fn new(safe: &[u8], protected: &[u8]) -> Quoter { - let mut quoter = Quoter { - safe_table: [0; 16], - protected_table: [0; 16], - }; - - // prepare safe table - for ch in 0..128 { - if ALLOWED.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - - if QS.contains(&ch) { - set_bit(&mut quoter.safe_table, ch); - } - } - - for &ch in safe { - set_bit(&mut quoter.safe_table, ch) - } + /// Constructs a new `Quoter` instance given a set of protected ASCII bytes. + /// + /// The first argument is ignored but is kept for backward compatibility. + /// + /// # Panics + /// Panics if any of the `protected` bytes are not in the 0-127 ASCII range. + pub fn new(_: &[u8], protected: &[u8]) -> Quoter { + let mut protected_table = AsciiBitmap::default(); // prepare protected table for &ch in protected { - set_bit(&mut quoter.safe_table, ch); - set_bit(&mut quoter.protected_table, ch); + protected_table.set_bit(ch); } - quoter + Quoter { protected_table } } - /// Decodes safe percent-encoded sequences from `val`. - /// - /// Returns `None` when no modification to the original byte string was required. - /// - /// Non-ASCII bytes are accepted as valid input. - /// - /// Behavior for invalid/incomplete percent-encoding sequences is unspecified and may include - /// removing the invalid sequence from the output or passing it as-is. - pub fn requote(&self, val: &[u8]) -> Option> { - let mut has_pct = 0; - let mut pct = [b'%', 0, 0]; - let mut idx = 0; - let mut cloned: Option> = None; - - let len = val.len(); - - while idx < len { - let ch = val[idx]; - - if has_pct != 0 { - pct[has_pct] = val[idx]; - has_pct += 1; - - if has_pct == 3 { - has_pct = 0; - let buf = cloned.as_mut().unwrap(); - - if let Some(ch) = hex_pair_to_char(pct[1], pct[2]) { - if ch < 128 { - if bit_at(&self.protected_table, ch) { - buf.extend_from_slice(&pct); - idx += 1; - continue; - } - - if bit_at(&self.safe_table, ch) { - buf.push(ch); - idx += 1; - continue; - } - } - - buf.push(ch); - } else { - buf.extend_from_slice(&pct[..]); - } + /// Decodes the next escape sequence, if any, and advances `val`. + #[inline(always)] + fn decode_next<'a>(&self, val: &mut &'a [u8]) -> Option<(&'a [u8], u8)> { + for i in 0..val.len() { + if let (prev, [b'%', p1, p2, rem @ ..]) = val.split_at(i) { + if let Some(ch) = hex_pair_to_char(*p1, *p2) + // ignore protected ascii bytes + .filter(|&ch| !(ch < 128 && self.protected_table.bit_at(ch))) + { + *val = rem; + return Some((prev, ch)); } - } else if ch == b'%' { - has_pct = 1; - - if cloned.is_none() { - let mut c = Vec::with_capacity(len); - c.extend_from_slice(&val[..idx]); - cloned = Some(c); - } - } else if let Some(ref mut cloned) = cloned { - cloned.push(ch) } - - idx += 1; } - cloned + None + } + + /// Partially percent-decodes the given bytes. + /// + /// Escape sequences of the protected set are *not* decoded. + /// + /// Returns `None` when no modification to the original bytes was required. + /// + /// Invalid/incomplete percent-encoding sequences are passed unmodified. + pub fn requote(&self, val: &[u8]) -> Option> { + let mut remaining = val; + + // early return indicates that no percent-encoded sequences exist and we can skip allocation + let (pre, decoded_char) = self.decode_next(&mut remaining)?; + + // decoded output will always be shorter than the input + let mut decoded = Vec::::with_capacity(val.len()); + + // push first segment and decoded char + decoded.extend_from_slice(pre); + decoded.push(decoded_char); + + // decode and push rest of segments and decoded chars + while let Some((prev, ch)) = self.decode_next(&mut remaining) { + // this ugly conditional achieves +50% perf in cases where this is a tight loop. + if !prev.is_empty() { + decoded.extend_from_slice(prev); + } + decoded.push(ch); + } + + decoded.extend_from_slice(remaining); + + Some(decoded) } pub(crate) fn requote_str_lossy(&self, val: &str) -> Option { @@ -135,24 +92,6 @@ impl Quoter { } } -/// Converts an ASCII character in the hex-encoded set (`0-9`, `A-F`, `a-f`) to its integer -/// representation from `0x0`–`0xF`. -/// -/// - `0x30 ('0') => 0x0` -/// - `0x39 ('9') => 0x9` -/// - `0x41 ('a') => 0xA` -/// - `0x61 ('A') => 0xA` -/// - `0x46 ('f') => 0xF` -/// - `0x66 ('F') => 0xF` -fn from_ascii_hex(v: u8) -> Option { - match v { - b'0'..=b'9' => Some(v - 0x30), // ord('0') == 0x30 - b'A'..=b'F' => Some(v - 0x41 + 10), // ord('A') == 0x41 - b'a'..=b'f' => Some(v - 0x61 + 10), // ord('a') == 0x61 - _ => None, - } -} - /// Decode a ASCII hex-encoded pair to an integer. /// /// Returns `None` if either portion of the decoded pair does not evaluate to a valid hex value. @@ -160,64 +99,52 @@ fn from_ascii_hex(v: u8) -> Option { /// - `0x33 ('3'), 0x30 ('0') => 0x30 ('0')` /// - `0x34 ('4'), 0x31 ('1') => 0x41 ('A')` /// - `0x36 ('6'), 0x31 ('1') => 0x61 ('a')` +#[inline(always)] fn hex_pair_to_char(d1: u8, d2: u8) -> Option { - let (d_high, d_low) = (from_ascii_hex(d1)?, from_ascii_hex(d2)?); + let d_high = char::from(d1).to_digit(16)?; + let d_low = char::from(d2).to_digit(16)?; // left shift high nibble by 4 bits - Some(d_high << 4 | d_low) + Some((d_high as u8) << 4 | (d_low as u8)) } -/// Sets bit in given bit-map to 1=true. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn set_bit(array: &mut [u8], ch: u8) { - array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) +#[derive(Debug, Default, Clone)] +struct AsciiBitmap { + array: [u8; 16], } -/// Returns true if bit to true in given bit-map. -/// -/// # Panics -/// Panics if `ch` index is out of bounds. -fn bit_at(array: &[u8], ch: u8) -> bool { - array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 +impl AsciiBitmap { + /// Sets bit in given bit-map to 1=true. + /// + /// # Panics + /// Panics if `ch` index is out of bounds. + fn set_bit(&mut self, ch: u8) { + self.array[(ch >> 3) as usize] |= 0b1 << (ch & 0b111) + } + + /// Returns true if bit to true in given bit-map. + /// + /// # Panics + /// Panics if `ch` index is out of bounds. + fn bit_at(&self, ch: u8) -> bool { + self.array[(ch >> 3) as usize] & (0b1 << (ch & 0b111)) != 0 + } } #[cfg(test)] mod tests { use super::*; - #[test] - fn hex_encoding() { - let hex = b"0123456789abcdefABCDEF"; - - for i in 0..256 { - let c = i as u8; - if hex.contains(&c) { - assert!(from_ascii_hex(c).is_some()) - } else { - assert!(from_ascii_hex(c).is_none()) - } - } - - let expected = [ - 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 10, 11, 12, 13, 14, 15, - ]; - for i in 0..hex.len() { - assert_eq!(from_ascii_hex(hex[i]).unwrap(), expected[i]); - } - } - #[test] fn custom_quoter() { let q = Quoter::new(b"", b"+"); assert_eq!(q.requote(b"/a%25c").unwrap(), b"/a%c"); - assert_eq!(q.requote(b"/a%2Bc").unwrap(), b"/a%2Bc"); + assert_eq!(q.requote(b"/a%2Bc"), None); let q = Quoter::new(b"%+", b"/"); assert_eq!(q.requote(b"/a%25b%2Bc").unwrap(), b"/a%b+c"); - assert_eq!(q.requote(b"/a%2fb").unwrap(), b"/a%2fb"); - assert_eq!(q.requote(b"/a%2Fb").unwrap(), b"/a%2Fb"); + assert_eq!(q.requote(b"/a%2fb"), None); + assert_eq!(q.requote(b"/a%2Fb"), None); assert_eq!(q.requote(b"/a%0Ab").unwrap(), b"/a\nb"); assert_eq!(q.requote(b"/a%FE\xffb").unwrap(), b"/a\xfe\xffb"); assert_eq!(q.requote(b"/a\xfe\xffb"), None); @@ -233,7 +160,8 @@ mod tests { #[test] fn invalid_sequences() { let q = Quoter::new(b"%+", b"/"); - assert_eq!(q.requote(b"/a%2x%2X%%").unwrap(), b"/a%2x%2X"); + assert_eq!(q.requote(b"/a%2x%2X%%"), None); + assert_eq!(q.requote(b"/a%20%2X%%").unwrap(), b"/a %2X%%"); } #[test] diff --git a/actix-router/src/url.rs b/actix-router/src/url.rs index e7dda3fca..8ac033861 100644 --- a/actix-router/src/url.rs +++ b/actix-router/src/url.rs @@ -3,7 +3,7 @@ use crate::ResourcePath; use crate::Quoter; thread_local! { - static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+"); + static DEFAULT_QUOTER: Quoter = Quoter::new(b"", b"%/+"); } #[derive(Debug, Clone, Default)] From 2fed9785972f964ecc1a27afca8705c3087aa17c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 28 Mar 2022 22:44:32 +0100 Subject: [PATCH 30/30] remove -http TestRequest doc test --- actix-http/src/test.rs | 24 +----------------------- actix-web/src/test/test_request.rs | 7 ++++--- 2 files changed, 5 insertions(+), 26 deletions(-) diff --git a/actix-http/src/test.rs b/actix-http/src/test.rs index 6212c19d1..3815e64c6 100644 --- a/actix-http/src/test.rs +++ b/actix-http/src/test.rs @@ -19,29 +19,7 @@ use crate::{ Request, }; -/// Test `Request` builder -/// -/// ```ignore -/// # use http::{header, StatusCode}; -/// # use actix_web::*; -/// use actix_web::test::TestRequest; -/// -/// fn index(req: &HttpRequest) -> Response { -/// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { -/// Response::Ok().into() -/// } else { -/// Response::BadRequest().into() -/// } -/// } -/// -/// let resp = TestRequest::default().insert_header("content-type", "text/plain") -/// .run(&index) -/// .unwrap(); -/// assert_eq!(resp.status(), StatusCode::OK); -/// -/// let resp = TestRequest::default().run(&index).unwrap(); -/// assert_eq!(resp.status(), StatusCode::BAD_REQUEST); -/// ``` +/// Test `Request` builder. pub struct TestRequest(Option); struct Inner { diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs index a368d873f..2b60fca71 100644 --- a/actix-web/src/test/test_request.rs +++ b/actix-web/src/test/test_request.rs @@ -33,7 +33,7 @@ use crate::cookie::{Cookie, CookieJar}; /// use actix_web::{test, HttpRequest, HttpResponse, HttpMessage}; /// use actix_web::http::{header, StatusCode}; /// -/// async fn index(req: HttpRequest) -> HttpResponse { +/// async fn handler(req: HttpRequest) -> HttpResponse { /// if let Some(hdr) = req.headers().get(header::CONTENT_TYPE) { /// HttpResponse::Ok().into() /// } else { @@ -45,10 +45,11 @@ use crate::cookie::{Cookie, CookieJar}; /// # // force rustdoc to display the correct thing and also compile check the test /// # async fn _test() {} /// async fn test_index() { -/// let req = test::TestRequest::default().insert_header(header::ContentType::plaintext()) +/// let req = test::TestRequest::default() +/// .insert_header(header::ContentType::plaintext()) /// .to_http_request(); /// -/// let resp = index(req).await; +/// let resp = handler(req).await; /// assert_eq!(resp.status(), StatusCode::OK); /// /// let req = test::TestRequest::default().to_http_request();