diff --git a/actix-files/CHANGES.md b/actix-files/CHANGES.md index b127cd9ea..5c0a48024 100644 --- a/actix-files/CHANGES.md +++ b/actix-files/CHANGES.md @@ -1,8 +1,14 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 0.6.2 - 2022-07-23 +- Allow partial range responses for video content to start streaming sooner. [#2817] - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +[#2817]: https://github.com/actix/actix-web/pull/2817 + ## 0.6.1 - 2022-06-11 - Add `NamedFile::{modified, metadata, content_type, content_disposition, encoding}()` getters. [#2021] diff --git a/actix-files/Cargo.toml b/actix-files/Cargo.toml index 02543095f..30356d81a 100644 --- a/actix-files/Cargo.toml +++ b/actix-files/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-files" -version = "0.6.1" +version = "0.6.2" authors = [ "Nikolay Kim ", "fakeshadow <24548779@qq.com>", @@ -46,6 +46,6 @@ actix-server = { version = "2.1", optional = true } # ensure matching tokio-urin [dev-dependencies] actix-rt = "2.7" -actix-test = "0.1.0-beta.13" +actix-test = "0.1" actix-web = "4" tempfile = "3.2" diff --git a/actix-files/README.md b/actix-files/README.md index 35db41c9a..c3204a68c 100644 --- a/actix-files/README.md +++ b/actix-files/README.md @@ -3,11 +3,11 @@ > Static file serving for Actix Web [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) -[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.1)](https://docs.rs/actix-files/0.6.1) +[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.2)](https://docs.rs/actix-files/0.6.2) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![License](https://img.shields.io/crates/l/actix-files.svg)
-[![dependency status](https://deps.rs/crate/actix-files/0.6.1/status.svg)](https://deps.rs/crate/actix-files/0.6.1) +[![dependency status](https://deps.rs/crate/actix-files/0.6.2/status.svg)](https://deps.rs/crate/actix-files/0.6.2) [![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-files/src/error.rs b/actix-files/src/error.rs index 6682529f8..2f3a36cd1 100644 --- a/actix-files/src/error.rs +++ b/actix-files/src/error.rs @@ -2,7 +2,7 @@ use actix_web::{http::StatusCode, ResponseError}; use derive_more::Display; /// Errors which can occur when serving static files. -#[derive(Display, Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Display)] pub enum FilesError { /// Path is not a directory #[allow(dead_code)] @@ -22,7 +22,7 @@ impl ResponseError for FilesError { } #[allow(clippy::enum_variant_names)] -#[derive(Display, Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq, Display)] #[non_exhaustive] pub enum UriSegmentError { /// The segment started with the wrapped invalid character. diff --git a/actix-files/src/named.rs b/actix-files/src/named.rs index 5580e6f7e..1213534c2 100644 --- a/actix-files/src/named.rs +++ b/actix-files/src/named.rs @@ -528,11 +528,26 @@ impl NamedFile { length = ranges[0].length; offset = ranges[0].start; - // don't allow compression middleware to modify partial content - res.insert_header(( - header::CONTENT_ENCODING, - HeaderValue::from_static("identity"), - )); + // When a Content-Encoding header is present in a 206 partial content response + // for video content, it prevents browser video players from starting playback + // before loading the whole video and also prevents seeking. + // + // See: https://github.com/actix/actix-web/issues/2815 + // + // The assumption of this fix is that the video player knows to not send an + // Accept-Encoding header for this request and that downstream middleware will + // not attempt compression for requests without it. + // + // TODO: Solve question around what to do if self.encoding is set and partial + // range is requested. Reject request? Ignoring self.encoding seems wrong, too. + // In practice, it should not come up. + if req.headers().contains_key(&header::ACCEPT_ENCODING) { + // don't allow compression middleware to modify partial content + res.insert_header(( + header::CONTENT_ENCODING, + HeaderValue::from_static("identity"), + )); + } res.insert_header(( header::CONTENT_RANGE, diff --git a/actix-files/tests/encoding.rs b/actix-files/tests/encoding.rs index 080292af5..7aec25ff9 100644 --- a/actix-files/tests/encoding.rs +++ b/actix-files/tests/encoding.rs @@ -1,11 +1,11 @@ -use actix_files::Files; +use actix_files::{Files, NamedFile}; use actix_web::{ http::{ header::{self, HeaderValue}, StatusCode, }, test::{self, TestRequest}, - App, + web, App, }; #[actix_web::test] @@ -36,3 +36,31 @@ async fn test_utf8_file_contents() { Some(&HeaderValue::from_static("text/plain")), ); } + +#[actix_web::test] +async fn partial_range_response_encoding() { + let srv = test::init_service(App::new().default_service(web::to(|| async { + NamedFile::open_async("./tests/test.binary").await.unwrap() + }))) + .await; + + // range request without accept-encoding returns no content-encoding header + let req = TestRequest::with_uri("/") + .append_header((header::RANGE, "bytes=10-20")) + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); + assert!(!res.headers().contains_key(header::CONTENT_ENCODING)); + + // range request with accept-encoding returns a content-encoding header + let req = TestRequest::with_uri("/") + .append_header((header::RANGE, "bytes=10-20")) + .append_header((header::ACCEPT_ENCODING, "identity")) + .to_request(); + let res = test::call_service(&srv, req).await; + assert_eq!(res.status(), StatusCode::PARTIAL_CONTENT); + assert_eq!( + res.headers().get(header::CONTENT_ENCODING).unwrap(), + "identity" + ); +} diff --git a/actix-http-test/CHANGES.md b/actix-http-test/CHANGES.md index f91ef4081..9aad2e4ba 100644 --- a/actix-http-test/CHANGES.md +++ b/actix-http-test/CHANGES.md @@ -1,9 +1,24 @@ # Changes ## Unreleased - 2022-xx-xx -- Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. +## 3.0.0 - 2022-07-24 +- `TestServer::stop` is now async and will wait for the server and system to shutdown. [#2442] +- Added `TestServer::client_headers` method. [#2097] +- Update `actix-server` dependency to `2`. +- Update `actix-tls` dependency to `3`. +- Update `bytes` to `1.0`. [#1813] +- Minimum supported Rust version (MSRV) is now 1.57. + +[#2442]: https://github.com/actix/actix-web/pull/2442 +[#2097]: https://github.com/actix/actix-web/pull/2097 +[#1813]: https://github.com/actix/actix-web/pull/1813 + + +
+3.0.0 Pre-Releases + ## 3.0.0-beta.13 - 2022-02-16 - No significant changes since `3.0.0-beta.12`. @@ -69,6 +84,7 @@ [#1813]: https://github.com/actix/actix-web/pull/1813 +
## 2.1.0 - 2020-11-25 - Add ability to set address for `TestServer`. [#1645] diff --git a/actix-http-test/Cargo.toml b/actix-http-test/Cargo.toml index 6f7563ffa..0a9ddf947 100644 --- a/actix-http-test/Cargo.toml +++ b/actix-http-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-http-test" -version = "3.0.0-beta.13" +version = "3.0.0" authors = ["Nikolay Kim "] description = "Various helpers for Actix applications to use during testing" keywords = ["http", "web", "framework", "async", "futures"] diff --git a/actix-http-test/README.md b/actix-http-test/README.md index 9429bb760..ec2bd769c 100644 --- a/actix-http-test/README.md +++ b/actix-http-test/README.md @@ -3,11 +3,11 @@ > Various helpers for Actix applications to use during testing. [![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test) -[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13) +[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0)](https://docs.rs/actix-http-test/3.0.0) ![Version](https://img.shields.io/badge/rustc-1.57+-ab6000.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
-[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13) +[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0) [![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) diff --git a/actix-http/CHANGES.md b/actix-http/CHANGES.md index 7e6604046..785a1b13f 100644 --- a/actix-http/CHANGES.md +++ b/actix-http/CHANGES.md @@ -1,6 +1,10 @@ # Changes ## Unreleased - 2022-xx-xx +### Fixed +- Avoid possibility of dispatcher getting stuck while back-pressuring I/O. [#2369] + +[#2369]: https://github.com/actix/actix-web/pull/2369 ## 3.2.1 - 2022-07-02 @@ -29,9 +33,9 @@ ### Fixed - Revert broken fix in [#2624] that caused erroneous 500 error responses. Temporarily re-introduces [#2357] bug. [#2779] +[#2624]: https://github.com/actix/actix-web/pull/2624 [#2357]: https://github.com/actix/actix-web/issues/2357 -[#2624]: https://github.com/actix/actix-web/issues/2624 -[#2779]: https://github.com/actix/actix-web/issues/2779 +[#2779]: https://github.com/actix/actix-web/pull/2779 ## 3.0.4 - 2022-03-09 @@ -43,14 +47,14 @@ ### Fixed - Allow spaces between header name and colon when parsing responses. [#2684] -[#2684]: https://github.com/actix/actix-web/issues/2684 +[#2684]: https://github.com/actix/actix-web/pull/2684 ## 3.0.2 - 2022-03-05 ### Fixed - Fix encoding camel-case header names with more than one hyphen. [#2683] -[#2683]: https://github.com/actix/actix-web/issues/2683 +[#2683]: https://github.com/actix/actix-web/pull/2683 ## 3.0.1 - 2022-03-04 diff --git a/actix-http/Cargo.toml b/actix-http/Cargo.toml index 03767ca4e..ba8c80e64 100644 --- a/actix-http/Cargo.toml +++ b/actix-http/Cargo.toml @@ -97,7 +97,7 @@ flate2 = { version = "1.0.13", optional = true } zstd = { version = "0.11", optional = true } [dev-dependencies] -actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } +actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" actix-tls = { version = "3", features = ["openssl"] } actix-web = "4" diff --git a/actix-http/src/builder.rs b/actix-http/src/builder.rs index 526a23d53..71b933835 100644 --- a/actix-http/src/builder.rs +++ b/actix-http/src/builder.rs @@ -211,6 +211,7 @@ where /// Finish service configuration and create a HTTP service for 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/config.rs b/actix-http/src/config.rs index ac95a2802..c0d297a20 100644 --- a/actix-http/src/config.rs +++ b/actix-http/src/config.rs @@ -35,7 +35,7 @@ impl Default for ServiceConfig { } impl ServiceConfig { - /// Create instance of `ServiceConfig` + /// Create instance of `ServiceConfig`. pub fn new( keep_alive: KeepAlive, client_request_timeout: Duration, diff --git a/actix-http/src/error.rs b/actix-http/src/error.rs index 3fce0a60b..2d443369d 100644 --- a/actix-http/src/error.rs +++ b/actix-http/src/error.rs @@ -294,6 +294,7 @@ 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), } @@ -351,6 +352,7 @@ 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. @@ -388,7 +390,7 @@ impl StdError for DispatchError { /// A set of error that can occur during parsing content type. #[derive(Debug, Display, Error)] -#[cfg_attr(test, derive(PartialEq))] +#[cfg_attr(test, derive(PartialEq, Eq))] #[non_exhaustive] pub enum ContentTypeError { /// Can not parse content type diff --git a/actix-http/src/h1/chunked.rs b/actix-http/src/h1/chunked.rs index c1dd4b283..4005ed892 100644 --- a/actix-http/src/h1/chunked.rs +++ b/actix-http/src/h1/chunked.rs @@ -15,7 +15,7 @@ macro_rules! byte ( }) ); -#[derive(Debug, PartialEq, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub(super) enum ChunkedState { Size, SizeLws, diff --git a/actix-http/src/h1/decoder.rs b/actix-http/src/h1/decoder.rs index 203b6c531..0b06bfe24 100644 --- a/actix-http/src/h1/decoder.rs +++ b/actix-http/src/h1/decoder.rs @@ -440,7 +440,7 @@ impl HeaderIndex { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] /// Chunk type yielded while decoding a payload. pub enum PayloadItem { Chunk(Bytes), @@ -450,7 +450,7 @@ pub enum PayloadItem { /// Decoder that can handle different payload types. /// /// If a message body does not use `Transfer-Encoding`, it should include a `Content-Length`. -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct PayloadDecoder { kind: Kind, } @@ -476,7 +476,7 @@ impl PayloadDecoder { } } -#[derive(Debug, Clone, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] enum Kind { /// A reader used when a `Content-Length` header is passed with a positive integer. Length(u64), diff --git a/actix-http/src/h1/dispatcher.rs b/actix-http/src/h1/dispatcher.rs index c2ddc06ba..81090667d 100644 --- a/actix-http/src/h1/dispatcher.rs +++ b/actix-http/src/h1/dispatcher.rs @@ -976,9 +976,11 @@ where // // A Request head too large to parse is only checked on `httparse::Status::Partial`. - if this.payload.is_none() { - // When dispatcher has a payload the responsibility of wake up it would be shift - // to h1::payload::Payload. + match this.payload { + // When dispatcher has a payload the responsibility of wake ups is shifted to + // `h1::payload::Payload` unless the payload is needing a read, in which case it + // might not have access to the waker and could result in the dispatcher + // getting stuck until timeout. // // Reason: // Self wake up when there is payload would waste poll and/or result in @@ -989,7 +991,8 @@ where // read anymore. At this case read_buf could always remain beyond // MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and // waste resources. - cx.waker().wake_by_ref(); + Some(ref p) if p.need_read(cx) != PayloadStatus::Read => {} + _ => cx.waker().wake_by_ref(), } return Ok(false); diff --git a/actix-http/src/h1/payload.rs b/actix-http/src/h1/payload.rs index a8c632396..1ed785a1b 100644 --- a/actix-http/src/h1/payload.rs +++ b/actix-http/src/h1/payload.rs @@ -16,7 +16,7 @@ use crate::error::PayloadError; /// max buffer size 32k pub(crate) const MAX_BUFFER_SIZE: usize = 32_768; -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum PayloadStatus { Read, Pause, @@ -252,19 +252,15 @@ impl Inner { #[cfg(test)] mod tests { - use std::panic::{RefUnwindSafe, UnwindSafe}; - use actix_utils::future::poll_fn; use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; assert_impl_all!(Payload: Unpin); - assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); + assert_not_impl_any!(Payload: Send, Sync); assert_impl_all!(Inner: Unpin, Send, Sync); - // assertion not stable wrt rustc versions yet - // assert_impl_all!(Inner: UnwindSafe, RefUnwindSafe); #[actix_rt::test] async fn test_unread_data() { diff --git a/actix-http/src/h1/service.rs b/actix-http/src/h1/service.rs index a791ea8c3..e4d90424d 100644 --- a/actix-http/src/h1/service.rs +++ b/actix-http/src/h1/service.rs @@ -134,6 +134,7 @@ mod openssl { U::InitError: fmt::Debug, { /// Create OpenSSL based service. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -196,6 +197,7 @@ 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/dispatcher.rs b/actix-http/src/h2/dispatcher.rs index 85516cccc..680936f0f 100644 --- a/actix-http/src/h2/dispatcher.rs +++ b/actix-http/src/h2/dispatcher.rs @@ -67,7 +67,7 @@ where timer }) .unwrap_or_else(|| Box::pin(sleep(dur))), - on_flight: false, + in_flight: false, ping_pong: conn.ping_pong().unwrap(), }); @@ -84,9 +84,14 @@ where } struct H2PingPong { - timer: Pin>, - on_flight: bool, + /// Handle to send ping frames from the peer. ping_pong: PingPong, + + /// True when a ping has been sent and is waiting for a reply. + in_flight: bool, + + /// Timeout for pong response. + timer: Pin>, } impl Future for Dispatcher @@ -152,26 +157,28 @@ where }); } Poll::Ready(None) => return Poll::Ready(Ok(())), + Poll::Pending => match this.ping_pong.as_mut() { Some(ping_pong) => loop { - if ping_pong.on_flight { - // When have on flight ping pong. poll pong and and keep alive timer. - // on success pong received update keep alive timer to determine the next timing of - // ping pong. + if ping_pong.in_flight { + // When there is an in-flight ping-pong, poll pong and and keep-alive + // timer. On successful pong received, update keep-alive timer to + // determine the next timing of ping pong. match ping_pong.ping_pong.poll_pong(cx)? { Poll::Ready(_) => { - ping_pong.on_flight = false; + ping_pong.in_flight = false; let dead_line = this.config.keep_alive_deadline().unwrap(); ping_pong.timer.as_mut().reset(dead_line.into()); } Poll::Pending => { - return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())) + return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())); } } } else { - // When there is no on flight ping pong. keep alive timer is used to wait for next - // timing of ping pong. Therefore at this point it serves as an interval instead. + // When there is no in-flight ping-pong, keep-alive timer is used to + // wait for next timing of ping-pong. Therefore, at this point it serves + // as an interval instead. ready!(ping_pong.timer.as_mut().poll(cx)); ping_pong.ping_pong.send_ping(Ping::opaque())?; @@ -179,7 +186,7 @@ where let dead_line = this.config.keep_alive_deadline().unwrap(); ping_pong.timer.as_mut().reset(dead_line.into()); - ping_pong.on_flight = true; + ping_pong.in_flight = true; } }, None => return Poll::Pending, @@ -287,13 +294,13 @@ fn prepare_response( _ => {} } - let _ = match size { - BodySize::None | BodySize::Stream => None, + match size { + BodySize::None | BodySize::Stream => {} BodySize::Sized(0) => { #[allow(clippy::declare_interior_mutable_const)] const HV_ZERO: HeaderValue = HeaderValue::from_static("0"); - res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO) + res.headers_mut().insert(CONTENT_LENGTH, HV_ZERO); } BodySize::Sized(len) => { @@ -302,7 +309,7 @@ fn prepare_response( res.headers_mut().insert( CONTENT_LENGTH, HeaderValue::from_str(buf.format(*len)).unwrap(), - ) + ); } }; diff --git a/actix-http/src/h2/mod.rs b/actix-http/src/h2/mod.rs index c8aaaaa5f..39198e0fe 100644 --- a/actix-http/src/h2/mod.rs +++ b/actix-http/src/h2/mod.rs @@ -103,11 +103,9 @@ where #[cfg(test)] mod tests { - use std::panic::{RefUnwindSafe, UnwindSafe}; - use static_assertions::assert_impl_all; use super::*; - assert_impl_all!(Payload: Unpin, Send, Sync, UnwindSafe, RefUnwindSafe); + assert_impl_all!(Payload: Unpin, Send, Sync); } diff --git a/actix-http/src/h2/service.rs b/actix-http/src/h2/service.rs index e526918c7..2a45fc1dc 100644 --- a/actix-http/src/h2/service.rs +++ b/actix-http/src/h2/service.rs @@ -117,6 +117,7 @@ mod openssl { B: MessageBody + 'static, { /// Create OpenSSL based service. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -164,6 +165,7 @@ 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/header/shared/extended.rs b/actix-http/src/header/shared/extended.rs index 1af9ca20e..cd8adb2bd 100644 --- a/actix-http/src/header/shared/extended.rs +++ b/actix-http/src/header/shared/extended.rs @@ -12,7 +12,7 @@ use crate::header::{Charset, HTTP_VALUE}; /// - A character sequence representing the actual value (`value`), separated by single quotes. /// /// It is defined in [RFC 5987 §3.2](https://datatracker.ietf.org/doc/html/rfc5987#section-3.2). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ExtendedValue { /// The character set that is used to encode the `value` to a string. pub charset: Charset, diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs index 71a3bdd53..0b35b5401 100644 --- a/actix-http/src/header/shared/quality_item.rs +++ b/actix-http/src/header/shared/quality_item.rs @@ -147,7 +147,7 @@ mod tests { // copy of encoding from actix-web headers #[allow(clippy::enum_variant_names)] // allow Encoding prefix on EncodingExt - #[derive(Clone, PartialEq, Debug)] + #[derive(Debug, Clone, PartialEq, Eq)] pub enum Encoding { Chunked, Brotli, diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs index 184049860..864db4986 100644 --- a/actix-http/src/lib.rs +++ b/actix-http/src/lib.rs @@ -40,6 +40,7 @@ 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; @@ -54,6 +55,7 @@ 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; @@ -71,6 +73,7 @@ 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/message.rs b/actix-http/src/message.rs index 5616a4762..7469d74ee 100644 --- a/actix-http/src/message.rs +++ b/actix-http/src/message.rs @@ -3,7 +3,7 @@ use std::{cell::RefCell, ops, rc::Rc}; use bitflags::bitflags; /// Represents various types of connection -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Debug, Clone, Copy, PartialEq, Eq)] pub enum ConnectionType { /// Close connection after response. Close, diff --git a/actix-http/src/payload.rs b/actix-http/src/payload.rs index ee0128af4..7d476c55f 100644 --- a/actix-http/src/payload.rs +++ b/actix-http/src/payload.rs @@ -97,12 +97,10 @@ where #[cfg(test)] mod tests { - use std::panic::{RefUnwindSafe, UnwindSafe}; - use static_assertions::{assert_impl_all, assert_not_impl_any}; use super::*; assert_impl_all!(Payload: Unpin); - assert_not_impl_any!(Payload: Send, Sync, UnwindSafe, RefUnwindSafe); + assert_not_impl_any!(Payload: Send, Sync); } diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs index 27029cb8e..bcca5b188 100644 --- a/actix-http/src/service.rs +++ b/actix-http/src/service.rs @@ -239,6 +239,7 @@ mod openssl { U::InitError: fmt::Debug, { /// Create OpenSSL based service. + #[cfg_attr(docsrs, doc(cfg(feature = "openssl")))] pub fn openssl( self, acceptor: SslAcceptor, @@ -253,6 +254,7 @@ 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, @@ -332,6 +334,7 @@ mod rustls { U::InitError: fmt::Debug, { /// Create Rustls based service. + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn rustls( self, config: ServerConfig, @@ -346,6 +349,7 @@ 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-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs index 3aa325d6a..4a2e741b6 100644 --- a/actix-http/src/ws/codec.rs +++ b/actix-http/src/ws/codec.rs @@ -11,7 +11,7 @@ use super::{ }; /// A WebSocket message. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Message { /// Text message. Text(ByteString), @@ -36,7 +36,7 @@ pub enum Message { } /// A WebSocket frame. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Frame { /// Text frame. Note that the codec does not validate UTF-8 encoding. Text(Bytes), @@ -58,7 +58,7 @@ pub enum Frame { } /// A WebSocket continuation item. -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Item { FirstText(Bytes), FirstBinary(Bytes), diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs index 568d801a2..75d4ca628 100644 --- a/actix-http/src/ws/mod.rs +++ b/actix-http/src/ws/mod.rs @@ -67,7 +67,7 @@ pub enum ProtocolError { } /// WebSocket handshake errors -#[derive(Debug, Clone, Copy, PartialEq, Display, Error)] +#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)] pub enum HandshakeError { /// Only get method is allowed. #[display(fmt = "Method not allowed.")] diff --git a/actix-router/Cargo.toml b/actix-router/Cargo.toml index ceb5b14dc..141b2d39e 100644 --- a/actix-router/Cargo.toml +++ b/actix-router/Cargo.toml @@ -21,7 +21,7 @@ default = ["http"] [dependencies] bytestring = ">=0.1.5, <2" -http = { version = "0.2.3", optional = true } +http = { version = "0.2.5", optional = true } regex = "1.5" serde = "1" tracing = { version = "0.1.30", default-features = false, features = ["log"] } diff --git a/actix-router/src/resource_path.rs b/actix-router/src/resource_path.rs index 00eb0927c..45948aa2a 100644 --- a/actix-router/src/resource_path.rs +++ b/actix-router/src/resource_path.rs @@ -27,7 +27,7 @@ impl<'a> ResourcePath for &'a str { impl ResourcePath for bytestring::ByteString { fn path(&self) -> &str { - &*self + self } } diff --git a/actix-router/src/router.rs b/actix-router/src/router.rs index 8ed966b59..064c5e904 100644 --- a/actix-router/src/router.rs +++ b/actix-router/src/router.rs @@ -1,6 +1,6 @@ use crate::{IntoPatterns, Resource, ResourceDef}; -#[derive(Debug, Copy, Clone, PartialEq)] +#[derive(Debug, Copy, Clone, PartialEq, Eq)] pub struct ResourceId(pub u16); /// Resource router. diff --git a/actix-test/CHANGES.md b/actix-test/CHANGES.md index 43e306bb1..bf5d9324f 100644 --- a/actix-test/CHANGES.md +++ b/actix-test/CHANGES.md @@ -1,6 +1,9 @@ # Changes ## Unreleased - 2022-xx-xx + + +## 0.1.0 - 2022-07-24 - Minimum supported Rust version (MSRV) is now 1.57 due to transitive `time` dependency. diff --git a/actix-test/Cargo.toml b/actix-test/Cargo.toml index 9938be67d..eaea15d47 100644 --- a/actix-test/Cargo.toml +++ b/actix-test/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "actix-test" -version = "0.1.0-beta.13" +version = "0.1.0" authors = [ "Nikolay Kim ", "Rob Ede ", @@ -30,7 +30,7 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"] [dependencies] actix-codec = "0.5" actix-http = "3" -actix-http-test = "3.0.0-beta.13" +actix-http-test = "3" actix-rt = "2.1" actix-service = "2" actix-utils = "3" diff --git a/actix-web-actors/Cargo.toml b/actix-web-actors/Cargo.toml index 284351ed3..8222fc864 100644 --- a/actix-web-actors/Cargo.toml +++ b/actix-web-actors/Cargo.toml @@ -27,7 +27,7 @@ tokio = { version = "1.13.1", features = ["sync"] } [dev-dependencies] actix-rt = "2.2" -actix-test = "0.1.0-beta.13" +actix-test = "0.1" awc = { version = "3", default-features = false } actix-web = { version = "4", features = ["macros"] } diff --git a/actix-web-codegen/Cargo.toml b/actix-web-codegen/Cargo.toml index a2aac7e68..0c3b70589 100644 --- a/actix-web-codegen/Cargo.toml +++ b/actix-web-codegen/Cargo.toml @@ -23,7 +23,7 @@ syn = { version = "1", features = ["full", "extra-traits"] } [dev-dependencies] actix-macros = "0.2.3" actix-rt = "2.2" -actix-test = "0.1.0-beta.13" +actix-test = "0.1" actix-utils = "3.0.0" actix-web = "4.0.0" diff --git a/actix-web/Cargo.toml b/actix-web/Cargo.toml index c58e1604b..12806e686 100644 --- a/actix-web/Cargo.toml +++ b/actix-web/Cargo.toml @@ -84,11 +84,12 @@ derive_more = "0.99.5" encoding_rs = "0.8" futures-core = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false } +http = "0.2.8" itoa = "1" language-tags = "0.3" -once_cell = "1.5" log = "0.4" mime = "0.3" +once_cell = "1.5" pin-project-lite = "0.2.7" regex = "1.5.5" serde = "1.0" @@ -101,7 +102,7 @@ url = "2.1" [dev-dependencies] actix-files = "0.6" -actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } +actix-test = { version = "0.1", features = ["openssl", "rustls"] } awc = { version = "3", features = ["openssl"] } brotli = "3.3.3" diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs index a689d13e0..89104a1ac 100644 --- a/actix-web/src/data.rs +++ b/actix-web/src/data.rs @@ -118,7 +118,7 @@ impl Deref for Data { impl Clone for Data { fn clone(&self) -> Data { - Data(self.0.clone()) + Data(Arc::clone(&self.0)) } } diff --git a/actix-web/src/error/mod.rs b/actix-web/src/error/mod.rs index 6095cd5d2..604c539f3 100644 --- a/actix-web/src/error/mod.rs +++ b/actix-web/src/error/mod.rs @@ -42,7 +42,7 @@ pub struct BlockingError; impl ResponseError for crate::error::BlockingError {} /// Errors which can occur when attempting to generate resource uri. -#[derive(Debug, PartialEq, Display, Error, From)] +#[derive(Debug, PartialEq, Eq, Display, Error, From)] #[non_exhaustive] pub enum UrlGenerationError { /// Resource not found. diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs index 8b7101aa1..0bb459193 100644 --- a/actix-web/src/http/header/content_disposition.rs +++ b/actix-web/src/http/header/content_disposition.rs @@ -10,9 +10,10 @@ //! - Browser conformance tests at: //! - IANA assignment: +use std::fmt::{self, Write}; + use once_cell::sync::Lazy; use regex::Regex; -use std::fmt::{self, Write}; use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer}; use crate::http::header; @@ -36,7 +37,7 @@ fn split_once_and_trim(haystack: &str, needle: char) -> (&str, &str) { } /// The implied disposition of the content of the HTTP body. -#[derive(Clone, Debug, PartialEq)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum DispositionType { /// Inline implies default processing. Inline, @@ -78,7 +79,7 @@ impl<'a> From<&'a str> for DispositionType { /// assert!(param.is_filename()); /// assert_eq!(param.as_filename().unwrap(), "sample.txt"); /// ``` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] #[allow(clippy::large_enum_variant)] pub enum DispositionParam { /// For [`DispositionType::FormData`] (i.e. *multipart/form-data*), the name of an field from @@ -301,7 +302,7 @@ impl DispositionParam { /// change to match local file system conventions if applicable, and do not use directory path /// information that may be present. /// See [RFC 2183 §2.3](https://datatracker.ietf.org/doc/html/rfc2183#section-2.3). -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub struct ContentDisposition { /// The disposition type pub disposition: DispositionType, diff --git a/actix-web/src/http/header/if_range.rs b/actix-web/src/http/header/if_range.rs index b845fb3bf..eb3632a4d 100644 --- a/actix-web/src/http/header/if_range.rs +++ b/actix-web/src/http/header/if_range.rs @@ -57,7 +57,7 @@ use crate::HttpMessage; /// IfRange::Date(fetched.into()) /// ); /// ``` -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Eq)] pub enum IfRange { /// The entity-tag the client has of the resource. EntityTag(EntityTag), diff --git a/actix-web/src/http/header/macros.rs b/actix-web/src/http/header/macros.rs index 25f40a52b..b40eca03b 100644 --- a/actix-web/src/http/header/macros.rs +++ b/actix-web/src/http/header/macros.rs @@ -224,10 +224,11 @@ macro_rules! common_header { // List header, one or more items with "*" option ($(#[$attrs:meta])*($id:ident, $name:expr) => {Any / ($item:ty)+}) => { $(#[$attrs])* - #[derive(Clone, Debug, PartialEq)] + #[derive(Clone, Debug, PartialEq, Eq)] pub enum $id { /// Any value is a match Any, + /// Only the listed items are a match Items(Vec<$item>), } diff --git a/actix-web/src/http/header/range.rs b/actix-web/src/http/header/range.rs index 68028f53a..2326bb19c 100644 --- a/actix-web/src/http/header/range.rs +++ b/actix-web/src/http/header/range.rs @@ -53,7 +53,7 @@ use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderVa /// builder.insert_header(Range::bytes(1, 100)); /// builder.insert_header(Range::bytes_multi(vec![(1, 100), (200, 300)])); /// ``` -#[derive(PartialEq, Clone, Debug)] +#[derive(Debug, Clone, PartialEq, Eq)] pub enum Range { /// Byte range. Bytes(Vec), diff --git a/actix-web/src/info.rs b/actix-web/src/info.rs index 77b98110e..7c685406e 100644 --- a/actix-web/src/info.rs +++ b/actix-web/src/info.rs @@ -2,7 +2,6 @@ use std::{convert::Infallible, net::SocketAddr}; use actix_utils::future::{err, ok, Ready}; use derive_more::{Display, Error}; -use once_cell::sync::Lazy; use crate::{ dev::{AppConfig, Payload, RequestHead}, @@ -13,12 +12,9 @@ use crate::{ FromRequest, HttpRequest, ResponseError, }; -static X_FORWARDED_FOR: Lazy = - Lazy::new(|| HeaderName::from_static("x-forwarded-for")); -static X_FORWARDED_HOST: Lazy = - Lazy::new(|| HeaderName::from_static("x-forwarded-host")); -static X_FORWARDED_PROTO: Lazy = - Lazy::new(|| HeaderName::from_static("x-forwarded-proto")); +static X_FORWARDED_FOR: HeaderName = HeaderName::from_static("x-forwarded-for"); +static X_FORWARDED_HOST: HeaderName = HeaderName::from_static("x-forwarded-host"); +static X_FORWARDED_PROTO: HeaderName = HeaderName::from_static("x-forwarded-proto"); /// Trim whitespace then any quote marks. fn unquote(val: &str) -> &str { @@ -117,21 +113,21 @@ impl ConnectionInfo { } let scheme = scheme - .or_else(|| first_header_value(req, &*X_FORWARDED_PROTO)) + .or_else(|| first_header_value(req, &X_FORWARDED_PROTO)) .or_else(|| req.uri.scheme().map(Scheme::as_str)) .or_else(|| Some("https").filter(|_| cfg.secure())) .unwrap_or("http") .to_owned(); let host = host - .or_else(|| first_header_value(req, &*X_FORWARDED_HOST)) + .or_else(|| first_header_value(req, &X_FORWARDED_HOST)) .or_else(|| req.headers.get(&header::HOST)?.to_str().ok()) .or_else(|| req.uri.authority().map(Authority::as_str)) .unwrap_or_else(|| cfg.host()) .to_owned(); let realip_remote_addr = realip_remote_addr - .or_else(|| first_header_value(req, &*X_FORWARDED_FOR)) + .or_else(|| first_header_value(req, &X_FORWARDED_FOR)) .map(str::to_owned); let peer_addr = req.peer_addr.map(|addr| addr.ip().to_string()); diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs index ed4291bed..51b44c6ef 100644 --- a/actix-web/src/middleware/compress.rs +++ b/actix-web/src/middleware/compress.rs @@ -220,32 +220,25 @@ static SUPPORTED_ENCODINGS_STRING: Lazy = Lazy::new(|| { encoding.join(", ") }); -static SUPPORTED_ENCODINGS: Lazy> = Lazy::new(|| { - let mut encodings = vec![Encoding::identity()]; - +static SUPPORTED_ENCODINGS: &[Encoding] = &[ + Encoding::identity(), #[cfg(feature = "compress-brotli")] { - encodings.push(Encoding::brotli()); - } - + Encoding::brotli() + }, #[cfg(feature = "compress-gzip")] { - encodings.push(Encoding::gzip()); - encodings.push(Encoding::deflate()); - } - + Encoding::gzip() + }, + #[cfg(feature = "compress-gzip")] + { + Encoding::deflate() + }, #[cfg(feature = "compress-zstd")] { - encodings.push(Encoding::zstd()); - } - - assert!( - !encodings.is_empty(), - "encodings can not be empty unless __compress feature has been explicitly enabled by itself" - ); - - encodings -}); + Encoding::zstd() + }, +]; // move cfg(feature) to prevents_double_compressing if more tests are added #[cfg(feature = "compress-gzip")] @@ -326,6 +319,7 @@ mod tests { .to_request(); let res = test::call_service(&app, req).await; assert_eq!(res.status(), StatusCode::OK); + #[allow(clippy::mutable_key_type)] let vary_headers = res.headers().get_all(header::VARY).collect::>(); assert!(vary_headers.contains(&HeaderValue::from_static("x-test"))); assert!(vary_headers.contains(&HeaderValue::from_static("accept-encoding"))); diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs index d26488e2b..e2a9bd4e5 100644 --- a/actix-web/src/request.rs +++ b/actix-web/src/request.rs @@ -253,7 +253,7 @@ impl HttpRequest { #[inline] pub fn connection_info(&self) -> Ref<'_, ConnectionInfo> { if !self.extensions().contains::() { - let info = ConnectionInfo::new(self.head(), &*self.app_config()); + let info = ConnectionInfo::new(self.head(), self.app_config()); self.extensions_mut().insert(info); } @@ -311,6 +311,7 @@ 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; @@ -334,6 +335,7 @@ 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 169eafab0..5021e0a2d 100644 --- a/actix-web/src/server.rs +++ b/actix-web/src/server.rs @@ -41,17 +41,22 @@ 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`. +/// +/// # Examples /// ```no_run /// use actix_web::{web, App, HttpResponse, HttpServer}; /// -/// #[actix_rt::main] +/// #[actix_web::main] /// async fn main() -> std::io::Result<()> { -/// HttpServer::new( -/// || App::new() -/// .service(web::resource("/").to(|| HttpResponse::Ok()))) -/// .bind("127.0.0.1:59090")? -/// .run() -/// .await +/// HttpServer::new(|| { +/// App::new() +/// .service(web::resource("/").to(|| async { "hello world" })) +/// }) +/// .bind(("127.0.0.1", 8080))? +/// .run() +/// .await /// } /// ``` pub struct HttpServer @@ -108,32 +113,7 @@ where } } - /// Sets function that will be called once before each connection is handled. - /// It will receive a `&std::any::Any`, which contains underlying connection type and an - /// [Extensions] container so that connection data can be accessed in middleware and handlers. - /// - /// # Connection Types - /// - `actix_tls::accept::openssl::TlsStream` when using openssl. - /// - `actix_tls::accept::rustls::TlsStream` when using rustls. - /// - `actix_web::rt::net::TcpStream` when no encryption is used. - /// - /// See the `on_connect` example for additional details. - pub fn on_connect(self, f: CB) -> HttpServer - where - CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, - { - HttpServer { - factory: self.factory, - config: self.config, - backlog: self.backlog, - sockets: self.sockets, - builder: self.builder, - on_connect_fn: Some(Arc::new(f)), - _phantom: PhantomData, - } - } - - /// Set number of workers to start. + /// Sets number of workers to start (per bind address). /// /// By default, the number of available physical CPUs is used as the worker count. pub fn workers(mut self, num: usize) -> Self { @@ -141,23 +121,30 @@ where self } - /// Set the maximum number of pending connections. + /// Sets server keep-alive preference. /// - /// This refers to the number of clients that can be waiting to be served. - /// Exceeding this number results in the client getting an error when - /// attempting to connect. It should only affect servers under significant - /// load. + /// By default keep-alive is set to 5 seconds. + pub fn keep_alive>(self, val: T) -> Self { + self.config.lock().unwrap().keep_alive = val.into(); + self + } + + /// Sets the maximum number of pending connections. /// - /// Generally set in the 64-2048 range. Default value is 2048. + /// This refers to the number of clients that can be waiting to be served. Exceeding this number + /// results in the client getting an error when attempting to connect. It should only affect + /// servers under significant load. /// - /// This method should be called before `bind()` method call. + /// Generally set in the 64–2048 range. Default value is 2048. + /// + /// This method will have no effect if called after a `bind()`. pub fn backlog(mut self, backlog: u32) -> Self { self.backlog = backlog; self.builder = self.builder.backlog(backlog); self } - /// Sets the maximum per-worker number of concurrent connections. + /// Sets the per-worker maximum number of concurrent connections. /// /// All socket listeners will stop accepting connections when this limit is reached for /// each worker. @@ -168,7 +155,7 @@ where self } - /// Sets the maximum per-worker concurrent connection establish process. + /// Sets the per-worker maximum concurrent TLS connection limit. /// /// All listeners will stop accepting connections when this limit is reached. It can be used to /// limit the global TLS CPU usage. @@ -181,7 +168,7 @@ where self } - /// Set max number of threads for each worker's blocking task thread pool. + /// Sets max number of threads for each worker's blocking task thread pool. /// /// One thread pool is set up **per worker**; not shared across workers. /// @@ -191,19 +178,10 @@ where self } - /// Set server keep-alive setting. + /// Sets server client timeout for first request. /// - /// By default keep alive is set to a 5 seconds. - pub fn keep_alive>(self, val: T) -> Self { - self.config.lock().unwrap().keep_alive = val.into(); - self - } - - /// Set server client timeout in milliseconds for first request. - /// - /// Defines a timeout for reading client request header. If a client does not transmit - /// the entire set headers within this time, the request is terminated with - /// the 408 (Request Time-out) error. + /// Defines a timeout for reading client request head. If a client does not transmit the entire + /// set headers within this time, the request is terminated with a 408 (Request Timeout) error. /// /// To disable timeout set value to 0. /// @@ -219,10 +197,10 @@ where self.client_request_timeout(dur) } - /// Set server connection shutdown timeout in milliseconds. + /// Sets server connection shutdown timeout. /// - /// Defines a timeout for shutdown connection. If a shutdown procedure does not complete - /// within this time, the request is dropped. + /// Defines a timeout for connection shutdown. If a shutdown procedure does not complete within + /// this time, the request is dropped. /// /// To disable timeout set value to 0. /// @@ -232,10 +210,10 @@ where self } - /// Set TLS handshake timeout. + /// Sets TLS handshake timeout. /// - /// Defines a timeout for TLS handshake. If the TLS handshake does not complete - /// within this time, the connection is closed. + /// Defines a timeout for TLS handshake. If the TLS handshake does not complete within this + /// time, the connection is closed. /// /// By default handshake timeout is set to 3000 milliseconds. #[cfg(any(feature = "openssl", feature = "rustls"))] @@ -256,35 +234,61 @@ where self.client_disconnect_timeout(Duration::from_millis(dur)) } - /// Set server host name. + /// Sets function that will be called once before each connection is handled. /// - /// Host name is used by application router as a hostname for url generation. - /// Check [ConnectionInfo](super::dev::ConnectionInfo::host()) - /// documentation for more information. + /// It will receive a `&std::any::Any`, which contains underlying connection type and an + /// [Extensions] container so that connection data can be accessed in middleware and handlers. /// - /// By default host name is set to a "localhost" value. + /// # Connection Types + /// - `actix_tls::accept::openssl::TlsStream` when using OpenSSL. + /// - `actix_tls::accept::rustls::TlsStream` when using Rustls. + /// - `actix_web::rt::net::TcpStream` when no encryption is used. + /// + /// See the `on_connect` example for additional details. + pub fn on_connect(self, f: CB) -> HttpServer + where + CB: Fn(&dyn Any, &mut Extensions) + Send + Sync + 'static, + { + HttpServer { + factory: self.factory, + config: self.config, + backlog: self.backlog, + sockets: self.sockets, + builder: self.builder, + on_connect_fn: Some(Arc::new(f)), + _phantom: PhantomData, + } + } + + /// Sets server host name. + /// + /// Host name is used by application router as a hostname for url generation. Check + /// [`ConnectionInfo`](crate::dev::ConnectionInfo::host()) docs for more info. + /// + /// By default, hostname is set to "localhost". pub fn server_hostname>(self, val: T) -> Self { self.config.lock().unwrap().host = Some(val.as_ref().to_owned()); self } - /// Stop Actix `System` after server shutdown. + /// Flags the `System` to exit after server shutdown. + /// + /// Does nothing when running under `#[tokio::main]` runtime. pub fn system_exit(mut self) -> Self { self.builder = self.builder.system_exit(); self } - /// Disable signal handling + /// Disables signal handling. pub fn disable_signals(mut self) -> Self { self.builder = self.builder.disable_signals(); self } - /// Timeout for graceful workers shutdown. + /// Sets timeout for graceful worker shutdown of workers. /// - /// After receiving a stop signal, workers have this much time to finish - /// serving requests. Workers still alive after the timeout are force - /// dropped. + /// After receiving a stop signal, workers have this much time to finish serving requests. + /// Workers still alive after the timeout are force dropped. /// /// By default shutdown timeout sets to 30 seconds. pub fn shutdown_timeout(mut self, sec: u64) -> Self { @@ -292,33 +296,141 @@ where self } - /// Get addresses of bound sockets. + /// Returns addresses of bound sockets. pub fn addrs(&self) -> Vec { self.sockets.iter().map(|s| s.addr).collect() } - /// Get addresses of bound sockets and the scheme for it. + /// Returns addresses of bound sockets and the scheme for it. /// - /// This is useful when the server is bound from different sources - /// with some sockets listening on HTTP and some listening on HTTPS - /// and the user should be presented with an enumeration of which - /// socket requires which protocol. + /// This is useful when the server is bound from different sources with some sockets listening + /// on HTTP and some listening on HTTPS and the user should be presented with an enumeration of + /// which socket requires which protocol. pub fn addrs_with_scheme(&self) -> Vec<(net::SocketAddr, &str)> { self.sockets.iter().map(|s| (s.addr, s.scheme)).collect() } - /// Use listener for accepting incoming connection requests + /// Resolves socket address(es) and binds server to created listener(s). /// - /// HttpServer does not change any configuration for TcpListener, - /// it needs to be configured before passing it to listen() method. + /// # Hostname Resolution + /// When `addr` includes a hostname, it is possible for this method to bind to both the IPv4 and + /// IPv6 addresses that result from a DNS lookup. You can test this by passing `localhost:8080` + /// and noting that the server binds to `127.0.0.1:8080` _and_ `[::1]:8080`. To bind additional + /// addresses, call this method multiple times. + /// + /// Note that, if a DNS lookup is required, resolving hostnames is a blocking operation. + /// + /// # Typical Usage + /// In general, use `127.0.0.1:` when testing locally and `0.0.0.0:` when deploying + /// (with or without a reverse proxy or load balancer) so that the server is accessible. + /// + /// # Errors + /// Returns an `io::Error` if: + /// - `addrs` cannot be resolved into one or more socket addresses; + /// - all the resolved socket addresses are already bound. + /// + /// # Example + /// ``` + /// # use actix_web::{App, HttpServer}; + /// # fn inner() -> std::io::Result<()> { + /// HttpServer::new(|| App::new()) + /// .bind(("127.0.0.1", 8080))? + /// .bind("[::1]:9000")? + /// # ; Ok(()) } + /// ``` + pub fn bind(mut self, addrs: A) -> io::Result { + let sockets = self.bind2(addrs)?; + + for lst in sockets { + self = self.listen(lst)?; + } + + 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. + /// + /// See [`bind()`](Self::bind) for more details on `addrs` argument. + /// + /// 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, + config: RustlsServerConfig, + ) -> io::Result { + let sockets = self.bind2(addrs)?; + for lst in sockets { + self = self.listen_rustls_inner(lst, config.clone())?; + } + Ok(self) + } + + /// Resolves socket address(es) and binds server to created listener(s) for TLS connections + /// using OpenSSL. + /// + /// See [`bind()`](Self::bind) for more details on `addrs` argument. + /// + /// 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, + { + let sockets = self.bind2(addrs)?; + let acceptor = openssl_acceptor(builder)?; + + for lst in sockets { + self = self.listen_openssl_inner(lst, acceptor.clone())?; + } + + Ok(self) + } + + /// Binds to existing listener for accepting incoming connection requests. + /// + /// No changes are made to `lst`'s configuration. Ensure it is configured properly before + /// passing ownership to `listen()`. pub fn listen(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 = @@ -351,74 +463,13 @@ where Ok(self) } - #[cfg(feature = "openssl")] - /// Use listener for accepting incoming tls connection requests + /// Binds to existing listener for accepting incoming TLS connection requests using Rustls. /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn listen_openssl( - self, - lst: net::TcpListener, - builder: SslAcceptorBuilder, - ) -> io::Result { - self.listen_ssl_inner(lst, openssl_acceptor(builder)?) - } - - #[cfg(feature = "openssl")] - fn listen_ssl_inner( - mut self, - lst: net::TcpListener, - acceptor: SslAcceptor, - ) -> io::Result { - let factory = self.factory.clone(); - let cfg = self.config.clone(); - let addr = lst.local_addr().unwrap(); - self.sockets.push(Socket { - addr, - scheme: "https", - }); - - let on_connect_fn = self.on_connect_fn.clone(); - - self.builder = - self.builder - .listen(format!("actix-web-service-{}", addr), lst, move || { - let c = cfg.lock().unwrap(); - let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); - - let svc = HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout) - .local_addr(addr); - - let svc = if let Some(handler) = on_connect_fn.clone() { - svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) - } else { - svc - }; - - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); - - let acceptor_config = match c.tls_handshake_timeout { - Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), - None => TlsAcceptorConfig::default(), - }; - - svc.finish(map_config(fac, move |_| { - AppConfig::new(true, host.clone(), addr) - })) - .openssl_with_config(acceptor.clone(), acceptor_config) - })?; - - Ok(self) - } - + /// See [`listen()`](Self::listen) for more details on the `lst` argument. + /// + /// ALPN protocols "h2" and "http/1.1" are added to any configured ones. #[cfg(feature = "rustls")] - /// Use listener for accepting incoming tls connection requests - /// - /// This method prepends alpn protocols "h2" and "http/1.1" to configured ones + #[cfg_attr(docsrs, doc(cfg(feature = "rustls")))] pub fn listen_rustls( self, lst: net::TcpListener, @@ -478,82 +529,125 @@ where Ok(self) } - /// The socket address to bind + /// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL. /// - /// To bind multiple addresses this method can be called multiple times. - pub fn bind(mut self, addr: A) -> io::Result { - let sockets = self.bind2(addr)?; - - for lst in sockets { - self = self.listen(lst)?; - } - - Ok(self) - } - - fn bind2(&self, addr: A) -> io::Result> { - let mut err = None; - let mut success = false; - let mut sockets = Vec::new(); - - for addr in addr.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.", - )) - } + /// See [`listen()`](Self::listen) for more details on the `lst` argument. + /// + /// 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, + builder: SslAcceptorBuilder, + ) -> io::Result { + self.listen_openssl_inner(lst, openssl_acceptor(builder)?) } #[cfg(feature = "openssl")] - /// Start listening for incoming tls connections. - /// - /// This method sets alpn protocols to "h2" and "http/1.1" - pub fn bind_openssl(mut self, addr: A, builder: SslAcceptorBuilder) -> io::Result - where - A: net::ToSocketAddrs, - { - let sockets = self.bind2(addr)?; - let acceptor = openssl_acceptor(builder)?; - - for lst in sockets { - self = self.listen_ssl_inner(lst, acceptor.clone())?; - } - - Ok(self) - } - - #[cfg(feature = "rustls")] - /// Start listening for incoming tls connections. - /// - /// This method prepends alpn protocols "h2" and "http/1.1" to configured ones - pub fn bind_rustls( + fn listen_openssl_inner( mut self, - addr: A, - config: RustlsServerConfig, + lst: net::TcpListener, + acceptor: SslAcceptor, ) -> io::Result { - let sockets = self.bind2(addr)?; - for lst in sockets { - self = self.listen_rustls_inner(lst, config.clone())?; - } + let factory = self.factory.clone(); + let cfg = self.config.clone(); + let addr = lst.local_addr().unwrap(); + self.sockets.push(Socket { + addr, + scheme: "https", + }); + + let on_connect_fn = self.on_connect_fn.clone(); + + self.builder = + self.builder + .listen(format!("actix-web-service-{}", addr), lst, move || { + let c = cfg.lock().unwrap(); + let host = c.host.clone().unwrap_or_else(|| format!("{}", addr)); + + let svc = HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) + .local_addr(addr); + + let svc = if let Some(handler) = on_connect_fn.clone() { + svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext)) + } else { + svc + }; + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + // false positive lint (?) + #[allow(clippy::significant_drop_in_scrutinee)] + let acceptor_config = match c.tls_handshake_timeout { + Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur), + None => TlsAcceptorConfig::default(), + }; + + svc.finish(map_config(fac, move |_| { + AppConfig::new(true, host.clone(), addr) + })) + .openssl_with_config(acceptor.clone(), acceptor_config) + })?; + Ok(self) } + /// Opens Unix Domain Socket (UDS) from `uds` path and binds server to created listener. + #[cfg(unix)] + pub fn bind_uds(mut self, uds_path: A) -> io::Result + where + A: AsRef, + { + use actix_http::Protocol; + use actix_rt::net::UnixStream; + use actix_service::{fn_service, ServiceFactoryExt as _}; + + let cfg = self.config.clone(); + let factory = self.factory.clone(); + let socket_addr = + net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); + + self.sockets.push(Socket { + scheme: "http", + addr: socket_addr, + }); + + self.builder = self.builder.bind_uds( + format!("actix-web-service-{:?}", uds_path.as_ref()), + uds_path, + move || { + let c = cfg.lock().unwrap(); + let config = AppConfig::new( + false, + c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), + socket_addr, + ); + + let fac = factory() + .into_factory() + .map_err(|err| err.into().error_response()); + + fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( + HttpService::build() + .keep_alive(c.keep_alive) + .client_request_timeout(c.client_request_timeout) + .client_disconnect_timeout(c.client_disconnect_timeout) + .finish(map_config(fac, move |_| config.clone())), + ) + }, + )?; + + Ok(self) + } + + /// Binds to existing Unix Domain Socket (UDS) listener. #[cfg(unix)] - /// Start listening for unix domain (UDS) connections on existing listener. pub fn listen_uds(mut self, lst: std::os::unix::net::UnixListener) -> io::Result { use actix_http::Protocol; use actix_rt::net::UnixStream; @@ -600,54 +694,6 @@ where })?; Ok(self) } - - /// Start listening for incoming unix domain connections. - #[cfg(unix)] - pub fn bind_uds(mut self, addr: A) -> io::Result - where - A: AsRef, - { - use actix_http::Protocol; - use actix_rt::net::UnixStream; - use actix_service::{fn_service, ServiceFactoryExt as _}; - - let cfg = self.config.clone(); - let factory = self.factory.clone(); - let socket_addr = - net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080); - - self.sockets.push(Socket { - scheme: "http", - addr: socket_addr, - }); - - self.builder = self.builder.bind_uds( - format!("actix-web-service-{:?}", addr.as_ref()), - addr, - move || { - let c = cfg.lock().unwrap(); - let config = AppConfig::new( - false, - c.host.clone().unwrap_or_else(|| format!("{}", socket_addr)), - socket_addr, - ); - - let fac = factory() - .into_factory() - .map_err(|err| err.into().error_response()); - - fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then( - HttpService::build() - .keep_alive(c.keep_alive) - .client_request_timeout(c.client_request_timeout) - .client_disconnect_timeout(c.client_disconnect_timeout) - .finish(map_config(fac, move |_| config.clone())), - ) - }, - )?; - - Ok(self) - } } impl HttpServer @@ -663,25 +709,16 @@ where { /// Start listening for incoming connections. /// - /// This method starts number of HTTP workers in separate threads. - /// For each address this method starts separate thread which does - /// `accept()` in a loop. + /// # Workers + /// This method starts a number of HTTP workers in separate threads. The number of workers in a + /// set is defined by [`workers()`](Self::workers) or, by default, the number of the machine's + /// physical cores. One worker set is created for each socket address to be bound. For example, + /// if workers is set to 4, and there are 2 addresses to bind, then 8 worker threads will be + /// spawned. /// - /// This methods panics if no socket address can be bound or an `Actix` system is not yet - /// configured. - /// - /// ```no_run - /// use std::io; - /// use actix_web::{web, App, HttpResponse, HttpServer}; - /// - /// #[actix_rt::main] - /// async fn main() -> io::Result<()> { - /// HttpServer::new(|| App::new().service(web::resource("/").to(|| HttpResponse::Ok()))) - /// .bind("127.0.0.1:0")? - /// .run() - /// .await - /// } - /// ``` + /// # Panics + /// This methods panics if no socket addresses were successfully bound or if no Tokio runtime + /// is set up. pub fn run(self) -> Server { self.builder.run() } diff --git a/actix-web/src/types/either.rs b/actix-web/src/types/either.rs index c0faf04b1..119dd0d62 100644 --- a/actix-web/src/types/either.rs +++ b/actix-web/src/types/either.rs @@ -73,7 +73,7 @@ use crate::{ /// } /// } /// ``` -#[derive(Debug, PartialEq)] +#[derive(Debug, PartialEq, Eq)] pub enum Either { /// A value of type `L`. Left(L), diff --git a/actix-web/src/types/path.rs b/actix-web/src/types/path.rs index 34c335ba9..a90c912f6 100644 --- a/actix-web/src/types/path.rs +++ b/actix-web/src/types/path.rs @@ -134,6 +134,7 @@ where /// ``` #[derive(Clone, Default)] pub struct PathConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, } diff --git a/actix-web/src/types/query.rs b/actix-web/src/types/query.rs index 97d17123d..e71b886f2 100644 --- a/actix-web/src/types/query.rs +++ b/actix-web/src/types/query.rs @@ -169,6 +169,7 @@ impl FromRequest for Query { /// ``` #[derive(Clone, Default)] pub struct QueryConfig { + #[allow(clippy::type_complexity)] err_handler: Option Error + Send + Sync>>, } diff --git a/awc/Cargo.toml b/awc/Cargo.toml index 9da103cb0..0250091bf 100644 --- a/awc/Cargo.toml +++ b/awc/Cargo.toml @@ -94,9 +94,9 @@ trust-dns-resolver = { version = "0.21", optional = true } [dev-dependencies] actix-http = { version = "3", features = ["openssl"] } -actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] } +actix-http-test = { version = "3", features = ["openssl"] } actix-server = "2" -actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] } +actix-test = { version = "0.1", features = ["openssl", "rustls"] } actix-tls = { version = "3", features = ["openssl", "rustls"] } actix-utils = "3" actix-web = { version = "4", features = ["openssl"] } diff --git a/scripts/bump b/scripts/bump index 209e2281d..33ea52010 100755 --- a/scripts/bump +++ b/scripts/bump @@ -98,7 +98,7 @@ rm -f $README_FILE.bak echo "manifest, changelog, and readme updated" echo echo "check other references:" -rg --glob='**/Cargo.toml' "\ +rg --glob='**/{Cargo.toml,README.md}' "\ ${PACKAGE_NAME} ?= ?\"[^\"]+\"\ |${PACKAGE_NAME} ?=.*version ?= ?\"([^\"]+)\"\ |package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\"([^\"]+)\"\