From 740d0c0c9dc256c1aee0bad6f514f8cd9d6443d9 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 26 Feb 2023 21:44:14 +0000 Subject: [PATCH 01/31] prepare actix-multipart-derive release 0.6.0 --- actix-multipart-derive/CHANGES.md | 5 +++++ actix-multipart-derive/Cargo.toml | 6 +++++- actix-multipart-derive/README.md | 16 +++++++++++++++- actix-multipart/Cargo.toml | 6 +----- 4 files changed, 26 insertions(+), 7 deletions(-) create mode 100644 actix-multipart-derive/CHANGES.md diff --git a/actix-multipart-derive/CHANGES.md b/actix-multipart-derive/CHANGES.md new file mode 100644 index 000000000..8dd7aa4d0 --- /dev/null +++ b/actix-multipart-derive/CHANGES.md @@ -0,0 +1,5 @@ +# Changes + +## 0.6.0 - 2023-02-26 + +- Add `MultipartForm` derive macro. diff --git a/actix-multipart-derive/Cargo.toml b/actix-multipart-derive/Cargo.toml index 4a30898b4..d2db040d4 100644 --- a/actix-multipart-derive/Cargo.toml +++ b/actix-multipart-derive/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart-derive" -version = "0.5.0" +version = "0.6.0" authors = ["Jacob Halsey "] description = "Multipart form derive macro for Actix Web" keywords = ["http", "web", "framework", "async", "futures"] @@ -9,6 +9,10 @@ repository = "https://github.com/actix/actix-web.git" license = "MIT OR Apache-2.0" edition = "2018" +[package.metadata.docs.rs] +rustdoc-args = ["--cfg", "docsrs"] +all-features = true + [lib] proc-macro = true diff --git a/actix-multipart-derive/README.md b/actix-multipart-derive/README.md index 95f80bc79..44f08c7bd 100644 --- a/actix-multipart-derive/README.md +++ b/actix-multipart-derive/README.md @@ -1,3 +1,17 @@ # actix-multipart-derive -> The derive macro implementation for actix-multipart. +> The derive macro implementation for actix-multipart-derive. + +[![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive) +[![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.5.0)](https://docs.rs/actix-multipart-derive/0.5.0) +![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) +![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg) +
+[![dependency status](https://deps.rs/crate/actix-multipart-derive/0.5.0/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.5.0) +[![Download](https://img.shields.io/crates/d/actix-multipart-derive.svg)](https://crates.io/crates/actix-multipart-derive) +[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) + +## Documentation & Resources + +- [API Documentation](https://docs.rs/actix-multipart-derive) +- Minimum Supported Rust Version (MSRV): 1.59 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index b63ab1f86..90670f11b 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -21,12 +21,8 @@ default = ["tempfile", "derive"] derive = ["actix-multipart-derive"] tempfile = ["tempfile-dep", "tokio/fs"] -[lib] -name = "actix_multipart" -path = "src/lib.rs" - [dependencies] -actix-multipart-derive = { version = "=0.5.0", optional = true } +actix-multipart-derive = { version = "=0.6.0", optional = true } actix-utils = "3" actix-web = { version = "4", default-features = false } From c4db9a1ae2a70867c976fe496ec549a6528e0994 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 26 Feb 2023 21:44:57 +0000 Subject: [PATCH 02/31] prepare actix-multipart release 0.6.0 --- actix-multipart-derive/Cargo.toml | 2 +- actix-multipart/CHANGES.md | 2 ++ actix-multipart/Cargo.toml | 2 +- actix-multipart/README.md | 4 ++-- 4 files changed, 6 insertions(+), 4 deletions(-) diff --git a/actix-multipart-derive/Cargo.toml b/actix-multipart-derive/Cargo.toml index d2db040d4..e0b78fa26 100644 --- a/actix-multipart-derive/Cargo.toml +++ b/actix-multipart-derive/Cargo.toml @@ -24,7 +24,7 @@ quote = "1" syn = "1" [dev-dependencies] -actix-multipart = "0.5" +actix-multipart = "0.6" actix-web = "4" rustversion = "1" trybuild = "1" diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index 3d93a4921..eb1ad6b02 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased - 2022-xx-xx +## 0.6.0 - 2023-02-26 + - Added `MultipartForm` typed data extractor. [#2883] [#2883]: https://github.com/actix/actix-web/pull/2883 diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml index 90670f11b..a36fbffc0 100644 --- a/actix-multipart/Cargo.toml +++ b/actix-multipart/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-multipart" -version = "0.5.0" +version = "0.6.0" authors = [ "Nikolay Kim ", "Jacob Halsey ", diff --git a/actix-multipart/README.md b/actix-multipart/README.md index 23c7aa4f5..c4101e1ce 100644 --- a/actix-multipart/README.md +++ b/actix-multipart/README.md @@ -3,11 +3,11 @@ > Multipart form support for Actix Web. [![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart) -[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.5.0)](https://docs.rs/actix-multipart/0.5.0) +[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.0)](https://docs.rs/actix-multipart/0.6.0) ![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.5.0/status.svg)](https://deps.rs/crate/actix-multipart/0.5.0) +[![dependency status](https://deps.rs/crate/actix-multipart/0.6.0/status.svg)](https://deps.rs/crate/actix-multipart/0.6.0) [![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) From d77bcb0b7cee83f40c097de94ab07f40a3b6c98d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 26 Feb 2023 21:45:36 +0000 Subject: [PATCH 03/31] update date in unreleased changelog sections --- actix-files/CHANGES.md | 2 +- actix-http-test/CHANGES.md | 2 +- actix-http/CHANGES.md | 2 +- actix-multipart/CHANGES.md | 2 +- actix-router/CHANGES.md | 2 +- actix-test/CHANGES.md | 2 +- actix-web-actors/CHANGES.md | 2 +- actix-web-codegen/CHANGES.md | 2 +- actix-web/CHANGES.md | 2 +- 9 files changed, 9 insertions(+), 9 deletions(-) diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index b4b2fd8c1..d64380a8d 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 0.6.3 - 2023-01-21 diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index e56883c19..ec31b6196 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 3.1.0 - 2023-01-21 diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 033cd994c..3a7af60af 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 3.3.0 - 2023-01-21 diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md index eb1ad6b02..4bb120c61 100644 --- a/actix-multipart/CHANGES.md +++ b/actix-multipart/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 0.6.0 - 2023-02-26 diff --git a/actix-router/CHANGES.md b/actix-router/CHANGES.md index b3a85d3fe..7ef9497dc 100644 --- a/actix-router/CHANGES.md +++ b/actix-router/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 0.5.1 - 2022-09-19 diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 28b772b38..47fea4173 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 0.1.1 - 2023-02-26 diff --git a/actix-web-actors/CHANGES.md b/actix-web-actors/CHANGES.md index 480470bee..ea19411b5 100644 --- a/actix-web-actors/CHANGES.md +++ b/actix-web-actors/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 4.2.0 - 2023-01-21 diff --git a/actix-web-codegen/CHANGES.md b/actix-web-codegen/CHANGES.md index 5bc510273..6e962a6ca 100644 --- a/actix-web-codegen/CHANGES.md +++ b/actix-web-codegen/CHANGES.md @@ -1,6 +1,6 @@ # Changes -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 4.2.0 - 2023-02-26 diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 1eb1f02c9..093c77891 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -1,6 +1,6 @@ # Changelog -## Unreleased - 2022-xx-xx +## Unreleased - 2023-xx-xx ## 4.3.1 - 2023-02-26 From 67efa4a4dbc8f7812a562d19fbb4e9cd5442df55 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 26 Feb 2023 21:55:25 +0000 Subject: [PATCH 04/31] migrate to doc_auto_cfg --- actix-files/src/lib.rs | 3 +++ actix-http-test/src/lib.rs | 1 + actix-http/src/builder.rs | 1 - actix-http/src/error.rs | 2 -- actix-http/src/h1/service.rs | 2 -- actix-http/src/h2/service.rs | 2 -- actix-http/src/lib.rs | 5 +---- actix-http/src/service.rs | 6 ------ actix-multipart-derive/src/lib.rs | 2 +- actix-multipart/src/form/mod.rs | 2 -- actix-multipart/src/lib.rs | 4 +++- actix-router/src/lib.rs | 1 + actix-test/src/lib.rs | 3 +++ actix-web-actors/src/lib.rs | 3 +++ actix-web-codegen/src/lib.rs | 3 +++ actix-web/src/lib.rs | 4 +--- actix-web/src/request.rs | 2 -- actix-web/src/server.rs | 5 ----- awc/src/lib.rs | 1 + 19 files changed, 21 insertions(+), 31 deletions(-) diff --git a/actix-files/src/lib.rs b/actix-files/src/lib.rs index 0fbe39a8e..bed8194c8 100644 --- a/actix-files/src/lib.rs +++ b/actix-files/src/lib.rs @@ -14,6 +14,9 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible, missing_docs, missing_debug_implementations)] #![allow(clippy::uninlined_format_args)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] use actix_service::boxed::{BoxService, BoxServiceFactory}; use actix_web::{ diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index a66f7b486..55224dc46 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index e2693acaf..916083a98 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -211,7 +211,6 @@ where /// Finish service configuration and create a service for the HTTP/2 protocol. #[cfg(feature = "http2")] - #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn h2(self, service: F) -> crate::h2::H2Service where F: IntoServiceFactory, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 2d443369d..41522a254 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -294,7 +294,6 @@ impl std::error::Error for PayloadError { PayloadError::Overflow => None, PayloadError::UnknownLength => None, #[cfg(feature = "http2")] - #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] PayloadError::Http2Payload(err) => Some(err), PayloadError::Io(err) => Some(err), } @@ -352,7 +351,6 @@ pub enum DispatchError { /// HTTP/2 error. #[display(fmt = "{}", _0)] #[cfg(feature = "http2")] - #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] H2(h2::Error), /// The first request did not complete within the specified timeout. diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index e4d90424d..a791ea8c3 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -134,7 +134,6 @@ mod openssl { U::InitError: fmt::Debug, { /// Create OpenSSL based service. - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -197,7 +196,6 @@ mod rustls { U::InitError: fmt::Debug, { /// Create Rustls based service. - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, config: ServerConfig, diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index 2a45fc1dc..e526918c7 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -117,7 +117,6 @@ mod openssl { B: MessageBody + 'static, { /// Create OpenSSL based service. - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -165,7 +164,6 @@ mod rustls { B: MessageBody + 'static, { /// Create Rustls based service. - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, mut config: ServerConfig, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 05f80eba4..8bf834f73 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -26,7 +26,7 @@ )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use ::http::{uri, uri::Uri}; pub use ::http::{Method, StatusCode, Version}; @@ -41,7 +41,6 @@ pub mod error; mod extensions; pub mod h1; #[cfg(feature = "http2")] -#[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub mod h2; pub mod header; mod helpers; @@ -56,7 +55,6 @@ mod responses; mod service; pub mod test; #[cfg(feature = "ws")] -#[cfg_attr(docsrs, doc(cfg(feature = "ws")))] pub mod ws; pub use self::builder::HttpServiceBuilder; @@ -74,7 +72,6 @@ pub use self::requests::{Request, RequestHead, RequestHeadType}; pub use self::responses::{Response, ResponseBuilder, ResponseHead}; pub use self::service::HttpService; #[cfg(any(feature = "openssl", feature = "rustls"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] pub use self::service::TlsAcceptorConfig; /// A major HTTP protocol version. diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 62128f3ec..22177b849 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -217,7 +217,6 @@ where /// Creates TCP stream service from HTTP service that automatically selects HTTP/1.x or HTTP/2 /// on plaintext connections. #[cfg(feature = "http2")] - #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] pub fn tcp_auto_h2c( self, ) -> impl ServiceFactory< @@ -253,7 +252,6 @@ where /// Configuration options used when accepting TLS connection. #[cfg(any(feature = "openssl", feature = "rustls"))] -#[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] #[derive(Debug, Default)] pub struct TlsAcceptorConfig { pub(crate) handshake_timeout: Option, @@ -309,7 +307,6 @@ mod openssl { U::InitError: fmt::Debug, { /// Create OpenSSL based service. - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -324,7 +321,6 @@ mod openssl { } /// Create OpenSSL based service with custom TLS acceptor configuration. - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl_with_config( self, acceptor: SslAcceptor, @@ -404,7 +400,6 @@ mod rustls { U::InitError: fmt::Debug, { /// Create Rustls based service. - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, config: ServerConfig, @@ -419,7 +414,6 @@ mod rustls { } /// Create Rustls based service with custom TLS acceptor configuration. - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls_with_config( self, mut config: ServerConfig, diff --git a/actix-multipart-derive/src/lib.rs b/actix-multipart-derive/src/lib.rs index 9b6ecbae6..2af023aec 100644 --- a/actix-multipart-derive/src/lib.rs +++ b/actix-multipart-derive/src/lib.rs @@ -6,7 +6,7 @@ #![warn(future_incompatible)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] use std::{collections::HashSet, convert::TryFrom as _}; diff --git a/actix-multipart/src/form/mod.rs b/actix-multipart/src/form/mod.rs index b0285d97e..711d4aeb6 100644 --- a/actix-multipart/src/form/mod.rs +++ b/actix-multipart/src/form/mod.rs @@ -16,12 +16,10 @@ use crate::{Field, Multipart, MultipartError}; pub mod bytes; pub mod json; -#[cfg_attr(docsrs, doc(cfg(feature = "tempfile")))] #[cfg(feature = "tempfile")] pub mod tempfile; pub mod text; -#[cfg_attr(docsrs, doc(cfg(feature = "derive")))] #[cfg(feature = "derive")] pub use actix_multipart_derive::MultipartForm; diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs index c8fba77d0..73e10c913 100644 --- a/actix-multipart/src/lib.rs +++ b/actix-multipart/src/lib.rs @@ -3,7 +3,9 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] #![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] // This allows us to use the actix_multipart_derive within this crate's tests #[cfg(test)] diff --git a/actix-router/src/lib.rs b/actix-router/src/lib.rs index 418dd432b..a02129495 100644 --- a/actix-router/src/lib.rs +++ b/actix-router/src/lib.rs @@ -5,6 +5,7 @@ #![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] mod de; mod path; diff --git a/actix-test/src/lib.rs b/actix-test/src/lib.rs index 18453b599..2beb64dca 100644 --- a/actix-test/src/lib.rs +++ b/actix-test/src/lib.rs @@ -28,6 +28,9 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] #[cfg(feature = "openssl")] extern crate tls_openssl as openssl; diff --git a/actix-web-actors/src/lib.rs b/actix-web-actors/src/lib.rs index 7a34048da..cf2eb3645 100644 --- a/actix-web-actors/src/lib.rs +++ b/actix-web-actors/src/lib.rs @@ -58,6 +58,9 @@ #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] #![allow(clippy::uninlined_format_args)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] mod context; pub mod ws; diff --git a/actix-web-codegen/src/lib.rs b/actix-web-codegen/src/lib.rs index 5d392be1d..8b68ea16b 100644 --- a/actix-web-codegen/src/lib.rs +++ b/actix-web-codegen/src/lib.rs @@ -75,6 +75,9 @@ #![recursion_limit = "512"] #![deny(rust_2018_idioms, nonstandard_style)] #![warn(future_incompatible)] +#![doc(html_logo_url = "https://actix.rs/img/logo.png")] +#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] use proc_macro::TokenStream; use quote::quote; diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs index 6a94976c5..57cdaea69 100644 --- a/actix-web/src/lib.rs +++ b/actix-web/src/lib.rs @@ -72,7 +72,7 @@ #![allow(clippy::uninlined_format_args)] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] -#![cfg_attr(docsrs, feature(doc_cfg))] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] mod app; mod app_service; @@ -119,14 +119,12 @@ pub use crate::types::Either; pub use actix_http::{body, HttpMessage}; #[cfg(feature = "cookies")] -#[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] #[doc(inline)] pub use cookie; macro_rules! codegen_reexport { ($name:ident) => { #[cfg(feature = "macros")] - #[cfg_attr(docsrs, doc(cfg(feature = "macros")))] pub use actix_web_codegen::$name; }; } diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index a99dcaa3e..16a947b65 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -311,7 +311,6 @@ impl HttpRequest { /// Load request cookies. #[cfg(feature = "cookies")] - #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookies(&self) -> Result>>, CookieParseError> { use actix_http::header::COOKIE; @@ -335,7 +334,6 @@ impl HttpRequest { /// Return request cookie. #[cfg(feature = "cookies")] - #[cfg_attr(docsrs, doc(cfg(feature = "cookies")))] pub fn cookie(&self, name: &str) -> Option> { if let Ok(cookies) = self.cookies() { for cookie in cookies.iter() { diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 3a8897f11..c87fea7f3 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -217,7 +217,6 @@ where /// /// By default handshake timeout is set to 3000 milliseconds. #[cfg(any(feature = "openssl", feature = "rustls"))] - #[cfg_attr(docsrs, doc(cfg(any(feature = "openssl", feature = "rustls"))))] pub fn tls_handshake_timeout(self, dur: Duration) -> Self { self.config .lock() @@ -382,7 +381,6 @@ where /// /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "rustls")] - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn bind_rustls( mut self, addrs: A, @@ -402,7 +400,6 @@ where /// /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "openssl")] - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn bind_openssl(mut self, addrs: A, builder: SslAcceptorBuilder) -> io::Result where A: net::ToSocketAddrs, @@ -469,7 +466,6 @@ where /// /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "rustls")] - #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn listen_rustls( self, lst: net::TcpListener, @@ -535,7 +531,6 @@ where /// /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "openssl")] - #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn listen_openssl( self, lst: net::TcpListener, diff --git a/awc/src/lib.rs b/awc/src/lib.rs index 42f029669..b06df6b7d 100644 --- a/awc/src/lib.rs +++ b/awc/src/lib.rs @@ -110,6 +110,7 @@ )] #![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")] +#![cfg_attr(docsrs, feature(doc_auto_cfg))] pub use actix_http::body; From d7c6774ad525821cdba2c6625399ba31d718ef06 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Mar 2023 08:22:22 +0000 Subject: [PATCH 05/31] add resource method helpers (#2978) --- actix-web/CHANGES.md | 2 ++ actix-web/src/resource.rs | 50 +++++++++++++++++++++++++++++++++++---- 2 files changed, 48 insertions(+), 4 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 093c77891..9a768a122 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased - 2023-xx-xx +- Add `Resource::{get, post, etc...}` methods for more concisely adding routes that don't need additional guards. + ## 4.3.1 - 2023-02-26 - Add support for custom methods with the `#[route]` macro. [#2969] diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs index 997036751..5d2c9706a 100644 --- a/actix-web/src/resource.rs +++ b/actix-web/src/resource.rs @@ -21,7 +21,7 @@ use crate::{ BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest, ServiceResponse, }, - Error, FromRequest, HttpResponse, Responder, + web, Error, FromRequest, HttpResponse, Responder, }; /// A collection of [`Route`]s that respond to the same path pattern. @@ -38,11 +38,13 @@ use crate::{ /// /// let app = App::new().service( /// web::resource("/") -/// .route(web::get().to(|| HttpResponse::Ok()))); +/// .get(|| HttpResponse::Ok()) +/// .post(|| async { "Hello World!" }) +/// ); /// ``` /// -/// If no matching route is found, [a 405 response is returned with an appropriate Allow header][RFC -/// 9110 §15.5.6]. This default behavior can be overridden using +/// If no matching route is found, an empty 405 response is returned which includes an +/// [appropriate Allow header][RFC 9110 §15.5.6]. This default behavior can be overridden using /// [`default_service()`](Self::default_service). /// /// [RFC 9110 §15.5.6]: https://www.rfc-editor.org/rfc/rfc9110.html#section-15.5.6 @@ -58,6 +60,7 @@ pub struct Resource { } impl Resource { + /// Constructs new resource that matches a `path` pattern. pub fn new(path: T) -> Resource { let fref = Rc::new(RefCell::new(None)); @@ -368,6 +371,45 @@ where } } +macro_rules! route_shortcut { + ($method_fn:ident, $method_upper:literal) => { + #[doc = concat!(" Adds a ", $method_upper, " route.")] + /// + /// Use [`route`](Self::route) if you need to add additional guards. + /// + /// # Examples + /// + /// ``` + /// # use actix_web::web; + /// web::resource("/") + #[doc = concat!(" .", stringify!($method_fn), "(|| async { \"Hello World!\" })")] + /// # ; + /// ``` + pub fn $method_fn(self, handler: F) -> Self + where + F: Handler, + Args: FromRequest + 'static, + F::Output: Responder + 'static, + { + self.route(web::$method_fn().to(handler)) + } + }; +} + +/// Concise routes for well-known HTTP methods. +impl Resource +where + T: ServiceFactory, +{ + route_shortcut!(get, "GET"); + route_shortcut!(post, "POST"); + route_shortcut!(put, "PUT"); + route_shortcut!(patch, "PATCH"); + route_shortcut!(delete, "DELETE"); + route_shortcut!(head, "HEAD"); + route_shortcut!(trace, "TRACE"); +} + impl HttpServiceFactory for Resource where T: ServiceFactory< From 20c7c07dc00236b2d74aaed8c79a805aec54f89e Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Mar 2023 16:21:13 +0000 Subject: [PATCH 06/31] fix http version req --- actix-http-test/Cargo.toml | 2 +- actix-http/CHANGES.md | 4 ++++ actix-http/Cargo.toml | 2 +- actix-router/Cargo.toml | 4 ++-- actix-web/Cargo.toml | 1 - awc/Cargo.toml | 2 +- 6 files changed, 9 insertions(+), 6 deletions(-) diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index cd5e87162..cb4754125 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -39,7 +39,7 @@ awc = { version = "3", default-features = false } bytes = "1" futures-core = { version = "0.3.17", default-features = false } -http = "0.2.5" +http = "0.2.7" log = "0.4" socket2 = "0.4" serde = "1.0" diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 3a7af60af..29cc22d35 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,10 @@ ## Unreleased - 2023-xx-xx +### Fixed + +- Use correct `http` version requirement to ensure support for const `HeaderName` definitions. + ## 3.3.0 - 2023-01-21 ### Added diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 108b2314a..77fcd4243 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -68,7 +68,7 @@ bytestring = "1" derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } -http = "0.2.5" +http = "0.2.7" httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index f3a5f15e4..f8efd5350 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,14 +21,14 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -http = { version = "0.2.5", optional = true } +http = { version = "0.2.7", optional = true } regex = "1.5" serde = "1" tracing = { version = "0.1.30", default-features = false, features = ["log"] } [dev-dependencies] criterion = { version = "0.4", features = ["html_reports"] } -http = "0.2.5" +http = "0.2.7" serde = { version = "1", features = ["derive"] } percent-encoding = "2.1" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index 664379c79..c7314422d 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -81,7 +81,6 @@ derive_more = "0.99.8" encoding_rs = "0.8" futures-core = { version = "0.3.17", default-features = false } futures-util = { version = "0.3.17", default-features = false } -http = "0.2.8" itoa = "1" language-tags = "0.3" log = "0.4" diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 8a75e28f7..8edc90fd1 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -69,7 +69,7 @@ derive_more = "0.99.5" futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] } h2 = "0.3.9" -http = "0.2.5" +http = "0.2.7" itoa = "1" log =" 0.4" mime = "0.3" From e0939a01fcb82bf948b3e4fe4cc9889c10518394 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 2 Mar 2023 17:09:26 +0000 Subject: [PATCH 07/31] prepare actix-http release 3.3.1 --- actix-http/CHANGES.md | 2 ++ actix-http/Cargo.toml | 2 +- actix-http/README.md | 4 ++-- 3 files changed, 5 insertions(+), 3 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 29cc22d35..831a0bcd0 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,8 @@ ## Unreleased - 2023-xx-xx +## 3.3.1 - 2023-03-02 + ### Fixed - Use correct `http` version requirement to ensure support for const `HeaderName` definitions. diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 77fcd4243..d2218f6de 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http" -version = "3.3.0" +version = "3.3.1" authors = [ "Nikolay Kim ", "Rob Ede ", diff --git a/actix-http/README.md b/actix-http/README.md index aa98f953f..f372096ff 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.3.0)](https://docs.rs/actix-http/3.3.0) +[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.3.1)](https://docs.rs/actix-http/3.3.1) ![Version](https://img.shields.io/badge/rustc-1.59+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
-[![dependency status](https://deps.rs/crate/actix-http/3.3.0/status.svg)](https://deps.rs/crate/actix-http/3.3.0) +[![dependency status](https://deps.rs/crate/actix-http/3.3.1/status.svg)](https://deps.rs/crate/actix-http/3.3.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 4c4024c949f219d9108f00c8cda759e82c6f81b3 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Mar 2023 22:14:58 +0000 Subject: [PATCH 08/31] fix minimal version specs for mime --- actix-files/Cargo.toml | 4 ++-- actix-http-test/Cargo.toml | 4 ++-- actix-http/Cargo.toml | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 4c29c95b2..6909c0ef2 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -32,11 +32,11 @@ derive_more = "0.99.5" futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } http-range = "0.1.4" log = "0.4" -mime = "0.3" +mime = "0.3.9" mime_guess = "2.0.1" percent-encoding = "2.1" pin-project-lite = "0.2.7" -v_htmlescape= "0.15" +v_htmlescape = "0.15.5" # experimental-io-uring [target.'cfg(target_os = "linux")'.dependencies] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index cb4754125..12739fbd4 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -42,8 +42,8 @@ futures-core = { version = "0.3.17", default-features = false } http = "0.2.7" log = "0.4" socket2 = "0.4" -serde = "1.0" -serde_json = "1.0" +serde = "1" +serde_json = "1" slab = "0.4" serde_urlencoded = "0.7" tls-openssl = { version = "0.10.9", package = "openssl", optional = true } diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index d2218f6de..07e412a2c 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -73,7 +73,7 @@ httparse = "1.5.1" httpdate = "1.0.1" itoa = "1" language-tags = "0.3" -mime = "0.3" +mime = "0.3.4" percent-encoding = "2.1" pin-project-lite = "0.2" smallvec = "1.6.1" From 3fc01c48878ee487b872551d9ac5898fc2031086 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Mar 2023 22:17:52 +0000 Subject: [PATCH 09/31] refactor server binding --- actix-web/src/server.rs | 67 +++++++++++++++++++---------------- actix-web/src/types/either.rs | 6 ++-- 2 files changed, 39 insertions(+), 34 deletions(-) diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index c87fea7f3..1fa279a65 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -338,7 +338,7 @@ where /// # ; Ok(()) } /// ``` pub fn bind(mut self, addrs: A) -> io::Result { - let sockets = self.bind2(addrs)?; + let sockets = bind_addrs(addrs, self.backlog)?; for lst in sockets { self = self.listen(lst)?; @@ -347,33 +347,6 @@ where Ok(self) } - fn bind2(&self, addrs: A) -> io::Result> { - let mut err = None; - let mut success = false; - let mut sockets = Vec::new(); - - for addr in addrs.to_socket_addrs()? { - match create_tcp_listener(addr, self.backlog) { - Ok(lst) => { - success = true; - sockets.push(lst); - } - Err(e) => err = Some(e), - } - } - - if success { - Ok(sockets) - } else if let Some(e) = err.take() { - Err(e) - } else { - Err(io::Error::new( - io::ErrorKind::Other, - "Can not bind to address.", - )) - } - } - /// Resolves socket address(es) and binds server to created listener(s) for TLS connections /// using Rustls. /// @@ -386,7 +359,7 @@ where addrs: A, config: RustlsServerConfig, ) -> io::Result { - let sockets = self.bind2(addrs)?; + let sockets = bind_addrs(addrs, self.backlog)?; for lst in sockets { self = self.listen_rustls_inner(lst, config.clone())?; } @@ -404,7 +377,7 @@ where where A: net::ToSocketAddrs, { - let sockets = self.bind2(addrs)?; + let sockets = bind_addrs(addrs, self.backlog)?; let acceptor = openssl_acceptor(builder)?; for lst in sockets { @@ -719,6 +692,38 @@ where } } +/// Bind TCP listeners to socket addresses resolved from `addrs` with options. +fn bind_addrs( + addrs: impl net::ToSocketAddrs, + backlog: u32, +) -> io::Result> { + let mut err = None; + let mut success = false; + let mut sockets = Vec::new(); + + for addr in addrs.to_socket_addrs()? { + match create_tcp_listener(addr, backlog) { + Ok(lst) => { + success = true; + sockets.push(lst); + } + Err(e) => err = Some(e), + } + } + + if success { + Ok(sockets) + } else if let Some(err) = err.take() { + Err(err) + } else { + Err(io::Error::new( + io::ErrorKind::Other, + "Can not bind to address.", + )) + } +} + +/// Creates a TCP listener from socket address and options. fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result { use socket2::{Domain, Protocol, Socket, Type}; let domain = Domain::for_address(addr); @@ -731,7 +736,7 @@ fn create_tcp_listener(addr: net::SocketAddr, backlog: u32) -> io::Result io::Result { builder.set_alpn_select_callback(|_, protocols| { diff --git a/actix-web/src/types/either.rs b/actix-web/src/types/either.rs index 119dd0d62..df93fb5ec 100644 --- a/actix-web/src/types/either.rs +++ b/actix-web/src/types/either.rs @@ -304,7 +304,7 @@ mod tests { #[actix_rt::test] async fn test_either_extract_first_try() { let (req, mut pl) = TestRequest::default() - .set_form(&TestForm { + .set_form(TestForm { hello: "world".to_owned(), }) .to_http_parts(); @@ -320,7 +320,7 @@ mod tests { #[actix_rt::test] async fn test_either_extract_fallback() { let (req, mut pl) = TestRequest::default() - .set_json(&TestForm { + .set_json(TestForm { hello: "world".to_owned(), }) .to_http_parts(); @@ -351,7 +351,7 @@ mod tests { #[actix_rt::test] async fn test_either_extract_recursive_fallback_inner() { let (req, mut pl) = TestRequest::default() - .set_json(&TestForm { + .set_json(TestForm { hello: "world".to_owned(), }) .to_http_parts(); From 0ba147ef71072e7ce780da85a71307e8de138e38 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Mar 2023 23:19:03 +0000 Subject: [PATCH 10/31] update actions/checkout to v3 --- .github/workflows/bench.yml | 2 +- .github/workflows/ci-post-merge.yml | 6 +++--- .github/workflows/ci.yml | 8 ++++---- .github/workflows/clippy-fmt.yml | 6 +++--- .github/workflows/coverage.yml | 2 +- .github/workflows/upload-doc.yml | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 008c33f89..3d16a7eb7 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install Rust uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 7ac6388d4..30d13bf88 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -29,7 +29,7 @@ jobs: CARGO_UNSTABLE_SPARSE_REGISTRY: true steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # install OpenSSL on Windows # TODO: GitHub actions docs state that OpenSSL is @@ -93,7 +93,7 @@ jobs: CARGO_INCREMENTAL: 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable @@ -120,7 +120,7 @@ jobs: CARGO_INCREMENTAL: 0 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 421becc63..48380265a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -7,7 +7,7 @@ on: branches: [master] permissions: - contents: read # to fetch code (actions/checkout) + contents: read # to fetch code (actions/checkout) jobs: build_and_test: @@ -31,7 +31,7 @@ jobs: VCPKGRS_DYNAMIC: 1 steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 # install OpenSSL on Windows # TODO: GitHub actions docs state that OpenSSL is @@ -102,7 +102,7 @@ jobs: name: io-uring tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable @@ -123,7 +123,7 @@ jobs: name: doc tests runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@nightly diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index e94c4d1af..877ca74e4 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -8,7 +8,7 @@ jobs: fmt: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@nightly with: { components: rustfmt } - run: cargo fmt --all -- --check @@ -16,7 +16,7 @@ jobs: clippy: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: { components: clippy } @@ -35,7 +35,7 @@ jobs: lint-docs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@stable with: { components: rust-docs } diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 137a413d0..bb6d7fb97 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -12,7 +12,7 @@ jobs: name: coverage runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - name: Install stable uses: actions-rs/toolchain@v1 diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 9aadafafc..2464ebcd6 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -13,7 +13,7 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v3 - uses: dtolnay/rust-toolchain@nightly From 4131786127bb1633a2731108195055b52d7a8cf6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 11 Mar 2023 23:20:02 +0000 Subject: [PATCH 11/31] remove old benchmarks --- actix-http/Cargo.toml | 16 -- actix-http/benches/quality-value.rs | 97 ----------- actix-http/benches/status-line.rs | 214 ------------------------- actix-http/benches/uninit-headers.rs | 135 ---------------- actix-http/benches/write-camel-case.rs | 93 ----------- 5 files changed, 555 deletions(-) delete mode 100644 actix-http/benches/quality-value.rs delete mode 100644 actix-http/benches/status-line.rs delete mode 100644 actix-http/benches/uninit-headers.rs delete mode 100644 actix-http/benches/write-camel-case.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 07e412a2c..ce653f440 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -124,19 +124,3 @@ tokio = { version = "1.24.2", features = ["net", "rt", "macros"] } [[example]] name = "ws" required-features = ["ws", "rustls"] - -[[bench]] -name = "write-camel-case" -harness = false - -[[bench]] -name = "status-line" -harness = false - -[[bench]] -name = "uninit-headers" -harness = false - -[[bench]] -name = "quality-value" -harness = false diff --git a/actix-http/benches/quality-value.rs b/actix-http/benches/quality-value.rs deleted file mode 100644 index 0ed274ded..000000000 --- a/actix-http/benches/quality-value.rs +++ /dev/null @@ -1,97 +0,0 @@ -#![allow(clippy::uninlined_format_args)] - -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; - -const CODES: &[u16] = &[0, 1000, 201, 800, 550]; - -fn bench_quality_display_impls(c: &mut Criterion) { - let mut group = c.benchmark_group("quality value display impls"); - - for i in CODES.iter() { - group.bench_with_input(BenchmarkId::new("New (fast?)", i), i, |b, &i| { - b.iter(|| _new::Quality(i).to_string()) - }); - - group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| { - b.iter(|| _naive::Quality(i).to_string()) - }); - } - - group.finish(); -} - -criterion_group!(benches, bench_quality_display_impls); -criterion_main!(benches); - -mod _new { - use std::fmt; - - pub struct Quality(pub(crate) u16); - - impl fmt::Display for Quality { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - 0 => f.write_str("0"), - 1000 => f.write_str("1"), - - // some number in the range 1–999 - x => { - f.write_str("0.")?; - - // this implementation avoids string allocation otherwise required - // for `.trim_end_matches('0')` - - if x < 10 { - f.write_str("00")?; - // 0 is handled so it's not possible to have a trailing 0, we can just return - itoa_fmt(f, x) - } else if x < 100 { - f.write_str("0")?; - if x % 10 == 0 { - // trailing 0, divide by 10 and write - itoa_fmt(f, x / 10) - } else { - itoa_fmt(f, x) - } - } else { - // x is in range 101–999 - - if x % 100 == 0 { - // two trailing 0s, divide by 100 and write - itoa_fmt(f, x / 100) - } else if x % 10 == 0 { - // one trailing 0, divide by 10 and write - itoa_fmt(f, x / 10) - } else { - itoa_fmt(f, x) - } - } - } - } - } - } - - pub fn itoa_fmt(mut wr: W, value: V) -> fmt::Result { - let mut buf = itoa::Buffer::new(); - wr.write_str(buf.format(value)) - } -} - -mod _naive { - use std::fmt; - - pub struct Quality(pub(crate) u16); - - impl fmt::Display for Quality { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match self.0 { - 0 => f.write_str("0"), - 1000 => f.write_str("1"), - - x => { - write!(f, "{}", format!("{:03}", x).trim_end_matches('0')) - } - } - } - } -} diff --git a/actix-http/benches/status-line.rs b/actix-http/benches/status-line.rs deleted file mode 100644 index 9fe099478..000000000 --- a/actix-http/benches/status-line.rs +++ /dev/null @@ -1,214 +0,0 @@ -use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion}; - -use bytes::BytesMut; -use http::Version; - -const CODES: &[u16] = &[201, 303, 404, 515]; - -fn bench_write_status_line_11(c: &mut Criterion) { - let mut group = c.benchmark_group("write_status_line v1.1"); - - let version = Version::HTTP_11; - - for i in CODES.iter() { - group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _original::write_status_line(version, i, &mut b); - }) - }); - - group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _new::write_status_line(version, i, &mut b); - }) - }); - - group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _naive::write_status_line(version, i, &mut b); - }) - }); - } - - group.finish(); -} - -fn bench_write_status_line_10(c: &mut Criterion) { - let mut group = c.benchmark_group("write_status_line v1.0"); - - let version = Version::HTTP_10; - - for i in CODES.iter() { - group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _original::write_status_line(version, i, &mut b); - }) - }); - - group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _new::write_status_line(version, i, &mut b); - }) - }); - - group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _naive::write_status_line(version, i, &mut b); - }) - }); - } - - group.finish(); -} - -fn bench_write_status_line_09(c: &mut Criterion) { - let mut group = c.benchmark_group("write_status_line v0.9"); - - let version = Version::HTTP_09; - - for i in CODES.iter() { - group.bench_with_input(BenchmarkId::new("Original (unsafe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _original::write_status_line(version, i, &mut b); - }) - }); - - group.bench_with_input(BenchmarkId::new("New (safe)", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _new::write_status_line(version, i, &mut b); - }) - }); - - group.bench_with_input(BenchmarkId::new("Naive", i), i, |b, &i| { - b.iter(|| { - let mut b = BytesMut::with_capacity(35); - _naive::write_status_line(version, i, &mut b); - }) - }); - } - - group.finish(); -} - -criterion_group!( - benches, - bench_write_status_line_11, - bench_write_status_line_10, - bench_write_status_line_09 -); -criterion_main!(benches); - -mod _naive { - use bytes::{BufMut, BytesMut}; - use http::Version; - - pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) { - match version { - Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "), - Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "), - Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "), - _ => { - // other HTTP version handlers do not use this method - } - } - - bytes.put_slice(n.to_string().as_bytes()); - } -} - -mod _new { - use bytes::{BufMut, BytesMut}; - use http::Version; - - const DIGITS_START: u8 = b'0'; - - pub(crate) fn write_status_line(version: Version, n: u16, bytes: &mut BytesMut) { - match version { - Version::HTTP_11 => bytes.put_slice(b"HTTP/1.1 "), - Version::HTTP_10 => bytes.put_slice(b"HTTP/1.0 "), - Version::HTTP_09 => bytes.put_slice(b"HTTP/0.9 "), - _ => { - // other HTTP version handlers do not use this method - } - } - - let d100 = (n / 100) as u8; - let d10 = ((n / 10) % 10) as u8; - let d1 = (n % 10) as u8; - - bytes.put_u8(DIGITS_START + d100); - bytes.put_u8(DIGITS_START + d10); - bytes.put_u8(DIGITS_START + d1); - - bytes.put_u8(b' '); - } -} - -mod _original { - use std::ptr; - - use bytes::{BufMut, BytesMut}; - use http::Version; - - const DEC_DIGITS_LUT: &[u8] = b"0001020304050607080910111213141516171819\ - 2021222324252627282930313233343536373839\ - 4041424344454647484950515253545556575859\ - 6061626364656667686970717273747576777879\ - 8081828384858687888990919293949596979899"; - - pub(crate) const STATUS_LINE_BUF_SIZE: usize = 13; - - pub(crate) fn write_status_line(version: Version, mut n: u16, bytes: &mut BytesMut) { - let mut buf: [u8; STATUS_LINE_BUF_SIZE] = *b"HTTP/1.1 "; - - match version { - Version::HTTP_2 => buf[5] = b'2', - Version::HTTP_10 => buf[7] = b'0', - Version::HTTP_09 => { - buf[5] = b'0'; - buf[7] = b'9'; - } - _ => {} - } - - let mut curr: isize = 12; - let buf_ptr = buf.as_mut_ptr(); - let lut_ptr = DEC_DIGITS_LUT.as_ptr(); - let four = n > 999; - - // decode 2 more chars, if > 2 chars - let d1 = (n % 100) << 1; - n /= 100; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); - } - - // decode last 1 or 2 chars - if n < 10 { - curr -= 1; - unsafe { - *buf_ptr.offset(curr) = (n as u8) + b'0'; - } - } else { - let d1 = n << 1; - curr -= 2; - unsafe { - ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2); - } - } - - bytes.put_slice(&buf); - if four { - bytes.put_u8(b' '); - } - } -} diff --git a/actix-http/benches/uninit-headers.rs b/actix-http/benches/uninit-headers.rs deleted file mode 100644 index 688c64d6e..000000000 --- a/actix-http/benches/uninit-headers.rs +++ /dev/null @@ -1,135 +0,0 @@ -use criterion::{criterion_group, criterion_main, Criterion}; - -use bytes::BytesMut; - -// A Miri run detects UB, seen on this playground: -// https://play.rust-lang.org/?version=stable&mode=debug&edition=2018&gist=f5d9aa166aa48df8dca05fce2b6c3915 - -fn bench_header_parsing(c: &mut Criterion) { - c.bench_function("Original (Unsound) [short]", |b| { - b.iter(|| { - let mut buf = BytesMut::from(REQ_SHORT); - _original::parse_headers(&mut buf); - }) - }); - - c.bench_function("New (safe) [short]", |b| { - b.iter(|| { - let mut buf = BytesMut::from(REQ_SHORT); - _new::parse_headers(&mut buf); - }) - }); - - c.bench_function("Original (Unsound) [realistic]", |b| { - b.iter(|| { - let mut buf = BytesMut::from(REQ); - _original::parse_headers(&mut buf); - }) - }); - - c.bench_function("New (safe) [realistic]", |b| { - b.iter(|| { - let mut buf = BytesMut::from(REQ); - _new::parse_headers(&mut buf); - }) - }); -} - -criterion_group!(benches, bench_header_parsing); -criterion_main!(benches); - -const MAX_HEADERS: usize = 96; - -const EMPTY_HEADER_ARRAY: [httparse::Header<'static>; MAX_HEADERS] = - [httparse::EMPTY_HEADER; MAX_HEADERS]; - -#[derive(Clone, Copy)] -struct HeaderIndex { - name: (usize, usize), - value: (usize, usize), -} - -const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex { - name: (0, 0), - value: (0, 0), -}; - -const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS]; - -impl HeaderIndex { - fn record(bytes: &[u8], headers: &[httparse::Header<'_>], indices: &mut [HeaderIndex]) { - let bytes_ptr = bytes.as_ptr() as usize; - for (header, indices) in headers.iter().zip(indices.iter_mut()) { - let name_start = header.name.as_ptr() as usize - bytes_ptr; - let name_end = name_start + header.name.len(); - indices.name = (name_start, name_end); - let value_start = header.value.as_ptr() as usize - bytes_ptr; - let value_end = value_start + header.value.len(); - indices.value = (value_start, value_end); - } - } -} - -// test cases taken from: -// https://github.com/seanmonstar/httparse/blob/master/benches/parse.rs - -const REQ_SHORT: &[u8] = b"\ -GET / HTTP/1.0\r\n\ -Host: example.com\r\n\ -Cookie: session=60; user_id=1\r\n\r\n"; - -const REQ: &[u8] = b"\ -GET /wp-content/uploads/2010/03/hello-kitty-darth-vader-pink.jpg HTTP/1.1\r\n\ -Host: www.kittyhell.com\r\n\ -User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.6; ja-JP-mac; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3 Pathtraq/0.9\r\n\ -Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n\ -Accept-Language: ja,en-us;q=0.7,en;q=0.3\r\n\ -Accept-Encoding: gzip,deflate\r\n\ -Accept-Charset: Shift_JIS,utf-8;q=0.7,*;q=0.7\r\n\ -Keep-Alive: 115\r\n\ -Connection: keep-alive\r\n\ -Cookie: wp_ozh_wsa_visits=2; wp_ozh_wsa_visit_lasttime=xxxxxxxxxx; __utma=xxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.xxxxxxxxxx.x; __utmz=xxxxxxxxx.xxxxxxxxxx.x.x.utmccn=(referral)|utmcsr=reader.livedoor.com|utmcct=/reader/|utmcmd=referral|padding=under256\r\n\r\n"; - -mod _new { - use super::*; - - pub fn parse_headers(src: &mut BytesMut) -> usize { - let mut headers: [HeaderIndex; MAX_HEADERS] = EMPTY_HEADER_INDEX_ARRAY; - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = EMPTY_HEADER_ARRAY; - - let mut req = httparse::Request::new(&mut parsed); - match req.parse(src).unwrap() { - httparse::Status::Complete(_len) => { - HeaderIndex::record(src, req.headers, &mut headers); - req.headers.len() - } - _ => unreachable!(), - } - } -} - -mod _original { - use super::*; - - use std::mem::MaybeUninit; - - pub fn parse_headers(src: &mut BytesMut) -> usize { - #![allow(invalid_value, clippy::uninit_assumed_init)] - - let mut headers: [HeaderIndex; MAX_HEADERS] = - unsafe { MaybeUninit::uninit().assume_init() }; - - #[allow(invalid_value)] - let mut parsed: [httparse::Header<'_>; MAX_HEADERS] = - unsafe { MaybeUninit::uninit().assume_init() }; - - let mut req = httparse::Request::new(&mut parsed); - match req.parse(src).unwrap() { - httparse::Status::Complete(_len) => { - HeaderIndex::record(src, req.headers, &mut headers); - req.headers.len() - } - _ => unreachable!(), - } - } -} diff --git a/actix-http/benches/write-camel-case.rs b/actix-http/benches/write-camel-case.rs deleted file mode 100644 index ccf09b37e..000000000 --- a/actix-http/benches/write-camel-case.rs +++ /dev/null @@ -1,93 +0,0 @@ -use criterion::{black_box, criterion_group, criterion_main, BenchmarkId, Criterion}; - -fn bench_write_camel_case(c: &mut Criterion) { - let mut group = c.benchmark_group("write_camel_case"); - - let names = ["connection", "Transfer-Encoding", "transfer-encoding"]; - - for &i in &names { - let bts = i.as_bytes(); - - group.bench_with_input(BenchmarkId::new("Original", i), bts, |b, bts| { - b.iter(|| { - let mut buf = black_box([0; 24]); - _original::write_camel_case(black_box(bts), &mut buf) - }); - }); - - group.bench_with_input(BenchmarkId::new("New", i), bts, |b, bts| { - b.iter(|| { - let mut buf = black_box([0; 24]); - let len = black_box(bts.len()); - _new::write_camel_case(black_box(bts), buf.as_mut_ptr(), len) - }); - }); - } - - group.finish(); -} - -criterion_group!(benches, bench_write_camel_case); -criterion_main!(benches); - -mod _new { - pub fn write_camel_case(value: &[u8], buf: *mut u8, len: usize) { - // first copy entire (potentially wrong) slice to output - let buffer = unsafe { - std::ptr::copy_nonoverlapping(value.as_ptr(), buf, len); - std::slice::from_raw_parts_mut(buf, len) - }; - - let mut iter = value.iter(); - - // first character should be uppercase - if let Some(c @ b'a'..=b'z') = iter.next() { - buffer[0] = c & 0b1101_1111; - } - - // track 1 ahead of the current position since that's the location being assigned to - let mut index = 2; - - // remaining characters after hyphens should also be uppercase - while let Some(&c) = iter.next() { - if c == b'-' { - // advance iter by one and uppercase if needed - if let Some(c @ b'a'..=b'z') = iter.next() { - buffer[index] = c & 0b1101_1111; - } - } - - index += 1; - } - } -} - -mod _original { - pub fn write_camel_case(value: &[u8], buffer: &mut [u8]) { - let mut index = 0; - let key = value; - let mut key_iter = key.iter(); - - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } - } else { - return; - } - - while let Some(c) = key_iter.next() { - buffer[index] = *c; - index += 1; - if *c == b'-' { - if let Some(c) = key_iter.next() { - if *c >= b'a' && *c <= b'z' { - buffer[index] = *c ^ b' '; - index += 1; - } - } - } - } - } -} From 19c9d858f25e8262e14546f430d713addb397e96 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 12 Mar 2023 04:29:22 +0000 Subject: [PATCH 12/31] support 16 extractors --- actix-web/CHANGES.md | 8 ++++++++ actix-web/src/extract.rs | 4 ++++ actix-web/src/handler.rs | 5 +++++ 3 files changed, 17 insertions(+) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 9a768a122..757e31eeb 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -2,10 +2,18 @@ ## Unreleased - 2023-xx-xx +### Added + - Add `Resource::{get, post, etc...}` methods for more concisely adding routes that don't need additional guards. +### Changed + +- Handler functions can now receive up to 16 extractor parameters. + ## 4.3.1 - 2023-02-26 +### Added + - Add support for custom methods with the `#[route]` macro. [#2969] [#2969]: https://github.com/actix/actix-web/pull/2969 diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs index d4f5cc91f..84904a9eb 100644 --- a/actix-web/src/extract.rs +++ b/actix-web/src/extract.rs @@ -416,6 +416,10 @@ mod tuple_from_req { tuple_from_req! { TupleFromRequest10; A, B, C, D, E, F, G, H, I, J } tuple_from_req! { TupleFromRequest11; A, B, C, D, E, F, G, H, I, J, K } tuple_from_req! { TupleFromRequest12; A, B, C, D, E, F, G, H, I, J, K, L } + tuple_from_req! { TupleFromRequest13; A, B, C, D, E, F, G, H, I, J, K, L, M } + tuple_from_req! { TupleFromRequest14; A, B, C, D, E, F, G, H, I, J, K, L, M, N } + tuple_from_req! { TupleFromRequest15; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O } + tuple_from_req! { TupleFromRequest16; A, B, C, D, E, F, G, H, I, J, K, L, M, N, O, P } } #[cfg(test)] diff --git a/actix-web/src/handler.rs b/actix-web/src/handler.rs index 522a48b82..0c5e58e28 100644 --- a/actix-web/src/handler.rs +++ b/actix-web/src/handler.rs @@ -151,6 +151,10 @@ factory_tuple! { A B C D E F G H I } factory_tuple! { A B C D E F G H I J } factory_tuple! { A B C D E F G H I J K } factory_tuple! { A B C D E F G H I J K L } +factory_tuple! { A B C D E F G H I J K L M } +factory_tuple! { A B C D E F G H I J K L M N } +factory_tuple! { A B C D E F G H I J K L M N O } +factory_tuple! { A B C D E F G H I J K L M N O P } #[cfg(test)] mod tests { @@ -167,6 +171,7 @@ mod tests { async fn handler_max( _01: (), _02: (), _03: (), _04: (), _05: (), _06: (), _07: (), _08: (), _09: (), _10: (), _11: (), _12: (), + _13: (), _14: (), _15: (), _16: (), ) {} assert_impl_handler(handler_min); From dfaca18584cc36c08506cf8433ac2d118e450856 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 12 Mar 2023 15:32:07 +0000 Subject: [PATCH 13/31] add compression_responses benchmark (#3001) --- actix-http/Cargo.toml | 5 ++ .../benches/response-body-compression.rs | 90 +++++++++++++++++++ 2 files changed, 95 insertions(+) create mode 100644 actix-http/benches/response-body-compression.rs diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index ce653f440..235e4e980 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -124,3 +124,8 @@ tokio = { version = "1.24.2", features = ["net", "rt", "macros"] } [[example]] name = "ws" required-features = ["ws", "rustls"] + +[[bench]] +name = "response-body-compression" +harness = false +required-features = ["compress-brotli", "compress-gzip", "compress-zstd"] diff --git a/actix-http/benches/response-body-compression.rs b/actix-http/benches/response-body-compression.rs new file mode 100644 index 000000000..d128bf75b --- /dev/null +++ b/actix-http/benches/response-body-compression.rs @@ -0,0 +1,90 @@ +#![allow(clippy::uninlined_format_args)] + +use std::convert::Infallible; + +use actix_http::{encoding::Encoder, ContentEncoding, Request, Response, StatusCode}; +use actix_service::{fn_service, Service as _}; +use criterion::{black_box, criterion_group, criterion_main, Criterion}; + +static BODY: &[u8] = include_bytes!("../Cargo.toml"); + +fn compression_responses(c: &mut Criterion) { + let mut group = c.benchmark_group("compression responses"); + + group.bench_function("identity", |b| { + let rt = actix_rt::Runtime::new().unwrap(); + + let identity_svc = fn_service(|_: Request| async move { + let mut res = Response::with_body(StatusCode::OK, ()); + let body = black_box(Encoder::response( + ContentEncoding::Identity, + res.head_mut(), + BODY, + )); + Ok::<_, Infallible>(black_box(res.set_body(black_box(body)))) + }); + + b.iter(|| { + rt.block_on(identity_svc.call(Request::new())).unwrap(); + }); + }); + + group.bench_function("gzip", |b| { + let rt = actix_rt::Runtime::new().unwrap(); + + let identity_svc = fn_service(|_: Request| async move { + let mut res = Response::with_body(StatusCode::OK, ()); + let body = black_box(Encoder::response( + ContentEncoding::Gzip, + res.head_mut(), + BODY, + )); + Ok::<_, Infallible>(black_box(res.set_body(black_box(body)))) + }); + + b.iter(|| { + rt.block_on(identity_svc.call(Request::new())).unwrap(); + }); + }); + + group.bench_function("br", |b| { + let rt = actix_rt::Runtime::new().unwrap(); + + let identity_svc = fn_service(|_: Request| async move { + let mut res = Response::with_body(StatusCode::OK, ()); + let body = black_box(Encoder::response( + ContentEncoding::Brotli, + res.head_mut(), + BODY, + )); + Ok::<_, Infallible>(black_box(res.set_body(black_box(body)))) + }); + + b.iter(|| { + rt.block_on(identity_svc.call(Request::new())).unwrap(); + }); + }); + + group.bench_function("zstd", |b| { + let rt = actix_rt::Runtime::new().unwrap(); + + let identity_svc = fn_service(|_: Request| async move { + let mut res = Response::with_body(StatusCode::OK, ()); + let body = black_box(Encoder::response( + ContentEncoding::Zstd, + res.head_mut(), + BODY, + )); + Ok::<_, Infallible>(black_box(res.set_body(black_box(body)))) + }); + + b.iter(|| { + rt.block_on(identity_svc.call(Request::new())).unwrap(); + }); + }); + + group.finish(); +} + +criterion_group!(benches, compression_responses); +criterion_main!(benches); From 9e7a6fe57bfffc36d199c3e729f3470d10a381aa Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Mar 2023 13:31:48 +0000 Subject: [PATCH 14/31] add `body::to_bytes_limited` (#3000 * add body::to_body_limit * rename to_bytes_limited --- actix-http/CHANGES.md | 5 ++ actix-http/src/body/mod.rs | 2 +- actix-http/src/body/utils.rs | 141 ++++++++++++++++++++++++++++++++--- 3 files changed, 135 insertions(+), 13 deletions(-) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 831a0bcd0..aaf84d765 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -2,6 +2,11 @@ ## Unreleased - 2023-xx-xx +### Added + +- Add `body::to_body_limit()` function. +- Add `body::BodyLimitExceeded` error type. + ## 3.3.1 - 2023-03-02 ### Fixed diff --git a/actix-http/src/body/mod.rs b/actix-http/src/body/mod.rs index 0fb090eb5..d1708b9d5 100644 --- a/actix-http/src/body/mod.rs +++ b/actix-http/src/body/mod.rs @@ -22,4 +22,4 @@ pub(crate) use self::message_body::MessageBodyMapErr; pub use self::none::None; pub use self::size::BodySize; pub use self::sized_stream::SizedStream; -pub use self::utils::to_bytes; +pub use self::utils::{to_bytes, to_bytes_limited, BodyLimitExceeded}; diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs index 0a6fb0c15..c741eefd2 100644 --- a/actix-http/src/body/utils.rs +++ b/actix-http/src/body/utils.rs @@ -7,71 +7,188 @@ use futures_core::ready; use super::{BodySize, MessageBody}; -/// Collects the body produced by a `MessageBody` implementation into `Bytes`. +/// Collects all the bytes produced by `body`. /// /// Any errors produced by the body stream are returned immediately. /// +/// Consider using [`to_bytes_limited`] instead to protect against memory exhaustion. +/// /// # Examples +/// /// ``` /// use actix_http::body::{self, to_bytes}; /// use bytes::Bytes; /// -/// # async fn test_to_bytes() { +/// # actix_rt::System::new().block_on(async { /// let body = body::None::new(); /// let bytes = to_bytes(body).await.unwrap(); /// assert!(bytes.is_empty()); /// /// let body = Bytes::from_static(b"123"); /// let bytes = to_bytes(body).await.unwrap(); -/// assert_eq!(bytes, b"123"[..]); -/// # } +/// assert_eq!(bytes, "123"); +/// # }); /// ``` pub async fn to_bytes(body: B) -> Result { + to_bytes_limited(body, usize::MAX) + .await + .expect("body should never overflow usize::MAX") +} + +/// Error type returned from [`to_bytes_limited`] when body produced exceeds limit. +#[derive(Debug)] +#[non_exhaustive] +pub struct BodyLimitExceeded; + +/// Collects the bytes produced by `body`, up to `limit` bytes. +/// +/// If a chunk read from `poll_next` causes the total number of bytes read to exceed `limit`, an +/// `Err(BodyLimitExceeded)` is returned. +/// +/// Any errors produced by the body stream are returned immediately as `Ok(Err(B::Error))`. +/// +/// # Examples +/// +/// ``` +/// use actix_http::body::{self, to_bytes_limited}; +/// use bytes::Bytes; +/// +/// # actix_rt::System::new().block_on(async { +/// let body = body::None::new(); +/// let bytes = to_bytes_limited(body, 10).await.unwrap().unwrap(); +/// assert!(bytes.is_empty()); +/// +/// let body = Bytes::from_static(b"123"); +/// let bytes = to_bytes_limited(body, 10).await.unwrap().unwrap(); +/// assert_eq!(bytes, "123"); +/// +/// let body = Bytes::from_static(b"123"); +/// assert!(to_bytes_limited(body, 2).await.is_err()); +/// # }); +/// ``` +pub async fn to_bytes_limited( + body: B, + limit: usize, +) -> Result, BodyLimitExceeded> { let cap = match body.size() { - BodySize::None | BodySize::Sized(0) => return Ok(Bytes::new()), + BodySize::None | BodySize::Sized(0) => return Ok(Ok(Bytes::new())), + BodySize::Sized(size) if size as usize > limit => return Err(BodyLimitExceeded), BodySize::Sized(size) => size as usize, // good enough first guess for chunk size BodySize::Stream => 32_768, }; + let mut exceeded_limit = false; let mut buf = BytesMut::with_capacity(cap); pin!(body); - poll_fn(|cx| loop { + match poll_fn(|cx| loop { let body = body.as_mut(); match ready!(body.poll_next(cx)) { - Some(Ok(bytes)) => buf.extend_from_slice(&bytes), + Some(Ok(bytes)) => { + // if limit is exceeded... + if buf.len() + bytes.len() > limit { + // ...set flag to true and break out of poll_fn + exceeded_limit = true; + return Poll::Ready(Ok(())); + } + + buf.extend_from_slice(&bytes) + } None => return Poll::Ready(Ok(())), Some(Err(err)) => return Poll::Ready(Err(err)), } }) - .await?; + .await + { + // propagate error returned from body poll + Err(err) => Ok(Err(err)), - Ok(buf.freeze()) + // limit was exceeded while reading body + Ok(()) if exceeded_limit => Err(BodyLimitExceeded), + + // otherwise return body buffer + Ok(()) => Ok(Ok(buf.freeze())), + } } #[cfg(test)] -mod test { +mod tests { + use std::io; + use futures_util::{stream, StreamExt as _}; use super::*; - use crate::{body::BodyStream, Error}; + use crate::{ + body::{BodyStream, SizedStream}, + Error, + }; #[actix_rt::test] - async fn test_to_bytes() { + async fn to_bytes_complete() { let bytes = to_bytes(()).await.unwrap(); assert!(bytes.is_empty()); let body = Bytes::from_static(b"123"); let bytes = to_bytes(body).await.unwrap(); assert_eq!(bytes, b"123"[..]); + } + #[actix_rt::test] + async fn to_bytes_streams() { let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")]) .map(Ok::<_, Error>); let body = BodyStream::new(stream); let bytes = to_bytes(body).await.unwrap(); assert_eq!(bytes, b"123abc"[..]); } + + #[actix_rt::test] + async fn to_bytes_limited_complete() { + let bytes = to_bytes_limited((), 0).await.unwrap().unwrap(); + assert!(bytes.is_empty()); + + let bytes = to_bytes_limited((), 1).await.unwrap().unwrap(); + assert!(bytes.is_empty()); + + assert!(to_bytes_limited(Bytes::from_static(b"12"), 0) + .await + .is_err()); + assert!(to_bytes_limited(Bytes::from_static(b"12"), 1) + .await + .is_err()); + assert!(to_bytes_limited(Bytes::from_static(b"12"), 2).await.is_ok()); + assert!(to_bytes_limited(Bytes::from_static(b"12"), 3).await.is_ok()); + } + + #[actix_rt::test] + async fn to_bytes_limited_streams() { + // hinting a larger body fails + let body = SizedStream::new(8, stream::empty().map(Ok::<_, Error>)); + assert!(to_bytes_limited(body, 3).await.is_err()); + + // hinting a smaller body is okay + let body = SizedStream::new(3, stream::empty().map(Ok::<_, Error>)); + assert!(to_bytes_limited(body, 3).await.unwrap().unwrap().is_empty()); + + // hinting a smaller body then returning a larger one fails + let stream = stream::iter(vec![Bytes::from_static(b"1234")]).map(Ok::<_, Error>); + let body = SizedStream::new(3, stream); + assert!(to_bytes_limited(body, 3).await.is_err()); + + let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")]) + .map(Ok::<_, Error>); + let body = BodyStream::new(stream); + assert!(to_bytes_limited(body, 3).await.is_err()); + } + + #[actix_rt::test] + async fn to_body_limit_error() { + let err_stream = stream::once(async { Err(io::Error::new(io::ErrorKind::Other, "")) }); + let body = SizedStream::new(8, err_stream); + // not too big, but propagates error from body stream + assert!(to_bytes_limited(body, 10).await.unwrap().is_err()); + } } From 44c5cdaa107e4c5bc220428f44166441144c2238 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Mar 2023 13:40:07 +0000 Subject: [PATCH 15/31] bound initial allocation in to_bytes_limited --- actix-http/src/body/utils.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs index c741eefd2..7af5a50ad 100644 --- a/actix-http/src/body/utils.rs +++ b/actix-http/src/body/utils.rs @@ -32,7 +32,7 @@ use super::{BodySize, MessageBody}; pub async fn to_bytes(body: B) -> Result { to_bytes_limited(body, usize::MAX) .await - .expect("body should never overflow usize::MAX") + .expect("body should never yield more than usize::MAX bytes") } /// Error type returned from [`to_bytes_limited`] when body produced exceeds limit. @@ -70,12 +70,14 @@ pub async fn to_bytes_limited( body: B, limit: usize, ) -> Result, BodyLimitExceeded> { + /// Sensible default (32kB) for initial, bounded allocation when collecting body bytes. + const INITIAL_ALLOC_BYTES: usize = 32 * 1024; + let cap = match body.size() { BodySize::None | BodySize::Sized(0) => return Ok(Ok(Bytes::new())), BodySize::Sized(size) if size as usize > limit => return Err(BodyLimitExceeded), - BodySize::Sized(size) => size as usize, - // good enough first guess for chunk size - BodySize::Stream => 32_768, + BodySize::Sized(size) => (size as usize).min(INITIAL_ALLOC_BYTES), + BodySize::Stream => INITIAL_ALLOC_BYTES, }; let mut exceeded_limit = false; From 0e7380659f844d216b206faaae6abd51f9d1aeea Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Mar 2023 13:40:09 +0000 Subject: [PATCH 16/31] implement Error for BodyLimitExceeded --- actix-http/src/body/utils.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/actix-http/src/body/utils.rs b/actix-http/src/body/utils.rs index 7af5a50ad..d1449179f 100644 --- a/actix-http/src/body/utils.rs +++ b/actix-http/src/body/utils.rs @@ -3,6 +3,7 @@ use std::task::Poll; use actix_rt::pin; use actix_utils::future::poll_fn; use bytes::{Bytes, BytesMut}; +use derive_more::{Display, Error}; use futures_core::ready; use super::{BodySize, MessageBody}; @@ -36,7 +37,8 @@ pub async fn to_bytes(body: B) -> Result { } /// Error type returned from [`to_bytes_limited`] when body produced exceeds limit. -#[derive(Debug)] +#[derive(Debug, Display, Error)] +#[display(fmt = "limit exceeded while collecting body bytes")] #[non_exhaustive] pub struct BodyLimitExceeded; From bfdc29ebb8d93f5cc276ed32e81bed37bceb4fa6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Mar 2023 14:22:50 +0000 Subject: [PATCH 17/31] normalize actix-files error messages --- actix-files/src/error.rs | 32 +++++++++++----------- actix-files/src/range.rs | 58 +++++++++++++++++++++++++++++----------- 2 files changed, 58 insertions(+), 32 deletions(-) diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index 2f3a36cd1..68cea8d89 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -4,46 +4,44 @@ use derive_more::Display; /// Errors which can occur when serving static files. #[derive(Debug, PartialEq, Eq, Display)] pub enum FilesError { - /// Path is not a directory + /// Path is not a directory. #[allow(dead_code)] - #[display(fmt = "Path is not a directory. Unable to serve static files")] + #[display(fmt = "path is not a directory. Unable to serve static files")] IsNotDirectory, - /// Cannot render directory - #[display(fmt = "Unable to render directory without index file")] + /// Cannot render directory. + #[display(fmt = "unable to render directory without index file")] IsDirectory, } -/// Return `NotFound` for `FilesError` impl ResponseError for FilesError { + /// Returns `404 Not Found`. fn status_code(&self) -> StatusCode { StatusCode::NOT_FOUND } } -#[allow(clippy::enum_variant_names)] #[derive(Debug, PartialEq, Eq, Display)] #[non_exhaustive] pub enum UriSegmentError { - /// The segment started with the wrapped invalid character. - #[display(fmt = "The segment started with the wrapped invalid character")] + /// Segment started with the wrapped invalid character. + #[display(fmt = "segment started with invalid character: ('{_0}')")] BadStart(char), - /// The segment contained the wrapped invalid character. - #[display(fmt = "The segment contained the wrapped invalid character")] + /// Segment contained the wrapped invalid character. + #[display(fmt = "segment contained invalid character ('{_0}')")] BadChar(char), - /// The segment ended with the wrapped invalid character. - #[display(fmt = "The segment ended with the wrapped invalid character")] + /// Segment ended with the wrapped invalid character. + #[display(fmt = "segment ended with invalid character: ('{_0}')")] BadEnd(char), - - /// The path is not a valid UTF-8 string after doing percent decoding. - #[display(fmt = "The path is not a valid UTF-8 string after percent-decoding")] - NotValidUtf8, + // /// Path is not a valid UTF-8 string after percent-decoding. + // #[display(fmt = "path is not a valid UTF-8 string after percent-decoding")] + // NotValidUtf8, } -/// Return `BadRequest` for `UriSegmentError` impl ResponseError for UriSegmentError { + /// Returns `400 Bad Request`. fn status_code(&self) -> StatusCode { StatusCode::BAD_REQUEST } diff --git a/actix-files/src/range.rs b/actix-files/src/range.rs index 8d9fe9445..65c680ede 100644 --- a/actix-files/src/range.rs +++ b/actix-files/src/range.rs @@ -1,4 +1,36 @@ -use derive_more::{Display, Error}; +use std::fmt; + +use derive_more::Error; + +/// Copy of `http_range::HttpRangeParseError`. +#[derive(Debug, Clone)] +enum HttpRangeParseError { + InvalidRange, + NoOverlap, +} + +impl From for HttpRangeParseError { + fn from(err: http_range::HttpRangeParseError) -> Self { + match err { + http_range::HttpRangeParseError::InvalidRange => Self::InvalidRange, + http_range::HttpRangeParseError::NoOverlap => Self::NoOverlap, + } + } +} + +#[derive(Debug, Clone, Error)] +#[non_exhaustive] +pub struct ParseRangeErr(#[error(not(source))] HttpRangeParseError); + +impl fmt::Display for ParseRangeErr { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + f.write_str("invalid Range header: ")?; + f.write_str(match self.0 { + HttpRangeParseError::InvalidRange => "invalid syntax", + HttpRangeParseError::NoOverlap => "range starts after end of content", + }) + } +} /// HTTP Range header representation. #[derive(Debug, Clone, Copy)] @@ -10,26 +42,22 @@ pub struct HttpRange { pub length: u64, } -#[derive(Debug, Clone, Display, Error)] -#[display(fmt = "Parse HTTP Range failed")] -pub struct ParseRangeErr(#[error(not(source))] ()); - impl HttpRange { /// Parses Range HTTP header string as per RFC 2616. /// /// `header` is HTTP Range header (e.g. `bytes=bytes=0-9`). /// `size` is full size of response (file). pub fn parse(header: &str, size: u64) -> Result, ParseRangeErr> { - match http_range::HttpRange::parse(header, size) { - Ok(ranges) => Ok(ranges - .iter() - .map(|range| HttpRange { - start: range.start, - length: range.length, - }) - .collect()), - Err(_) => Err(ParseRangeErr(())), - } + let ranges = http_range::HttpRange::parse(header, size) + .map_err(|err| ParseRangeErr(err.into()))?; + + Ok(ranges + .iter() + .map(|range| HttpRange { + start: range.start, + length: range.length, + }) + .collect()) } } From 442fa279dad32c34556624fdcbad077cd415bd09 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Mar 2023 14:30:21 +0000 Subject: [PATCH 18/31] uncomment error variant --- actix-files/src/error.rs | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index 68cea8d89..d614651fc 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -35,9 +35,10 @@ pub enum UriSegmentError { /// Segment ended with the wrapped invalid character. #[display(fmt = "segment ended with invalid character: ('{_0}')")] BadEnd(char), - // /// Path is not a valid UTF-8 string after percent-decoding. - // #[display(fmt = "path is not a valid UTF-8 string after percent-decoding")] - // NotValidUtf8, + + /// Path is not a valid UTF-8 string after percent-decoding. + #[display(fmt = "path is not a valid UTF-8 string after percent-decoding")] + NotValidUtf8, } impl ResponseError for UriSegmentError { From 5e29726c4fa725b1bf3a0317c57e3f0d1b3e327c Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Mon, 13 Mar 2023 17:17:02 +0000 Subject: [PATCH 19/31] standardize error messages in actix-http --- actix-http/src/error.rs | 66 +++++++++++++-------------- actix-http/src/ws/mod.rs | 32 ++++++------- actix-http/tests/test_ws.rs | 6 +-- actix-web/src/error/response_error.rs | 2 +- 4 files changed, 52 insertions(+), 54 deletions(-) diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 41522a254..fa0228a50 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -161,44 +161,44 @@ impl From for Error { #[non_exhaustive] pub enum ParseError { /// An invalid `Method`, such as `GE.T`. - #[display(fmt = "Invalid Method specified")] + #[display(fmt = "invalid method specified")] Method, /// An invalid `Uri`, such as `exam ple.domain`. - #[display(fmt = "Uri error: {}", _0)] + #[display(fmt = "URI error: {}", _0)] Uri(InvalidUri), /// An invalid `HttpVersion`, such as `HTP/1.1` - #[display(fmt = "Invalid HTTP version specified")] + #[display(fmt = "invalid HTTP version specified")] Version, /// An invalid `Header`. - #[display(fmt = "Invalid Header provided")] + #[display(fmt = "invalid Header provided")] Header, /// A message head is too large to be reasonable. - #[display(fmt = "Message head is too large")] + #[display(fmt = "message head is too large")] TooLarge, /// A message reached EOF, but is not complete. - #[display(fmt = "Message is incomplete")] + #[display(fmt = "message is incomplete")] Incomplete, /// An invalid `Status`, such as `1337 ELITE`. - #[display(fmt = "Invalid Status provided")] + #[display(fmt = "invalid status provided")] Status, /// A timeout occurred waiting for an IO event. #[allow(dead_code)] - #[display(fmt = "Timeout")] + #[display(fmt = "timeout")] Timeout, - /// An `io::Error` that occurred while trying to read or write to a network stream. - #[display(fmt = "IO error: {}", _0)] + /// An I/O error that occurred while trying to read or write to a network stream. + #[display(fmt = "I/O error: {}", _0)] Io(io::Error), /// Parsing a field as string failed. - #[display(fmt = "UTF8 error: {}", _0)] + #[display(fmt = "UTF-8 error: {}", _0)] Utf8(Utf8Error), } @@ -257,22 +257,19 @@ impl From for Response { #[non_exhaustive] pub enum PayloadError { /// A payload reached EOF, but is not complete. - #[display( - fmt = "A payload reached EOF, but is not complete. Inner error: {:?}", - _0 - )] + #[display(fmt = "payload reached EOF before completing: {:?}", _0)] Incomplete(Option), /// Content encoding stream corruption. - #[display(fmt = "Can not decode content-encoding.")] + #[display(fmt = "can not decode content-encoding")] EncodingCorrupted, /// Payload reached size limit. - #[display(fmt = "Payload reached size limit.")] + #[display(fmt = "payload reached size limit")] Overflow, /// Payload length is unknown. - #[display(fmt = "Payload length is unknown.")] + #[display(fmt = "payload length is unknown")] UnknownLength, /// HTTP/2 payload error. @@ -330,22 +327,23 @@ impl From for Error { #[non_exhaustive] pub enum DispatchError { /// Service error. - #[display(fmt = "Service Error")] + #[display(fmt = "service error")] Service(Response), /// Body streaming error. - #[display(fmt = "Body error: {}", _0)] + #[display(fmt = "body error: {}", _0)] Body(Box), /// Upgrade service error. + #[display(fmt = "upgrade error")] Upgrade, /// An `io::Error` that occurred while trying to read or write to a network stream. - #[display(fmt = "IO error: {}", _0)] + #[display(fmt = "I/O error: {}", _0)] Io(io::Error), /// Request parse error. - #[display(fmt = "Request parse error: {}", _0)] + #[display(fmt = "request parse error: {}", _0)] Parse(ParseError), /// HTTP/2 error. @@ -354,19 +352,19 @@ pub enum DispatchError { H2(h2::Error), /// The first request did not complete within the specified timeout. - #[display(fmt = "The first request did not complete within the specified timeout")] + #[display(fmt = "request did not complete within the specified timeout")] SlowRequestTimeout, - /// Disconnect timeout. Makes sense for ssl streams. - #[display(fmt = "Connection shutdown timeout")] + /// Disconnect timeout. Makes sense for TLS streams. + #[display(fmt = "connection shutdown timeout")] DisconnectTimeout, /// Handler dropped payload before reading EOF. - #[display(fmt = "Handler dropped payload before reading EOF")] + #[display(fmt = "handler dropped payload before reading EOF")] HandlerDroppedPayload, /// Internal error. - #[display(fmt = "Internal error")] + #[display(fmt = "internal error")] InternalError, } @@ -391,12 +389,12 @@ impl StdError for DispatchError { #[cfg_attr(test, derive(PartialEq, Eq))] #[non_exhaustive] pub enum ContentTypeError { - /// Can not parse content type - #[display(fmt = "Can not parse content type")] + /// Can not parse content type. + #[display(fmt = "could not parse content type")] ParseError, - /// Unknown content encoding - #[display(fmt = "Unknown content encoding")] + /// Unknown content encoding. + #[display(fmt = "unknown content encoding")] UnknownEncoding, } @@ -424,7 +422,7 @@ mod tests { let err: Error = ParseError::Io(orig).into(); assert_eq!( format!("{}", err), - "error parsing HTTP message: IO error: other" + "error parsing HTTP message: I/O error: other" ); } @@ -451,7 +449,7 @@ mod tests { let err = PayloadError::Incomplete(None); assert_eq!( err.to_string(), - "A payload reached EOF, but is not complete. Inner error: None" + "payload reached EOF before completing: None" ); } @@ -471,7 +469,7 @@ mod tests { match ParseError::from($from) { e @ $error => { let desc = format!("{}", e); - assert_eq!(desc, format!("IO error: {}", $from)); + assert_eq!(desc, format!("I/O error: {}", $from)); } _ => unreachable!("{:?}", $from), } diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 75d4ca628..2a0b0a99c 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -26,39 +26,39 @@ pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode}; #[derive(Debug, Display, Error, From)] pub enum ProtocolError { /// Received an unmasked frame from client. - #[display(fmt = "Received an unmasked frame from client.")] + #[display(fmt = "received an unmasked frame from client")] UnmaskedFrame, /// Received a masked frame from server. - #[display(fmt = "Received a masked frame from server.")] + #[display(fmt = "received a masked frame from server")] MaskedFrame, /// Encountered invalid opcode. - #[display(fmt = "Invalid opcode: {}.", _0)] + #[display(fmt = "invalid opcode ({})", _0)] InvalidOpcode(#[error(not(source))] u8), /// Invalid control frame length - #[display(fmt = "Invalid control frame length: {}.", _0)] + #[display(fmt = "invalid control frame length ({})", _0)] InvalidLength(#[error(not(source))] usize), /// Bad opcode. - #[display(fmt = "Bad opcode.")] + #[display(fmt = "bad opcode")] BadOpCode, /// A payload reached size limit. - #[display(fmt = "A payload reached size limit.")] + #[display(fmt = "payload reached size limit")] Overflow, - /// Continuation is not started. - #[display(fmt = "Continuation is not started.")] + /// Continuation has not started. + #[display(fmt = "continuation has not started")] ContinuationNotStarted, /// Received new continuation but it is already started. - #[display(fmt = "Received new continuation but it is already started.")] + #[display(fmt = "received new continuation but it has already started")] ContinuationStarted, /// Unknown continuation fragment. - #[display(fmt = "Unknown continuation fragment: {}.", _0)] + #[display(fmt = "unknown continuation fragment: {}", _0)] ContinuationFragment(#[error(not(source))] OpCode), /// I/O error. @@ -70,27 +70,27 @@ pub enum ProtocolError { #[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)] pub enum HandshakeError { /// Only get method is allowed. - #[display(fmt = "Method not allowed.")] + #[display(fmt = "method not allowed")] GetMethodRequired, /// Upgrade header if not set to WebSocket. - #[display(fmt = "WebSocket upgrade is expected.")] + #[display(fmt = "WebSocket upgrade is expected")] NoWebsocketUpgrade, /// Connection header is not set to upgrade. - #[display(fmt = "Connection upgrade is expected.")] + #[display(fmt = "connection upgrade is expected")] NoConnectionUpgrade, /// WebSocket version header is not set. - #[display(fmt = "WebSocket version header is required.")] + #[display(fmt = "WebSocket version header is required")] NoVersionHeader, /// Unsupported WebSocket version. - #[display(fmt = "Unsupported WebSocket version.")] + #[display(fmt = "unsupported WebSocket version")] UnsupportedVersion, /// WebSocket key is not set or wrong. - #[display(fmt = "Unknown websocket key.")] + #[display(fmt = "unknown WebSocket key")] BadWebsocketKey, } diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs index a9c1acd33..a2866613b 100644 --- a/actix-http/tests/test_ws.rs +++ b/actix-http/tests/test_ws.rs @@ -39,13 +39,13 @@ impl WsService { #[derive(Debug, Display, Error, From)] enum WsServiceError { - #[display(fmt = "http error")] + #[display(fmt = "HTTP error")] Http(actix_http::Error), - #[display(fmt = "ws handshake error")] + #[display(fmt = "WS handshake error")] Ws(actix_http::ws::HandshakeError), - #[display(fmt = "io error")] + #[display(fmt = "I/O error")] Io(std::io::Error), #[display(fmt = "dispatcher error")] diff --git a/actix-web/src/error/response_error.rs b/actix-web/src/error/response_error.rs index 7d2c06154..f5d8cf467 100644 --- a/actix-web/src/error/response_error.rs +++ b/actix-web/src/error/response_error.rs @@ -152,7 +152,7 @@ mod tests { let resp_err: &dyn ResponseError = &err; let err = resp_err.downcast_ref::().unwrap(); - assert_eq!(err.to_string(), "Payload reached size limit."); + assert_eq!(err.to_string(), "payload reached size limit"); let not_err = resp_err.downcast_ref::(); assert!(not_err.is_none()); From 0f3068f4884dfa439fcc3d87b21ff7c3c67208b6 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 15 Mar 2023 05:39:02 +0000 Subject: [PATCH 20/31] ci(windows): use choco to install openssl (#3003 ci: remove openssl install on windows --- .github/workflows/ci.yml | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 48380265a..88353fd30 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -17,7 +17,7 @@ jobs: target: - { name: Linux, os: ubuntu-latest, triple: x86_64-unknown-linux-gnu } - { name: macOS, os: macos-latest, triple: x86_64-apple-darwin } - - { name: Windows, os: windows-2022, triple: x86_64-pc-windows-msvc } + - { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc } version: - 1.59.0 # MSRV - stable @@ -28,20 +28,16 @@ jobs: env: CI: 1 CARGO_INCREMENTAL: 0 - VCPKGRS_DYNAMIC: 1 steps: - uses: actions/checkout@v3 - # install OpenSSL on Windows - # TODO: GitHub actions docs state that OpenSSL is - # already installed on these Windows machines somewhere - - name: Set vcpkg root - if: matrix.target.triple == 'x86_64-pc-windows-msvc' - run: echo "VCPKG_ROOT=$env:VCPKG_INSTALLATION_ROOT" | Out-File -FilePath $env:GITHUB_ENV -Append - name: Install OpenSSL - if: matrix.target.triple == 'x86_64-pc-windows-msvc' - run: vcpkg install openssl:x64-windows + if: matrix.target.os == 'windows-latest' + run: choco install openssl + - name: Set OpenSSL dir in env + if: matrix.target.os == 'windows-latest' + run: echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 From e68f87f84f3109b31de2f2361d4c06af691d8bfb Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Wed, 15 Mar 2023 13:32:55 +0000 Subject: [PATCH 21/31] add API diff to CI (#3002) --- .github/workflows/bench.yml | 6 +++- .github/workflows/ci-post-merge.yml | 8 +++-- .github/workflows/ci.yml | 12 ++++--- .github/workflows/clippy-fmt.yml | 51 +++++++++++++++++++++++------ .github/workflows/coverage.yml | 7 ++++ .github/workflows/upload-doc.yml | 10 ++++-- 6 files changed, 74 insertions(+), 20 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index 3d16a7eb7..a1a31fb8d 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -6,7 +6,11 @@ on: - master permissions: - contents: read # to fetch code (actions/checkout) + contents: read # to fetch code (actions/checkout) + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: check_benchmark: diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 30d13bf88..6ca17ef63 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -5,7 +5,11 @@ on: branches: [master] permissions: - contents: read # to fetch code (actions/checkout) + contents: read # to fetch code (actions/checkout) + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true jobs: build_and_test_nightly: @@ -81,7 +85,7 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.8.2 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean cargo-cache ci_feature_powerset_check: diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 88353fd30..f8867ce22 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -9,6 +9,10 @@ on: permissions: contents: read # to fetch code (actions/checkout) +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build_and_test: strategy: @@ -91,7 +95,7 @@ jobs: - name: Clear the cargo caches run: | - cargo install cargo-cache --version 0.8.2 --no-default-features --features ci-autoclean + cargo install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean cargo-cache io-uring: @@ -110,10 +114,8 @@ jobs: - name: tests (io-uring) timeout-minutes: 60 run: > - sudo bash -c "ulimit -Sl 512 - && ulimit -Hl 512 - && PATH=$PATH:/usr/share/rust/.cargo/bin - && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features" + sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features" + rustdoc: name: doc tests diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 877ca74e4..90e3b3a98 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -4,28 +4,42 @@ on: pull_request: types: [opened, synchronize, reopened] +permissions: + contents: read # to fetch code (actions/checkout) + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: fmt: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - with: { components: rustfmt } + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: + toolchain: nightly + components: rustfmt + - run: cargo fmt --all -- --check clippy: + permissions: + checks: write # to add clippy checks to PR diffs + runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: { components: clippy } - name: Generate Cargo.lock run: cargo generate-lockfile - name: Cache Dependencies uses: Swatinem/rust-cache@v1.2.0 - + - name: Check with Clippy uses: actions-rs/clippy-check@v1 with: @@ -37,13 +51,30 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable + - uses: actions-rust-lang/setup-rust-toolchain@v1 with: { components: rust-docs } - name: Check for broken intra-doc links - uses: actions-rs/cargo@v1 - env: - RUSTDOCFLAGS: "-D warnings" + env: { RUSTDOCFLAGS: "-D warnings" } + run: cargo doc --no-deps --all-features --workspace + + public-api-diff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 with: - command: doc - args: --no-deps --all-features --workspace + ref: ${{ github.base_ref }} + + - uses: actions/checkout@v3 + + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: { toolchain: nightly } + + - uses: taiki-e/cache-cargo-install-action@v1 + with: { tool: cargo-public-api } + + - name: generate API diff + run: | + for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do + cargo public-api --manifest-path "$f" diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }} + done diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index bb6d7fb97..11538c3d6 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -6,6 +6,13 @@ on: push: branches: [master] +permissions: + contents: read # to fetch code (actions/checkout) + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: # job currently (1st Feb 2022) segfaults coverage: diff --git a/.github/workflows/upload-doc.yml b/.github/workflows/upload-doc.yml index 2464ebcd6..743e14412 100644 --- a/.github/workflows/upload-doc.yml +++ b/.github/workflows/upload-doc.yml @@ -4,11 +4,17 @@ on: push: branches: [master] -permissions: {} +permissions: + contents: read # to fetch code (actions/checkout) + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + jobs: build: permissions: - contents: write # to push changes in repo (jamesives/github-pages-deploy-action) + contents: write # to push changes in repo (jamesives/github-pages-deploy-action) runs-on: ubuntu-latest From 8dee8a14265f02d3486cd0a58d5f3a8ddbc5679e Mon Sep 17 00:00:00 2001 From: Elijah <83283675+tltsutltsu@users.noreply.github.com> Date: Sat, 1 Apr 2023 00:09:13 +0600 Subject: [PATCH 22/31] docs(actix-http-test): update test server example (#3007) --- actix-http-test/src/lib.rs | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/actix-http-test/src/lib.rs b/actix-http-test/src/lib.rs index 55224dc46..8dcbe759d 100644 --- a/actix-http-test/src/lib.rs +++ b/actix-http-test/src/lib.rs @@ -34,7 +34,9 @@ use tokio::sync::mpsc; /// ```no_run /// use actix_http::HttpService; /// use actix_http_test::test_server; -/// use actix_web::{web, App, HttpResponse, Error}; +/// use actix_service::map_config; +/// use actix_service::ServiceFactoryExt; +/// use actix_web::{dev::AppConfig, web, App, Error, HttpResponse}; /// /// async fn my_handler() -> Result { /// Ok(HttpResponse::Ok().into()) @@ -42,14 +44,19 @@ use tokio::sync::mpsc; /// /// #[actix_web::test] /// async fn test_example() { -/// let mut srv = TestServer::start(|| -/// HttpService::new( -/// App::new().service(web::resource("/").to(my_handler)) -/// ) -/// ); +/// let srv = test_server(|| { +/// let app = App::new().service(web::resource("/").to(my_handler)); +/// +/// HttpService::build() +/// .h1(map_config(app, |_| AppConfig::default())) +/// .tcp() +/// .map_err(|_| ()) +/// }) +/// .await; /// /// let req = srv.get("/"); /// let response = req.send().await.unwrap(); +/// /// assert!(response.status().is_success()); /// } /// ``` From 97399e8c8ce584d005577604c10bd391e5da7268 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sun, 2 Apr 2023 03:27:14 +0100 Subject: [PATCH 23/31] simplify CI --- .github/workflows/ci-post-merge.yml | 2 +- .github/workflows/ci.yml | 36 ++++++++--------------------- .github/workflows/clippy-fmt.yml | 13 ++++------- .github/workflows/coverage.yml | 14 ++--------- 4 files changed, 17 insertions(+), 48 deletions(-) diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index 6ca17ef63..d47083575 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -48,7 +48,7 @@ jobs: - name: Install ${{ matrix.version }} uses: actions-rs/toolchain@v1 with: - toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} + toolchain: ${{ matrix.version }} profile: minimal override: true diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f8867ce22..4c9149722 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,9 +29,7 @@ jobs: name: ${{ matrix.target.name }} / ${{ matrix.version }} runs-on: ${{ matrix.target.os }} - env: - CI: 1 - CARGO_INCREMENTAL: 0 + env: {} steps: - uses: actions/checkout@v3 @@ -43,12 +41,10 @@ jobs: if: matrix.target.os == 'windows-latest' run: echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append - - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 + - name: Install Rust (${{ matrix.version }}) + uses: actions-rust-lang/setup-rust-toolchain@v1 with: - toolchain: ${{ matrix.version }}-${{ matrix.target.triple }} - profile: minimal - override: true + toolchain: ${{ matrix.version }} - name: Install cargo-hack uses: taiki-e/install-action@cargo-hack @@ -60,12 +56,6 @@ jobs: cargo add const-str@0.3 --dev -p=actix-web cargo add const-str@0.3 --dev -p=awc - - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 - - name: workaround MSRV issues if: matrix.version != 'stable' run: | @@ -104,12 +94,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@stable - - - name: Generate Cargo.lock - run: cargo generate-lockfile - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.3.0 + - name: Install Rust + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: { toolchain: nightly } - name: tests (io-uring) timeout-minutes: 60 @@ -123,12 +110,9 @@ jobs: steps: - uses: actions/checkout@v3 - - uses: dtolnay/rust-toolchain@nightly - - - name: Generate Cargo.lock - run: cargo generate-lockfile - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.3.0 + - name: Install Rust (nightly) + uses: actions-rust-lang/setup-rust-toolchain@v1 + with: { toolchain: nightly } - name: doc tests run: cargo ci-doctest diff --git a/.github/workflows/clippy-fmt.yml b/.github/workflows/clippy-fmt.yml index 90e3b3a98..a7da9b1c5 100644 --- a/.github/workflows/clippy-fmt.yml +++ b/.github/workflows/clippy-fmt.yml @@ -35,16 +35,11 @@ jobs: - uses: actions-rust-lang/setup-rust-toolchain@v1 with: { components: clippy } - - name: Generate Cargo.lock - run: cargo generate-lockfile - - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 - - - name: Check with Clippy - uses: actions-rs/clippy-check@v1 + - uses: giraffate/clippy-action@v1 with: - args: --workspace --tests --examples --all-features - token: ${{ secrets.GITHUB_TOKEN }} + reporter: 'github-pr-check' + github_token: ${{ secrets.GITHUB_TOKEN }} + clippy_flags: --workspace --all-features --tests --examples --bins -- -Dclippy::todo lint-docs: runs-on: ubuntu-latest diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index 11538c3d6..d537031c3 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -21,18 +21,8 @@ jobs: steps: - uses: actions/checkout@v3 - - 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 + - uses: actions-rust-lang/setup-rust-toolchain@v1 + with: { toolchain: nightly } - name: Generate coverage file run: | From e81dc768dcbb0c2f4147439a4249a8d97352302d Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Thu, 6 Apr 2023 03:11:28 +0100 Subject: [PATCH 24/31] expose h2c methods on HttpServer (#2999 * expose h2c methods on HttpServer * update h2c docs --- actix-web/CHANGES.md | 1 + actix-web/examples/on-connect.rs | 9 ++-- actix-web/src/server.rs | 80 +++++++++++++++++++++++++++++--- 3 files changed, 77 insertions(+), 13 deletions(-) diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index 757e31eeb..d5989982a 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -4,6 +4,7 @@ ### Added +- Add `HttpServer::{bind,listen}_auto_h2c()` method. - Add `Resource::{get, post, etc...}` methods for more concisely adding routes that don't need additional guards. ### Changed diff --git a/actix-web/examples/on-connect.rs b/actix-web/examples/on-connect.rs index 57017fcd6..0d56a8f25 100644 --- a/actix-web/examples/on-connect.rs +++ b/actix-web/examples/on-connect.rs @@ -4,8 +4,6 @@ //! For an example of extracting a client TLS certificate, see: //! -#![allow(clippy::uninlined_format_args)] - use std::{any::Any, io, net::SocketAddr}; use actix_web::{ @@ -24,8 +22,7 @@ struct ConnectionInfo { async fn route_whoami(req: HttpRequest) -> impl Responder { match req.conn_data::() { Some(info) => HttpResponse::Ok().body(format!( - "Here is some info about your connection:\n\n{:#?}", - info + "Here is some info about your connection:\n\n{info:#?}", )), None => { HttpResponse::InternalServerError().body("Missing expected request extension data") @@ -54,8 +51,8 @@ async fn main() -> io::Result<()> { HttpServer::new(|| App::new().default_service(web::to(route_whoami))) .on_connect(get_conn_info) - .bind(bind)? - .workers(1) + .bind_auto_h2c(bind)? + .workers(2) .run() .await } diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs index 1fa279a65..c11d0ef53 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -41,10 +41,19 @@ struct Config { /// /// Create new HTTP server with application factory. /// -/// # HTTP/2 -/// Currently, HTTP/2 is only supported when using TLS (HTTPS). See `bind_rustls` or `bind_openssl`. +/// # Automatic HTTP Version Selection +/// +/// There are two ways to select the HTTP version of an incoming connection: +/// +/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both +/// versions are supported automatically when using either of the `.bind_rustls()` or +/// `.bind_openssl()` methods. +/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach +/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use +/// the `.bind_auto_h2c()` method to enable this behavior. /// /// # Examples +/// /// ```no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; /// @@ -347,6 +356,18 @@ where Ok(self) } + /// Resolves socket address(es) and binds server to created listener(s) for plaintext HTTP/1.x + /// or HTTP/2 connections. + pub fn bind_auto_h2c(mut self, addrs: A) -> io::Result { + let sockets = bind_addrs(addrs, self.backlog)?; + + for lst in sockets { + self = self.listen_auto_h2c(lst)?; + } + + Ok(self) + } + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections /// using Rustls. /// @@ -406,13 +427,13 @@ where self.builder = self.builder .listen(format!("actix-web-service-{}", addr), lst, move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + let cfg = cfg.lock().unwrap(); + let host = cfg.host.clone().unwrap_or_else(|| format!("{}", addr)); let mut svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout) + .keep_alive(cfg.keep_alive) + .client_request_timeout(cfg.client_request_timeout) + .client_disconnect_timeout(cfg.client_disconnect_timeout) .local_addr(addr); if let Some(handler) = on_connect_fn.clone() { @@ -430,6 +451,51 @@ where })) .tcp() })?; + + Ok(self) + } + + /// Binds to existing listener for accepting incoming plaintext HTTP/1.x or HTTP/2 connections. + pub fn listen_auto_h2c(mut self, lst: net::TcpListener) -> io::Result { + let cfg = self.config.clone(); + let factory = self.factory.clone(); + let addr = lst.local_addr().unwrap(); + + self.sockets.push(Socket { + addr, + scheme: "http", + }); + + let on_connect_fn = self.on_connect_fn.clone(); + + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let cfg = cfg.lock().unwrap(); + let host = cfg.host.clone().unwrap_or_else(|| format!("{}", addr)); + + let mut svc = HttpService::build() + .keep_alive(cfg.keep_alive) + .client_request_timeout(cfg.client_request_timeout) + .client_disconnect_timeout(cfg.client_disconnect_timeout) + .local_addr(addr); + + if let Some(handler) = on_connect_fn.clone() { + svc = svc.on_connect_ext(move |io: &_, ext: _| { + (handler)(io as &dyn Any, ext) + }) + }; + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + svc.finish(map_config(fac, move |_| { + AppConfig::new(false, host.clone(), addr) + })) + .tcp_auto_h2c() + })?; + Ok(self) } From 5d4f5918752a873113863a17e91c16867b6f9bde Mon Sep 17 00:00:00 2001 From: Surya Date: Sat, 22 Apr 2023 13:53:35 +0100 Subject: [PATCH 25/31] fix RUSTSEC-2023-0034 by updating h2 (#3022) --- actix-http/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 235e4e980..7c5f07f2d 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -82,7 +82,7 @@ tokio-util = { version = "0.7", features = ["io", "codec"] } tracing = { version = "0.1.30", default-features = false, features = ["log"] } # http2 -h2 = { version = "0.3.9", optional = true } +h2 = { version = "0.3.17", optional = true } # websockets local-channel = { version = "0.1", optional = true } From de1efa673f13c6263e04acbc6c61b948dfa35b07 Mon Sep 17 00:00:00 2001 From: Yuki Okushi Date: Mon, 24 Apr 2023 04:46:57 +0900 Subject: [PATCH 26/31] Refine GHA workflows (#3023) --- .github/workflows/bench.yml | 14 +++++--------- .github/workflows/ci-post-merge.yml | 24 ++++++++++-------------- .github/workflows/ci.yml | 6 ++---- 3 files changed, 17 insertions(+), 27 deletions(-) diff --git a/.github/workflows/bench.yml b/.github/workflows/bench.yml index a1a31fb8d..f8bf2abda 100644 --- a/.github/workflows/bench.yml +++ b/.github/workflows/bench.yml @@ -20,14 +20,10 @@ jobs: - uses: actions/checkout@v3 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - toolchain: nightly - profile: minimal - override: true + run: | + rustup set profile minimal + rustup install nightly + rustup override set nightly - name: Check benchmark - uses: actions-rs/cargo@v1 - with: - command: bench - args: --bench=server -- --sample-size=15 + run: cargo bench --bench=server -- --sample-size=15 diff --git a/.github/workflows/ci-post-merge.yml b/.github/workflows/ci-post-merge.yml index d47083575..8dd941e04 100644 --- a/.github/workflows/ci-post-merge.yml +++ b/.github/workflows/ci-post-merge.yml @@ -46,28 +46,24 @@ jobs: run: vcpkg install openssl:x64-windows - name: Install ${{ matrix.version }} - uses: actions-rs/toolchain@v1 - with: - toolchain: ${{ matrix.version }} - profile: minimal - override: true + run: | + rustup set profile minimal + rustup install ${{ matrix.version }} + rustup override set ${{ matrix.version }} - name: Install cargo-hack uses: taiki-e/install-action@cargo-hack - name: Generate Cargo.lock - uses: actions-rs/cargo@v1 - with: { command: generate-lockfile } + run: cargo generate-lockfile - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 + uses: Swatinem/rust-cache@v2.2.1 - name: check minimal - uses: actions-rs/cargo@v1 - with: { command: ci-check-min } + run: cargo ci-check-min - name: check default - uses: actions-rs/cargo@v1 - with: { command: ci-check-default } + run: cargo ci-check-default - name: tests timeout-minutes: 60 @@ -107,7 +103,7 @@ jobs: - name: Generate Cargo.lock run: cargo generate-lockfile - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.2.0 + uses: Swatinem/rust-cache@v2.2.1 - name: check feature combinations run: cargo ci-check-all-feature-powerset @@ -134,7 +130,7 @@ jobs: - name: Generate Cargo.lock run: cargo generate-lockfile - name: Cache Dependencies - uses: Swatinem/rust-cache@v1.3.0 + uses: Swatinem/rust-cache@v2.2.1 - name: Test with cargo-nextest run: cargo nextest run diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4c9149722..2d0b71616 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -62,12 +62,10 @@ jobs: cargo update -p=zstd-sys --precise=2.0.1+zstd.1.5.2 - name: check minimal - uses: actions-rs/cargo@v1 - with: { command: ci-check-min } + run: cargo ci-check-min - name: check default - uses: actions-rs/cargo@v1 - with: { command: ci-check-default } + run: cargo ci-check-default - name: tests timeout-minutes: 60 From 8b2b755cdecdd21277ebcf8611f8bf028b05cbb1 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 6 May 2023 11:37:11 +0100 Subject: [PATCH 27/31] fix guard mod docs --- .github/workflows/ci.yml | 1 - actix-web/CHANGES.md | 5 +++-- actix-web/MIGRATION-4.0.md | 1 - actix-web/src/guard/mod.rs | 7 +++++-- 4 files changed, 8 insertions(+), 6 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2d0b71616..e0511339d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -101,7 +101,6 @@ jobs: run: > sudo bash -c "ulimit -Sl 512 && ulimit -Hl 512 && PATH=$PATH:/usr/share/rust/.cargo/bin && RUSTUP_TOOLCHAIN=stable cargo test --lib --tests -p=actix-files --all-features" - rustdoc: name: doc tests runs-on: ubuntu-latest diff --git a/actix-web/CHANGES.md b/actix-web/CHANGES.md index d5989982a..9688370b7 100644 --- a/actix-web/CHANGES.md +++ b/actix-web/CHANGES.md @@ -917,9 +917,9 @@ ### Changed -- Updated actix-web-codegen dependency for access to new `#[route(...)]` multi-method macro. +- Updated `actix-web-codegen` dependency for access to new `#[route(...)]` multi-method macro. - Print non-configured `Data` type when attempting extraction. [#1743] -- Re-export bytes::Buf{Mut} in web module. [#1750] +- Re-export `bytes::Buf{Mut}` in web module. [#1750] - Upgrade `pin-project` to `1.0`. [#1723]: https://github.com/actix/actix-web/pull/1723 @@ -927,6 +927,7 @@ [#1748]: https://github.com/actix/actix-web/pull/1748 [#1750]: https://github.com/actix/actix-web/pull/1750 [#1754]: https://github.com/actix/actix-web/pull/1754 +[#1757]: https://github.com/actix/actix-web/pull/1757 [#1749]: https://github.com/actix/actix-web/pull/1749 ## 3.1.0 - 2020-09-29 diff --git a/actix-web/MIGRATION-4.0.md b/actix-web/MIGRATION-4.0.md index 7b1cfc03b..0f0bd3a53 100644 --- a/actix-web/MIGRATION-4.0.md +++ b/actix-web/MIGRATION-4.0.md @@ -31,7 +31,6 @@ 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 diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs index 164032bdc..a9173a9d1 100644 --- a/actix-web/src/guard/mod.rs +++ b/actix-web/src/guard/mod.rs @@ -18,6 +18,7 @@ //! There are shortcuts for routes with method guards in the [`web`](crate::web) module: //! [`web::get()`](crate::web::get), [`web::post()`](crate::web::post), etc. The routes created by //! the following calls are equivalent: +//! //! - `web::get()` (recommended form) //! - `web::route().guard(guard::Get())` //! @@ -28,9 +29,11 @@ //! would result in inaccessible routes. See the [`Host`] guard for an example of virtual hosting. //! //! # Examples +//! //! In the following code, the `/guarded` resource has one defined route whose handler will only be -//! called if the request method is `POST` and there is a request header with name and value equal -//! to `x-guarded` and `secret`, respectively. +//! called if the request method is GET or POST and there is a `x-guarded` request header with value +//! equal to `secret`. +//! //! ``` //! use actix_web::{web, http::Method, guard, HttpResponse}; //! From 6fdda45ca371a4905d434d61584a4bc4bc944131 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Sat, 6 May 2023 11:38:51 +0100 Subject: [PATCH 28/31] update bitflags to v2 --- actix-files/Cargo.toml | 2 +- actix-files/src/named.rs | 1 + actix-http/Cargo.toml | 2 +- actix-http/src/h1/client.rs | 1 + actix-http/src/h1/codec.rs | 1 + actix-http/src/h1/dispatcher.rs | 1 + actix-http/src/message.rs | 1 + actix-http/src/ws/codec.rs | 1 + 8 files changed, 8 insertions(+), 2 deletions(-) diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 6909c0ef2..41f3b1135 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -26,7 +26,7 @@ actix-service = "2" actix-utils = "3" actix-web = { version = "4", default-features = false } -bitflags = "1" +bitflags = "2" bytes = "1" derive_more = "0.99.5" futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 23d3093dc..c10bc00ed 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -29,6 +29,7 @@ use mime_guess::from_path; use crate::{encoding::equiv_utf8_text, range::HttpRange}; bitflags! { + #[derive(Debug, Clone, Copy)] pub(crate) struct Flags: u8 { const ETAG = 0b0000_0001; const LAST_MD = 0b0000_0010; diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 7c5f07f2d..222a04c8f 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -62,7 +62,7 @@ actix-utils = "3" actix-rt = { version = "2.2", default-features = false } ahash = "0.8" -bitflags = "1.2" +bitflags = "2" bytes = "1" bytestring = "1" derive_more = "0.99.5" diff --git a/actix-http/src/h1/client.rs b/actix-http/src/h1/client.rs index 6a0d531d0..f3947dd12 100644 --- a/actix-http/src/h1/client.rs +++ b/actix-http/src/h1/client.rs @@ -16,6 +16,7 @@ use crate::{ }; bitflags! { + #[derive(Debug, Clone, Copy)] struct Flags: u8 { const HEAD = 0b0000_0001; const KEEP_ALIVE_ENABLED = 0b0000_1000; diff --git a/actix-http/src/h1/codec.rs b/actix-http/src/h1/codec.rs index e11f175c9..e1b629146 100644 --- a/actix-http/src/h1/codec.rs +++ b/actix-http/src/h1/codec.rs @@ -14,6 +14,7 @@ use crate::{ }; bitflags! { + #[derive(Debug, Clone, Copy)] struct Flags: u8 { const HEAD = 0b0000_0001; const KEEP_ALIVE_ENABLED = 0b0000_0010; diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index 60660b85b..c2f8ea453 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -40,6 +40,7 @@ const HW_BUFFER_SIZE: usize = 1024 * 8; const MAX_PIPELINED_MESSAGES: usize = 16; bitflags! { + #[derive(Debug, Clone, Copy)] pub struct Flags: u8 { /// Set when stream is read for first time. const STARTED = 0b0000_0001; diff --git a/actix-http/src/message.rs b/actix-http/src/message.rs index 7469d74ee..47b128fd0 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -16,6 +16,7 @@ pub enum ConnectionType { } bitflags! { + #[derive(Debug, Clone, Copy)] pub(crate) struct Flags: u8 { const CLOSE = 0b0000_0001; const KEEP_ALIVE = 0b0000_0010; diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 6a149f9a4..681649a7e 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -74,6 +74,7 @@ pub struct Codec { } bitflags! { + #[derive(Debug, Clone, Copy)] struct Flags: u8 { const SERVER = 0b0000_0001; const CONTINUATION = 0b0000_0010; From 17218dc6c88848938cebc560deafbf1c2184fa56 Mon Sep 17 00:00:00 2001 From: moh-eulith <101080211+moh-eulith@users.noreply.github.com> Date: Sun, 7 May 2023 11:13:10 -0400 Subject: [PATCH 29/31] minor optimization: reserve buffer once length is known (ws) (#2950) --- actix-http/src/ws/frame.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs index c7e0427ea..dddb03d18 100644 --- a/actix-http/src/ws/frame.rs +++ b/actix-http/src/ws/frame.rs @@ -1,3 +1,4 @@ +use std::cmp::min; use std::convert::TryFrom; use bytes::{Buf, BufMut, BytesMut}; @@ -96,6 +97,10 @@ impl Parser { // not enough data if src.len() < idx + length { + let min_length = min(length, max_size); + if src.capacity() < idx + min_length { + src.reserve(idx + min_length - src.capacity()); + } return Ok(None); } From 58c19b817fc248b4185f3e3d2151deac83c06d02 Mon Sep 17 00:00:00 2001 From: Imamuzzaki Abu Salam Date: Fri, 9 Jun 2023 20:29:10 +0700 Subject: [PATCH 30/31] docs(actix-web/README.md): update benchmark link (#3046) Round 20 doesn't have actix score, but round 21 has. So I changed it to the round 21 link for everyone to see this is one of the best frameworks for Web/API. --- actix-web/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/actix-web/README.md b/actix-web/README.md index 3c6524d36..32932cc0e 100644 --- a/actix-web/README.md +++ b/actix-web/README.md @@ -85,7 +85,7 @@ You may consider checking out [this directory](https://github.com/actix/examples ## Benchmarks -One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite). +One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r21&test=composite). ## License From 1072d0dacf3a5a7a8e2d5fd7fe5bb47658198bf8 Mon Sep 17 00:00:00 2001 From: Rob Ede Date: Fri, 9 Jun 2023 15:15:09 +0100 Subject: [PATCH 31/31] address lints --- actix-http/src/body/message_body.rs | 1 + actix-web/benches/responder.rs | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/actix-http/src/body/message_body.rs b/actix-http/src/body/message_body.rs index e274cf8aa..c3f55ce7d 100644 --- a/actix-http/src/body/message_body.rs +++ b/actix-http/src/body/message_body.rs @@ -555,6 +555,7 @@ mod tests { }; } + #[allow(unused_allocation)] // triggered by `Box::new(()).size()` #[actix_rt::test] async fn boxing_equivalence() { assert_eq!(().size(), BodySize::Sized(0)); diff --git a/actix-web/benches/responder.rs b/actix-web/benches/responder.rs index 20aae3351..ac4d18324 100644 --- a/actix-web/benches/responder.rs +++ b/actix-web/benches/responder.rs @@ -87,7 +87,7 @@ fn future_responder(c: &mut Criterion) { let start = Instant::now(); - let _res = rt.block_on(async { futs.await }); + let _res = rt.block_on(futs); start.elapsed() })