,
+
flags: Flags,
}
diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs
index 1750fb2f7..6a267a7a6 100644
--- a/actix-http/src/requests/request.rs
+++ b/actix-http/src/requests/request.rs
@@ -173,7 +173,7 @@ impl Request
{
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
/// the Actix Web server, then it would be address of this proxy.
///
- /// Will only return None when called in unit tests.
+ /// Will only return None when called in unit tests unless set manually.
#[inline]
pub fn peer_addr(&self) -> Option {
self.head().peer_addr
diff --git a/actix-http/src/responses/builder.rs b/actix-http/src/responses/builder.rs
index 063af92da..91c69ba54 100644
--- a/actix-http/src/responses/builder.rs
+++ b/actix-http/src/responses/builder.rs
@@ -93,7 +93,7 @@ impl ResponseBuilder {
Ok((key, value)) => {
parts.headers.insert(key, value);
}
- Err(e) => self.err = Some(e.into()),
+ Err(err) => self.err = Some(err.into()),
};
}
@@ -119,7 +119,7 @@ impl ResponseBuilder {
if let Some(parts) = self.inner() {
match header.try_into_pair() {
Ok((key, value)) => parts.headers.append(key, value),
- Err(e) => self.err = Some(e.into()),
+ Err(err) => self.err = Some(err.into()),
};
}
@@ -193,7 +193,7 @@ impl ResponseBuilder {
Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value);
}
- Err(e) => self.err = Some(e.into()),
+ Err(err) => self.err = Some(err.into()),
};
}
self
diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs
index e118d8361..a58be93c7 100644
--- a/actix-http/src/service.rs
+++ b/actix-http/src/service.rs
@@ -241,13 +241,25 @@ where
}
/// Configuration options used when accepting TLS connection.
-#[cfg(any(feature = "openssl", feature = "rustls"))]
+#[cfg(any(
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22",
+ feature = "rustls-0_23",
+))]
#[derive(Debug, Default)]
pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option,
}
-#[cfg(any(feature = "openssl", feature = "rustls"))]
+#[cfg(any(
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22",
+ feature = "rustls-0_23",
+))]
impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
@@ -352,13 +364,13 @@ mod openssl {
}
}
-#[cfg(feature = "rustls")]
-mod rustls {
+#[cfg(feature = "rustls-0_20")]
+mod rustls_0_20 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
- rustls::{reexports::ServerConfig, Acceptor, TlsStream},
+ rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
@@ -389,7 +401,7 @@ mod rustls {
U::Error: fmt::Display + Into>,
U::InitError: fmt::Debug,
{
- /// Create Rustls based service.
+ /// Create Rustls v0.20 based service.
pub fn rustls(
self,
config: ServerConfig,
@@ -403,7 +415,7 @@ mod rustls {
self.rustls_with_config(config, TlsAcceptorConfig::default())
}
- /// Create Rustls based service with custom TLS acceptor configuration.
+ /// Create Rustls v0.20 based service with custom TLS acceptor configuration.
pub fn rustls_with_config(
self,
mut config: ServerConfig,
@@ -448,6 +460,294 @@ mod rustls {
}
}
+#[cfg(feature = "rustls-0_21")]
+mod rustls_0_21 {
+ use std::io;
+
+ use actix_service::ServiceFactoryExt as _;
+ use actix_tls::accept::{
+ rustls_0_21::{reexports::ServerConfig, Acceptor, TlsStream},
+ TlsError,
+ };
+
+ use super::*;
+
+ impl HttpService, S, B, X, U>
+ where
+ S: ServiceFactory,
+ S::Future: 'static,
+ S::Error: Into> + 'static,
+ S::InitError: fmt::Debug,
+ S::Response: Into> + 'static,
+ >::Future: 'static,
+
+ B: MessageBody + 'static,
+
+ X: ServiceFactory,
+ X::Future: 'static,
+ X::Error: Into>,
+ X::InitError: fmt::Debug,
+
+ U: ServiceFactory<
+ (Request, Framed, h1::Codec>),
+ Config = (),
+ Response = (),
+ >,
+ U::Future: 'static,
+ U::Error: fmt::Display + Into>,
+ U::InitError: fmt::Debug,
+ {
+ /// Create Rustls v0.21 based service.
+ pub fn rustls_021(
+ self,
+ config: ServerConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ self.rustls_021_with_config(config, TlsAcceptorConfig::default())
+ }
+
+ /// Create Rustls v0.21 based service with custom TLS acceptor configuration.
+ pub fn rustls_021_with_config(
+ self,
+ mut config: ServerConfig,
+ tls_acceptor_config: TlsAcceptorConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
+ protos.extend_from_slice(&config.alpn_protocols);
+ config.alpn_protocols = protos;
+
+ let mut acceptor = Acceptor::new(config);
+
+ if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
+ acceptor.set_handshake_timeout(handshake_timeout);
+ }
+
+ acceptor
+ .map_init_err(|_| {
+ unreachable!("TLS acceptor service factory does not error on init")
+ })
+ .map_err(TlsError::into_service_error)
+ .and_then(|io: TlsStream| async {
+ let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
+ if protos.windows(2).any(|window| window == b"h2") {
+ Protocol::Http2
+ } else {
+ Protocol::Http1
+ }
+ } else {
+ Protocol::Http1
+ };
+ let peer_addr = io.get_ref().0.peer_addr().ok();
+ Ok((io, proto, peer_addr))
+ })
+ .and_then(self.map_err(TlsError::Service))
+ }
+ }
+}
+
+#[cfg(feature = "rustls-0_22")]
+mod rustls_0_22 {
+ use std::io;
+
+ use actix_service::ServiceFactoryExt as _;
+ use actix_tls::accept::{
+ rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
+ TlsError,
+ };
+
+ use super::*;
+
+ impl HttpService, S, B, X, U>
+ where
+ S: ServiceFactory,
+ S::Future: 'static,
+ S::Error: Into> + 'static,
+ S::InitError: fmt::Debug,
+ S::Response: Into> + 'static,
+ >::Future: 'static,
+
+ B: MessageBody + 'static,
+
+ X: ServiceFactory,
+ X::Future: 'static,
+ X::Error: Into>,
+ X::InitError: fmt::Debug,
+
+ U: ServiceFactory<
+ (Request, Framed, h1::Codec>),
+ Config = (),
+ Response = (),
+ >,
+ U::Future: 'static,
+ U::Error: fmt::Display + Into>,
+ U::InitError: fmt::Debug,
+ {
+ /// Create Rustls v0.22 based service.
+ pub fn rustls_0_22(
+ self,
+ config: ServerConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ self.rustls_0_22_with_config(config, TlsAcceptorConfig::default())
+ }
+
+ /// Create Rustls v0.22 based service with custom TLS acceptor configuration.
+ pub fn rustls_0_22_with_config(
+ self,
+ mut config: ServerConfig,
+ tls_acceptor_config: TlsAcceptorConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
+ protos.extend_from_slice(&config.alpn_protocols);
+ config.alpn_protocols = protos;
+
+ let mut acceptor = Acceptor::new(config);
+
+ if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
+ acceptor.set_handshake_timeout(handshake_timeout);
+ }
+
+ acceptor
+ .map_init_err(|_| {
+ unreachable!("TLS acceptor service factory does not error on init")
+ })
+ .map_err(TlsError::into_service_error)
+ .and_then(|io: TlsStream| async {
+ let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
+ if protos.windows(2).any(|window| window == b"h2") {
+ Protocol::Http2
+ } else {
+ Protocol::Http1
+ }
+ } else {
+ Protocol::Http1
+ };
+ let peer_addr = io.get_ref().0.peer_addr().ok();
+ Ok((io, proto, peer_addr))
+ })
+ .and_then(self.map_err(TlsError::Service))
+ }
+ }
+}
+
+#[cfg(feature = "rustls-0_23")]
+mod rustls_0_23 {
+ use std::io;
+
+ use actix_service::ServiceFactoryExt as _;
+ use actix_tls::accept::{
+ rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
+ TlsError,
+ };
+
+ use super::*;
+
+ impl HttpService, S, B, X, U>
+ where
+ S: ServiceFactory,
+ S::Future: 'static,
+ S::Error: Into> + 'static,
+ S::InitError: fmt::Debug,
+ S::Response: Into> + 'static,
+ >::Future: 'static,
+
+ B: MessageBody + 'static,
+
+ X: ServiceFactory,
+ X::Future: 'static,
+ X::Error: Into>,
+ X::InitError: fmt::Debug,
+
+ U: ServiceFactory<
+ (Request, Framed, h1::Codec>),
+ Config = (),
+ Response = (),
+ >,
+ U::Future: 'static,
+ U::Error: fmt::Display + Into>,
+ U::InitError: fmt::Debug,
+ {
+ /// Create Rustls v0.23 based service.
+ pub fn rustls_0_23(
+ self,
+ config: ServerConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ self.rustls_0_23_with_config(config, TlsAcceptorConfig::default())
+ }
+
+ /// Create Rustls v0.23 based service with custom TLS acceptor configuration.
+ pub fn rustls_0_23_with_config(
+ self,
+ mut config: ServerConfig,
+ tls_acceptor_config: TlsAcceptorConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
+ protos.extend_from_slice(&config.alpn_protocols);
+ config.alpn_protocols = protos;
+
+ let mut acceptor = Acceptor::new(config);
+
+ if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
+ acceptor.set_handshake_timeout(handshake_timeout);
+ }
+
+ acceptor
+ .map_init_err(|_| {
+ unreachable!("TLS acceptor service factory does not error on init")
+ })
+ .map_err(TlsError::into_service_error)
+ .and_then(|io: TlsStream| async {
+ let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
+ if protos.windows(2).any(|window| window == b"h2") {
+ Protocol::Http2
+ } else {
+ Protocol::Http1
+ }
+ } else {
+ Protocol::Http1
+ };
+ let peer_addr = io.get_ref().0.peer_addr().ok();
+ Ok((io, proto, peer_addr))
+ })
+ .and_then(self.map_err(TlsError::Service))
+ }
+ }
+}
+
impl ServiceFactory<(T, Protocol, Option)>
for HttpService
where
diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs
index 681649a7e..ad487e400 100644
--- a/actix-http/src/ws/codec.rs
+++ b/actix-http/src/ws/codec.rs
@@ -296,7 +296,7 @@ impl Decoder for Codec {
}
}
Ok(None) => Ok(None),
- Err(e) => Err(e),
+ Err(err) => Err(err),
}
}
}
diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs
index c9fb0cde9..35b3f8e66 100644
--- a/actix-http/src/ws/frame.rs
+++ b/actix-http/src/ws/frame.rs
@@ -178,14 +178,14 @@ impl Parser {
};
if payload_len < 126 {
- dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
+ dst.reserve(p_len + 2);
dst.put_slice(&[one, two | payload_len as u8]);
} else if payload_len <= 65_535 {
- dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
+ dst.reserve(p_len + 4);
dst.put_slice(&[one, two | 126]);
dst.put_u16(payload_len as u16);
} else {
- dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
+ dst.reserve(p_len + 10);
dst.put_slice(&[one, two | 127]);
dst.put_u64(payload_len as u64);
};
diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs
index 87f9b38f3..3ed53b70a 100644
--- a/actix-http/src/ws/mod.rs
+++ b/actix-http/src/ws/mod.rs
@@ -221,7 +221,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{header, test::TestRequest, Method};
+ use crate::{header, test::TestRequest};
#[test]
fn test_handshake() {
diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs
index 0653c00b0..27815eaf2 100644
--- a/actix-http/src/ws/proto.rs
+++ b/actix-http/src/ws/proto.rs
@@ -1,7 +1,4 @@
-use std::{
- convert::{From, Into},
- fmt,
-};
+use std::fmt;
use base64::prelude::*;
use tracing::error;
diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs
index b4d8ed1a5..4dd22b585 100644
--- a/actix-http/tests/test_openssl.rs
+++ b/actix-http/tests/test_openssl.rs
@@ -1,5 +1,4 @@
#![cfg(feature = "openssl")]
-#![allow(clippy::uninlined_format_args)]
extern crate tls_openssl as openssl;
@@ -43,9 +42,11 @@ where
}
fn tls_config() -> SslAcceptor {
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
+
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs
index 3d9a39cbd..3ca0d94c2 100644
--- a/actix-http/tests/test_rustls.rs
+++ b/actix-http/tests/test_rustls.rs
@@ -1,7 +1,6 @@
-#![cfg(feature = "rustls")]
-#![allow(clippy::uninlined_format_args)]
+#![cfg(feature = "rustls-0_23")]
-extern crate tls_rustls as rustls;
+extern crate tls_rustls_023 as rustls;
use std::{
convert::Infallible,
@@ -21,13 +20,13 @@ use actix_http::{
use actix_http_test::test_server;
use actix_rt::pin;
use actix_service::{fn_factory_with_config, fn_service};
-use actix_tls::connect::rustls::webpki_roots_cert_store;
+use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error};
use futures_core::{ready, Stream};
use futures_util::stream::once;
-use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
+use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
async fn load_body(stream: S) -> Result
@@ -53,24 +52,25 @@ where
}
fn tls_config() -> RustlsServerConfig {
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
- let cert_chain = certs(cert_file)
- .unwrap()
- .into_iter()
- .map(Certificate)
- .collect();
- let mut keys = pkcs8_private_keys(key_file).unwrap();
+ let cert_chain = certs(cert_file).collect::, _>>().unwrap();
+ let mut keys = pkcs8_private_keys(key_file)
+ .collect::, _>>()
+ .unwrap();
let mut config = RustlsServerConfig::builder()
- .with_safe_defaults()
.with_no_client_auth()
- .with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
+ .with_single_cert(
+ cert_chain,
+ rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
+ )
.unwrap();
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
@@ -84,7 +84,6 @@ pub fn get_negotiated_alpn_protocol(
client_alpn_protocol: &[u8],
) -> Option> {
let mut config = rustls::ClientConfig::builder()
- .with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth();
@@ -110,7 +109,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -124,7 +123,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -142,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok())
})
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -160,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok())
})
- .rustls_with_config(
+ .rustls_0_23_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
@@ -181,7 +180,7 @@ async fn h2_body1() -> io::Result<()> {
let body = load_body(req.take_payload()).await?;
Ok::<_, Error>(Response::ok().set_body(body))
})
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -207,7 +206,7 @@ async fn h2_content_length() {
];
ok::<_, Infallible>(Response::new(statuses[indx]))
})
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -279,7 +278,7 @@ async fn h2_headers() {
}
ok::<_, Infallible>(config.body(data.clone()))
})
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -318,7 +317,7 @@ async fn h2_body2() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -335,7 +334,7 @@ async fn h2_head_empty() {
let mut srv = test_server(move || {
HttpService::build()
.finish(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -361,7 +360,7 @@ async fn h2_head_binary() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -386,7 +385,7 @@ async fn h2_head_binary2() {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -412,7 +411,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
)
})
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -436,7 +435,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)),
)
})
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -465,7 +464,7 @@ async fn h2_response_http_error_handling() {
)
}))
}))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -495,7 +494,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| err::, _>(BadRequest))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -512,7 +511,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h1(|_| err::, _>(BadRequest))
- .rustls(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -535,7 +534,7 @@ async fn alpn_h1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
- .rustls(config)
+ .rustls_0_23(config)
})
.await;
@@ -557,7 +556,7 @@ async fn alpn_h2() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
- .rustls(config)
+ .rustls_0_23(config)
})
.await;
@@ -583,7 +582,7 @@ async fn alpn_h2_1() -> io::Result<()> {
config.alpn_protocols.push(CUSTOM_ALPN_PROTOCOL.to_vec());
HttpService::build()
.finish(|_| ok::<_, Error>(Response::ok()))
- .rustls(config)
+ .rustls_0_23(config)
})
.await;
diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs
index f55b1b36c..4ba64a53c 100644
--- a/actix-http/tests/test_server.rs
+++ b/actix-http/tests/test_server.rs
@@ -147,7 +147,7 @@ async fn chunked_payload() {
.take_payload()
.map(|res| match res {
Ok(pl) => pl,
- Err(e) => panic!("Error reading payload: {}", e),
+ Err(err) => panic!("Error reading payload: {err}"),
})
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
.map(|req_size| {
diff --git a/actix-http/tests/test_ws.rs b/actix-http/tests/test_ws.rs
index a2866613b..9a78074c4 100644
--- a/actix-http/tests/test_ws.rs
+++ b/actix-http/tests/test_ws.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use std::{
cell::Cell,
convert::Infallible,
diff --git a/actix-multipart-derive/CHANGES.md b/actix-multipart-derive/CHANGES.md
index f90b9e3cf..1b44ba4b7 100644
--- a/actix-multipart-derive/CHANGES.md
+++ b/actix-multipart-derive/CHANGES.md
@@ -2,9 +2,13 @@
## Unreleased
+- Minimum supported Rust version (MSRV) is now 1.72.
+
+## 0.6.1
+
- Update `syn` dependency to `2`.
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
-## 0.6.0 - 2023-02-26
+## 0.6.0
- Add `MultipartForm` derive macro.
diff --git a/actix-multipart-derive/Cargo.toml b/actix-multipart-derive/Cargo.toml
index 41e687e50..e978864a3 100644
--- a/actix-multipart-derive/Cargo.toml
+++ b/actix-multipart-derive/Cargo.toml
@@ -1,13 +1,14 @@
[package]
name = "actix-multipart-derive"
-version = "0.6.0"
+version = "0.6.1"
authors = ["Jacob Halsey "]
description = "Multipart form derive macro for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"]
-homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-web.git"
-license = "MIT OR Apache-2.0"
-edition = "2021"
+homepage.workspace = true
+repository.workspace = true
+license.workspace = true
+edition.workspace = true
+rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
diff --git a/actix-multipart-derive/README.md b/actix-multipart-derive/README.md
index 2737410f6..ec0afffdd 100644
--- a/actix-multipart-derive/README.md
+++ b/actix-multipart-derive/README.md
@@ -1,17 +1,16 @@
-# actix-multipart-derive
+# `actix-multipart-derive`
> The derive macro implementation for actix-multipart-derive.
+
+
[](https://crates.io/crates/actix-multipart-derive)
-[](https://docs.rs/actix-multipart-derive/0.5.0)
-
+[](https://docs.rs/actix-multipart-derive/0.6.1)
+

-[](https://deps.rs/crate/actix-multipart-derive/0.5.0)
+[](https://deps.rs/crate/actix-multipart-derive/0.6.1)
[](https://crates.io/crates/actix-multipart-derive)
[](https://discord.gg/NWpN5mmg3x)
-## Documentation & Resources
-
-- [API Documentation](https://docs.rs/actix-multipart-derive)
-- Minimum Supported Rust Version (MSRV): 1.68
+
diff --git a/actix-multipart-derive/tests/trybuild.rs b/actix-multipart-derive/tests/trybuild.rs
index 88aa619c6..6b25d78df 100644
--- a/actix-multipart-derive/tests/trybuild.rs
+++ b/actix-multipart-derive/tests/trybuild.rs
@@ -1,4 +1,4 @@
-#[rustversion::stable(1.68)] // MSRV
+#[rustversion::stable(1.72)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();
diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md
index 1ad5865ec..a91edf9c8 100644
--- a/actix-multipart/CHANGES.md
+++ b/actix-multipart/CHANGES.md
@@ -1,49 +1,56 @@
# Changes
-## Unreleased - 2023-xx-xx
+## Unreleased
+
+## 0.6.2
+
+- Add testing utilities under new module `test`.
+- Minimum supported Rust version (MSRV) is now 1.72.
+
+## 0.6.1
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
-## 0.6.0 - 2023-02-26
+## 0.6.0
- Added `MultipartForm` typed data extractor. [#2883]
[#2883]: https://github.com/actix/actix-web/pull/2883
-## 0.5.0 - 2023-01-21
+## 0.5.0
- `Field::content_type()` now returns `Option<&mime::Mime>`. [#2885]
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
[#2885]: https://github.com/actix/actix-web/pull/2885
-## 0.4.0 - 2022-02-25
+## 0.4.0
- No significant changes since `0.4.0-beta.13`.
-## 0.4.0-beta.13 - 2022-01-31
+## 0.4.0-beta.13
- No significant changes since `0.4.0-beta.12`.
-## 0.4.0-beta.12 - 2022-01-04
+## 0.4.0-beta.12
- Minimum supported Rust version (MSRV) is now 1.54.
-## 0.4.0-beta.11 - 2021-12-27
+## 0.4.0-beta.11
- No significant changes since `0.4.0-beta.10`.
-## 0.4.0-beta.10 - 2021-12-11
+## 0.4.0-beta.10
- No significant changes since `0.4.0-beta.9`.
-## 0.4.0-beta.9 - 2021-12-01
+## 0.4.0-beta.9
- Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
[#2463]: https://github.com/actix/actix-web/pull/2463
-## 0.4.0-beta.8 - 2021-11-22
+## 0.4.0-beta.8
- Ensure a correct Content-Disposition header is included in every part of a multipart message. [#2451]
- Added `MultipartError::NoContentDisposition` variant. [#2451]
@@ -54,31 +61,31 @@
[#2451]: https://github.com/actix/actix-web/pull/2451
-## 0.4.0-beta.7 - 2021-10-20
+## 0.4.0-beta.7
- Minimum supported Rust version (MSRV) is now 1.52.
-## 0.4.0-beta.6 - 2021-09-09
+## 0.4.0-beta.6
- Minimum supported Rust version (MSRV) is now 1.51.
-## 0.4.0-beta.5 - 2021-06-17
+## 0.4.0-beta.5
- No notable changes.
-## 0.4.0-beta.4 - 2021-04-02
+## 0.4.0-beta.4
- No notable changes.
-## 0.4.0-beta.3 - 2021-03-09
+## 0.4.0-beta.3
- No notable changes.
-## 0.4.0-beta.2 - 2021-02-10
+## 0.4.0-beta.2
- No notable changes.
-## 0.4.0-beta.1 - 2021-01-07
+## 0.4.0-beta.1
- Fix multipart consuming payload before header checks. [#1513]
- Update `bytes` to `1.0`. [#1813]
@@ -86,19 +93,19 @@
[#1813]: https://github.com/actix/actix-web/pull/1813
[#1513]: https://github.com/actix/actix-web/pull/1513
-## 0.3.0 - 2020-09-11
+## 0.3.0
- No significant changes from `0.3.0-beta.2`.
-## 0.3.0-beta.2 - 2020-09-10
+## 0.3.0-beta.2
- Update `actix-*` dependencies to latest versions.
-## 0.3.0-beta.1 - 2020-07-15
+## 0.3.0-beta.1
- Update `actix-web` to 3.0.0-beta.1
-## 0.3.0-alpha.1 - 2020-05-25
+## 0.3.0-alpha.1
- Update `actix-web` to 3.0.0-alpha.3
- Bump minimum supported Rust version to 1.40
@@ -106,45 +113,45 @@
- Remove the unused `time` dependency
- Fix missing `std::error::Error` implement for `MultipartError`.
-## [0.2.0] - 2019-12-20
+## 0.2.0
- Release
-## [0.2.0-alpha.4] - 2019-12-xx
+## 0.2.0-alpha.4
- Multipart handling now handles Pending during read of boundary #1205
-## [0.2.0-alpha.2] - 2019-12-03
+## 0.2.0-alpha.2
- Migrate to `std::future`
-## [0.1.4] - 2019-09-12
+## 0.1.4
- Multipart handling now parses requests which do not end in CRLF #1038
-## [0.1.3] - 2019-08-18
+## 0.1.3
- Fix ring dependency from actix-web default features for #741.
-## [0.1.2] - 2019-06-02
+## 0.1.2
- Fix boundary parsing #876
-## [0.1.1] - 2019-05-25
+## 0.1.1
- Fix disconnect handling #834
-## [0.1.0] - 2019-05-18
+## 0.1.0
- Release
-## [0.1.0-beta.4] - 2019-05-12
+## 0.1.0-beta.4
- Handle cancellation of uploads #736
- Upgrade to actix-web 1.0.0-beta.4
-## [0.1.0-beta.1] - 2019-04-21
+## 0.1.0-beta.1
- Do not support nested multipart
diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml
index 9220e793c..f1289d3a2 100644
--- a/actix-multipart/Cargo.toml
+++ b/actix-multipart/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-multipart"
-version = "0.6.0"
+version = "0.6.2"
authors = [
"Nikolay Kim ",
"Jacob Halsey ",
@@ -8,7 +8,7 @@ authors = [
description = "Multipart form support for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"]
homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-web.git"
+repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
@@ -22,7 +22,7 @@ derive = ["actix-multipart-derive"]
tempfile = ["dep:tempfile", "tokio/fs"]
[dependencies]
-actix-multipart-derive = { version = "=0.6.0", optional = true }
+actix-multipart-derive = { version = "=0.6.1", optional = true }
actix-utils = "3"
actix-web = { version = "4", default-features = false }
@@ -35,6 +35,7 @@ local-waker = "0.1"
log = "0.4"
memchr = "2.5"
mime = "0.3"
+rand = "0.8"
serde = "1"
serde_json = "1"
serde_plain = "1"
@@ -46,7 +47,9 @@ actix-http = "3"
actix-multipart-rfc7578 = "0.10"
actix-rt = "2.2"
actix-test = "0.1"
+actix-web = "4"
awc = "3"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
+multer = "3"
tokio = { version = "1.24.2", features = ["sync"] }
tokio-stream = "0.1"
diff --git a/actix-multipart/README.md b/actix-multipart/README.md
index 66550668d..c7697785a 100644
--- a/actix-multipart/README.md
+++ b/actix-multipart/README.md
@@ -1,17 +1,77 @@
-# actix-multipart
+# `actix-multipart`
> Multipart form support for Actix Web.
+
+
[](https://crates.io/crates/actix-multipart)
-[](https://docs.rs/actix-multipart/0.6.0)
-
+[](https://docs.rs/actix-multipart/0.6.2)
+

-[](https://deps.rs/crate/actix-multipart/0.6.0)
+[](https://deps.rs/crate/actix-multipart/0.6.2)
[](https://crates.io/crates/actix-multipart)
[](https://discord.gg/NWpN5mmg3x)
-## Documentation & Resources
+
-- [API Documentation](https://docs.rs/actix-multipart)
-- Minimum Supported Rust Version (MSRV): 1.68
+## Example
+
+Dependencies:
+
+```toml
+[dependencies]
+actix-multipart = "0.6"
+actix-web = "4.5"
+serde = { version = "1.0", features = ["derive"] }
+```
+
+Code:
+
+```rust
+use actix_web::{post, App, HttpServer, Responder};
+
+use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+struct Metadata {
+ name: String,
+}
+
+#[derive(Debug, MultipartForm)]
+struct UploadForm {
+ #[multipart(limit = "100MB")]
+ file: TempFile,
+ json: MPJson,
+}
+
+#[post("/videos")]
+pub async fn post_video(MultipartForm(form): MultipartForm) -> impl Responder {
+ format!(
+ "Uploaded file {}, with size: {}",
+ form.json.name, form.file.size
+ )
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ HttpServer::new(move || App::new().service(post_video))
+ .bind(("127.0.0.1", 8080))?
+ .run()
+ .await
+}
+```
+
+Curl request :
+
+```bash
+curl -v --request POST \
+ --url http://localhost:8080/videos \
+ -F 'json={"name": "Cargo.lock"};type=application/json' \
+ -F file=@./Cargo.lock
+```
+
+### Examples
+
+https://github.com/actix/examples/tree/master/forms/multipart
diff --git a/actix-multipart/src/form/json.rs b/actix-multipart/src/form/json.rs
index fb90a82b9..bb4e03bf6 100644
--- a/actix-multipart/src/form/json.rs
+++ b/actix-multipart/src/form/json.rs
@@ -131,14 +131,13 @@ impl Default for JsonConfig {
#[cfg(test)]
mod tests {
- use std::{collections::HashMap, io::Cursor};
+ use std::collections::HashMap;
- use actix_multipart_rfc7578::client::multipart;
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
+ use bytes::Bytes;
use crate::form::{
json::{Json, JsonConfig},
- tests::send_form,
MultipartForm,
};
@@ -155,6 +154,8 @@ mod tests {
HttpResponse::Ok().finish()
}
+ const TEST_JSON: &str = r#"{"key1": "value1", "key2": "value2"}"#;
+
#[actix_rt::test]
async fn test_json_without_content_type() {
let srv = actix_test::start(|| {
@@ -163,10 +164,16 @@ mod tests {
.app_data(JsonConfig::default().validate_content_type(false))
});
- let mut form = multipart::Form::default();
- form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}");
- let response = send_form(&srv, form, "/").await;
- assert_eq!(response.status(), StatusCode::OK);
+ let (body, headers) = crate::test::create_form_data_payload_and_headers(
+ "json",
+ None,
+ None,
+ Bytes::from_static(TEST_JSON.as_bytes()),
+ );
+ let mut req = srv.post("/");
+ *req.headers_mut() = headers;
+ let res = req.send_body(body).await.unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
}
#[actix_rt::test]
@@ -178,17 +185,27 @@ mod tests {
});
// Deny because wrong content type
- let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
- let mut form = multipart::Form::default();
- form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM);
- let response = send_form(&srv, form, "/").await;
- assert_eq!(response.status(), StatusCode::BAD_REQUEST);
+ let (body, headers) = crate::test::create_form_data_payload_and_headers(
+ "json",
+ None,
+ Some(mime::APPLICATION_OCTET_STREAM),
+ Bytes::from_static(TEST_JSON.as_bytes()),
+ );
+ let mut req = srv.post("/");
+ *req.headers_mut() = headers;
+ let res = req.send_body(body).await.unwrap();
+ assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// Allow because correct content type
- let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
- let mut form = multipart::Form::default();
- form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON);
- let response = send_form(&srv, form, "/").await;
- assert_eq!(response.status(), StatusCode::OK);
+ let (body, headers) = crate::test::create_form_data_payload_and_headers(
+ "json",
+ None,
+ Some(mime::APPLICATION_JSON),
+ Bytes::from_static(TEST_JSON.as_bytes()),
+ );
+ let mut req = srv.post("/");
+ *req.headers_mut() = headers;
+ let res = req.send_body(body).await.unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
}
}
diff --git a/actix-multipart/src/form/mod.rs b/actix-multipart/src/form/mod.rs
index 67adfd4b2..451b103fd 100644
--- a/actix-multipart/src/form/mod.rs
+++ b/actix-multipart/src/form/mod.rs
@@ -313,7 +313,8 @@ where
let entry = field_limits
.entry(field.name().to_owned())
.or_insert_with(|| T::limit(field.name()));
- limits.field_limit_remaining = entry.to_owned();
+
+ limits.field_limit_remaining.clone_from(entry);
T::handle_field(&req, field, &mut limits, &mut state).await?;
diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs
index 615a8e6de..d19e951e6 100644
--- a/actix-multipart/src/lib.rs
+++ b/actix-multipart/src/lib.rs
@@ -1,8 +1,43 @@
//! Multipart form support for Actix Web.
+//! # Examples
+//! ```no_run
+//! use actix_web::{post, App, HttpServer, Responder};
+//!
+//! use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
+//! use serde::Deserialize;
+//!
+//! #[derive(Debug, Deserialize)]
+//! struct Metadata {
+//! name: String,
+//! }
+//!
+//! #[derive(Debug, MultipartForm)]
+//! struct UploadForm {
+//! #[multipart(limit = "100MB")]
+//! file: TempFile,
+//! json: MPJson,
+//! }
+//!
+//! #[post("/videos")]
+//! pub async fn post_video(MultipartForm(form): MultipartForm) -> impl Responder {
+//! format!(
+//! "Uploaded file {}, with size: {}",
+//! form.json.name, form.file.size
+//! )
+//! }
+//!
+//! #[actix_web::main]
+//! async fn main() -> std::io::Result<()> {
+//! HttpServer::new(move || App::new().service(post_video))
+//! .bind(("127.0.0.1", 8080))?
+//! .run()
+//! .await
+//! }
+//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
-#![allow(clippy::borrow_interior_mutable_const, clippy::uninlined_format_args)]
+#![allow(clippy::borrow_interior_mutable_const)]
#![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))]
@@ -13,11 +48,14 @@ extern crate self as actix_multipart;
mod error;
mod extractor;
-mod server;
-
pub mod form;
+mod server;
+pub mod test;
pub use self::{
error::MultipartError,
server::{Field, Multipart},
+ test::{
+ create_form_data_payload_and_headers, create_form_data_payload_and_headers_with_boundary,
+ },
};
diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs
index b20429040..d0f833318 100644
--- a/actix-multipart/src/server.rs
+++ b/actix-multipart/src/server.rs
@@ -252,7 +252,7 @@ impl InnerMultipart {
fn poll(
&mut self,
safety: &Safety,
- cx: &mut Context<'_>,
+ cx: &Context<'_>,
) -> Poll
-[](https://crates.io/crates/actix-web) [](https://docs.rs/actix-web/4.3.1)   [](https://deps.rs/crate/actix-web/4.3.1)
[](https://github.com/actix/actix-web/actions/workflows/ci.yml) [](https://codecov.io/gh/actix/actix-web)  [](https://discord.gg/NWpN5mmg3x)
+
+
+[](https://crates.io/crates/actix-web)
+[](https://docs.rs/actix-web/4.7.0)
+
+
+[](https://deps.rs/crate/actix-web/4.7.0)
+
+[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
+[](https://codecov.io/gh/actix/actix-web)
+
+[](https://discord.gg/NWpN5mmg3x)
+
+
@@ -24,7 +37,7 @@
- SSL support using OpenSSL or Rustls
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
-- Runs on stable Rust 1.68+
+- Runs on stable Rust 1.72+
## Documentation
diff --git a/actix-web/benches/server.rs b/actix-web/benches/server.rs
index 2c9f71dc5..0d45c9403 100644
--- a/actix-web/benches/server.rs
+++ b/actix-web/benches/server.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use actix_web::{web, App, HttpResponse};
use awc::Client;
use criterion::{criterion_group, criterion_main, Criterion};
diff --git a/actix-web/examples/macroless.rs b/actix-web/examples/macroless.rs
index d3589da21..78ffd45c1 100644
--- a/actix-web/examples/macroless.rs
+++ b/actix-web/examples/macroless.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer};
async fn index(req: HttpRequest) -> &'static str {
diff --git a/actix-web/examples/uds.rs b/actix-web/examples/uds.rs
index 15e28ba1d..e854bb3b1 100644
--- a/actix-web/examples/uds.rs
+++ b/actix-web/examples/uds.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use actix_web::{get, web, HttpRequest};
#[cfg(unix)]
use actix_web::{middleware, App, Error, HttpResponse, HttpServer};
diff --git a/actix-web/examples/worker-cpu-pin.rs b/actix-web/examples/worker-cpu-pin.rs
new file mode 100644
index 000000000..58e060821
--- /dev/null
+++ b/actix-web/examples/worker-cpu-pin.rs
@@ -0,0 +1,41 @@
+use std::{
+ io,
+ sync::{
+ atomic::{AtomicUsize, Ordering},
+ Arc,
+ },
+ thread,
+};
+
+use actix_web::{middleware, web, App, HttpServer};
+
+async fn hello() -> &'static str {
+ "Hello world!"
+}
+
+#[actix_web::main]
+async fn main() -> io::Result<()> {
+ env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
+
+ let core_ids = core_affinity::get_core_ids().unwrap();
+ let n_core_ids = core_ids.len();
+ let next_core_id = Arc::new(AtomicUsize::new(0));
+
+ HttpServer::new(move || {
+ let pin = Arc::clone(&next_core_id).fetch_add(1, Ordering::AcqRel);
+ log::info!(
+ "setting CPU affinity for worker {}: pinning to core {}",
+ thread::current().name().unwrap(),
+ pin,
+ );
+ core_affinity::set_for_current(core_ids[pin]);
+
+ App::new()
+ .wrap(middleware::Logger::default())
+ .service(web::resource("/").get(hello))
+ })
+ .bind(("127.0.0.1", 8080))?
+ .workers(n_core_ids)
+ .run()
+ .await
+}
diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs
index a4cd8d819..3d86d1f9b 100644
--- a/actix-web/src/app.rs
+++ b/actix-web/src/app.rs
@@ -112,8 +112,8 @@ where
/// })
/// ```
#[doc(alias = "manage")]
- pub fn app_data(mut self, ext: U) -> Self {
- self.extensions.insert(ext);
+ pub fn app_data(mut self, data: U) -> Self {
+ self.extensions.insert(data);
self
}
@@ -129,6 +129,8 @@ where
///
/// Data items are constructed during application initialization, before the server starts
/// accepting requests.
+ ///
+ /// The returned data value `D` is wrapped as [`Data`].
pub fn data_factory(mut self, data: F) -> Self
where
F: Fn() -> Out + 'static,
@@ -141,8 +143,8 @@ where
let fut = data();
async move {
match fut.await {
- Err(e) => {
- log::error!("Can not construct data instance: {:?}", e);
+ Err(err) => {
+ log::error!("Can not construct data instance: {err:?}");
Err(())
}
Ok(data) => {
@@ -469,7 +471,6 @@ mod tests {
Method, StatusCode,
},
middleware::DefaultHeaders,
- service::ServiceRequest,
test::{call_service, init_service, read_body, try_init_service, TestRequest},
web, HttpRequest, HttpResponse,
};
diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs
index 513e7a8a1..65a6ed87b 100644
--- a/actix-web/src/app_service.rs
+++ b/actix-web/src/app_service.rs
@@ -112,11 +112,7 @@ where
let endpoint_fut = self.endpoint.new_service(());
// take extensions or create new one as app data container.
- let mut app_data = self
- .extensions
- .borrow_mut()
- .take()
- .unwrap_or_else(Extensions::new);
+ let mut app_data = self.extensions.borrow_mut().take().unwrap_or_default();
Box::pin(async move {
// async data factories
@@ -267,8 +263,9 @@ impl ServiceFactory for AppRoutingFactory {
let guards = guards.borrow_mut().take().unwrap_or_default();
let factory_fut = factory.new_service(());
async move {
- let service = factory_fut.await?;
- Ok((path, guards, service))
+ factory_fut
+ .await
+ .map(move |service| (path, guards, service))
}
}));
diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs
index fba0c2717..5e8b056f1 100644
--- a/actix-web/src/config.rs
+++ b/actix-web/src/config.rs
@@ -148,7 +148,7 @@ impl AppConfig {
#[cfg(test)]
pub(crate) fn set_host(&mut self, host: &str) {
- self.host = host.to_owned();
+ host.clone_into(&mut self.host);
}
}
diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs
index 423dd598c..acbb8e23a 100644
--- a/actix-web/src/data.rs
+++ b/actix-web/src/data.rs
@@ -3,7 +3,7 @@ use std::{any::type_name, ops::Deref, sync::Arc};
use actix_http::Extensions;
use actix_utils::future::{err, ok, Ready};
use futures_core::future::LocalBoxFuture;
-use serde::Serialize;
+use serde::{de, Serialize};
use crate::{dev::Payload, error, Error, FromRequest, HttpRequest};
@@ -32,8 +32,8 @@ pub(crate) type FnDataFactory =
/// Since the Actix Web router layers application data, the returned object will reference the
/// "closest" instance of the type. For example, if an `App` stores a `u32`, a nested `Scope`
/// also stores a `u32`, and the delegated request handler falls within that `Scope`, then
-/// extracting a `web::>` for that handler will return the `Scope`'s instance.
-/// However, using the same router set up and a request that does not get captured by the `Scope`,
+/// extracting a `web::Data` for that handler will return the `Scope`'s instance. However,
+/// using the same router set up and a request that does not get captured by the `Scope`,
/// `web::>` would return the `App`'s instance.
///
/// If route data is not set for a handler, using `Data` extractor would cause a `500 Internal
@@ -69,7 +69,7 @@ pub(crate) type FnDataFactory =
/// HttpResponse::Ok()
/// }
///
-/// /// Alteratively, use the `HttpRequest::app_data` method to access data in a handler.
+/// /// Alternatively, use the `HttpRequest::app_data` method to access data in a handler.
/// async fn index_alt(req: HttpRequest) -> impl Responder {
/// let data = req.app_data::>>().unwrap();
/// let mut my_data = data.lock().unwrap();
@@ -128,6 +128,12 @@ impl From> for Data {
}
}
+impl Default for Data {
+ fn default() -> Self {
+ Data::new(T::default())
+ }
+}
+
impl Serialize for Data
where
T: Serialize,
@@ -139,6 +145,17 @@ where
self.0.serialize(serializer)
}
}
+impl<'de, T> de::Deserialize<'de> for Data
+where
+ T: de::Deserialize<'de>,
+{
+ fn deserialize(deserializer: D) -> Result
+ where
+ D: de::Deserializer<'de>,
+ {
+ Ok(Data::new(T::deserialize(deserializer)?))
+ }
+}
impl FromRequest for Data {
type Error = Error;
diff --git a/actix-web/src/error/error.rs b/actix-web/src/error/error.rs
index 3a5a128f6..670a58a00 100644
--- a/actix-web/src/error/error.rs
+++ b/actix-web/src/error/error.rs
@@ -60,6 +60,12 @@ impl From for Error {
}
}
+impl From> for Error {
+ fn from(value: Box) -> Self {
+ Error { cause: value }
+ }
+}
+
impl From for Response {
fn from(err: Error) -> Response {
err.error_response().into()
diff --git a/actix-web/src/error/mod.rs b/actix-web/src/error/mod.rs
index 91a6bcc3f..25535332c 100644
--- a/actix-web/src/error/mod.rs
+++ b/actix-web/src/error/mod.rs
@@ -100,6 +100,7 @@ impl ResponseError for UrlencodedError {
match self {
Self::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE,
Self::UnknownLength => StatusCode::LENGTH_REQUIRED,
+ Self::ContentType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
Self::Payload(err) => err.status_code(),
_ => StatusCode::BAD_REQUEST,
}
@@ -232,7 +233,7 @@ mod tests {
let resp = UrlencodedError::UnknownLength.error_response();
assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED);
let resp = UrlencodedError::ContentType.error_response();
- assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
+ assert_eq!(resp.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
}
#[test]
diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs
index a2afd3827..249b56114 100644
--- a/actix-web/src/extract.rs
+++ b/actix-web/src/extract.rs
@@ -175,8 +175,8 @@ where
let res = ready!(this.fut.poll(cx));
match res {
Ok(t) => Poll::Ready(Ok(Some(t))),
- Err(e) => {
- log::debug!("Error for Option extractor: {}", e.into());
+ Err(err) => {
+ log::debug!("Error for Option extractor: {}", err.into());
Poll::Ready(Ok(None))
}
}
@@ -217,8 +217,8 @@ where
/// /// extract `Thing` from request
/// async fn index(supplied_thing: Result) -> String {
/// match supplied_thing {
-/// Ok(thing) => format!("Got thing: {:?}", thing),
-/// Err(e) => format!("Error extracting thing: {}", e)
+/// Ok(thing) => format!("Got thing: {thing:?}"),
+/// Err(err) => format!("Error extracting thing: {err}"),
/// }
/// }
///
@@ -355,7 +355,7 @@ mod tuple_from_req {
Poll::Ready(Ok(output)) => {
let _ = this.$T.as_mut().project_replace(ExtractFuture::Done { output });
},
- Poll::Ready(Err(e)) => return Poll::Ready(Err(e.into())),
+ Poll::Ready(Err(err)) => return Poll::Ready(Err(err.into())),
Poll::Pending => ready = false,
},
ExtractProj::Done { .. } => {},
diff --git a/actix-web/src/guard/acceptable.rs b/actix-web/src/guard/acceptable.rs
index a31494a18..8fa7165c8 100644
--- a/actix-web/src/guard/acceptable.rs
+++ b/actix-web/src/guard/acceptable.rs
@@ -20,7 +20,7 @@ use crate::http::header::Accept;
pub struct Acceptable {
mime: mime::Mime,
- /// Wether to match `*/*` mime type.
+ /// Whether to match `*/*` mime type.
///
/// Defaults to false because it's not very useful otherwise.
match_star_star: bool,
diff --git a/actix-web/src/guard/host.rs b/actix-web/src/guard/host.rs
index f05c81183..a971a3e30 100644
--- a/actix-web/src/guard/host.rs
+++ b/actix-web/src/guard/host.rs
@@ -2,7 +2,7 @@ use actix_http::{header, uri::Uri, RequestHead};
use super::{Guard, GuardContext};
-/// Creates a guard that matches requests targetting a specific host.
+/// Creates a guard that matches requests targeting a specific host.
///
/// # Matching Host
/// This guard will:
diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs
index 35294a3c4..41609953a 100644
--- a/actix-web/src/guard/mod.rs
+++ b/actix-web/src/guard/mod.rs
@@ -110,6 +110,12 @@ impl<'a> GuardContext<'a> {
pub fn header(&self) -> Option {
H::parse(self.req).ok()
}
+
+ /// Counterpart to [HttpRequest::app_data](crate::HttpRequest::app_data).
+ #[inline]
+ pub fn app_data(&self) -> Option<&T> {
+ self.req.app_data()
+ }
}
/// Interface for routing guards.
@@ -380,7 +386,7 @@ impl Guard for HeaderGuard {
#[cfg(test)]
mod tests {
- use actix_http::{header, Method};
+ use actix_http::Method;
use super::*;
use crate::test::TestRequest;
@@ -512,4 +518,18 @@ mod tests {
.to_srv_request();
assert!(guard.check(&req.guard_ctx()));
}
+
+ #[test]
+ fn app_data() {
+ const TEST_VALUE: u32 = 42;
+ let guard = fn_guard(|ctx| dbg!(ctx.app_data::()) == Some(&TEST_VALUE));
+
+ let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
+ assert!(guard.check(&req.guard_ctx()));
+
+ let req = TestRequest::default()
+ .app_data(TEST_VALUE * 2)
+ .to_srv_request();
+ assert!(!guard.check(&req.guard_ctx()));
+ }
}
diff --git a/actix-web/src/handler.rs b/actix-web/src/handler.rs
index 0c5e58e28..6e4e2250a 100644
--- a/actix-web/src/handler.rs
+++ b/actix-web/src/handler.rs
@@ -10,10 +10,12 @@ use crate::{
/// The interface for request handlers.
///
/// # What Is A Request Handler
+///
/// In short, a handler is just an async function that receives request-based arguments, in any
/// order, and returns something that can be converted to a response.
///
/// In particular, a request handler has three requirements:
+///
/// 1. It is an async function (or a function/closure that returns an appropriate future);
/// 1. The function parameters (up to 12) implement [`FromRequest`];
/// 1. The async function (or future) resolves to a type that can be converted into an
@@ -21,11 +23,15 @@ use crate::{
///
///
/// # Compiler Errors
+///
/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not
-/// fulfill the _first_ of the above requirements. Missing other requirements manifest as errors on
-/// implementing [`FromRequest`] and [`Responder`], respectively.
+/// fulfill the _first_ of the above requirements. (It could also mean that you're attempting to use
+/// a macro-routed handler in a manual routing context like `web::get().to(handler)`, which is not
+/// supported). Breaking the other requirements manifests as errors on implementing [`FromRequest`]
+/// and [`Responder`], respectively.
///
/// # How Do Handlers Receive Variable Numbers Of Arguments
+///
/// Rest assured there is no macro magic here; it's just traits.
///
/// The first thing to note is that [`FromRequest`] is implemented for tuples (up to 12 in length).
@@ -40,6 +46,7 @@ use crate::{
/// destructures the tuple into its component types and calls your handler function with them.
///
/// In pseudo-code the process looks something like this:
+///
/// ```ignore
/// async fn my_handler(body: String, state: web::Data) -> impl Responder {
/// ...
@@ -167,7 +174,7 @@ mod tests {
async fn handler_min() {}
#[rustfmt::skip]
- #[allow(clippy::too_many_arguments, clippy::just_underscores_and_digits)]
+ #[allow(clippy::too_many_arguments, clippy::just_underscores_and_digits, clippy::let_unit_value)]
async fn handler_max(
_01: (), _02: (), _03: (), _04: (), _05: (), _06: (),
_07: (), _08: (), _09: (), _10: (), _11: (), _12: (),
diff --git a/actix-web/src/http/header/accept_encoding.rs b/actix-web/src/http/header/accept_encoding.rs
index cc80e7bb0..19d649926 100644
--- a/actix-web/src/http/header/accept_encoding.rs
+++ b/actix-web/src/http/header/accept_encoding.rs
@@ -149,7 +149,7 @@ impl AcceptEncoding {
/// Extracts the most preferable encoding, accounting for [q-factor weighting].
///
- /// If no q-factors are provided, the first encoding is chosen. Note that items without
+ /// If no q-factors are provided, we prefer brotli > zstd > gzip. Note that items without
/// q-factors are given the maximum preference value.
///
/// As per the spec, returns [`Preference::Any`] if acceptable list is empty. Though, if this is
@@ -167,6 +167,7 @@ impl AcceptEncoding {
let mut max_item = None;
let mut max_pref = Quality::ZERO;
+ let mut max_rank = 0;
// uses manual max lookup loop since we want the first occurrence in the case of same
// preference but `Iterator::max_by_key` would give us the last occurrence
@@ -174,9 +175,13 @@ impl AcceptEncoding {
for pref in &self.0 {
// only change if strictly greater
// equal items, even while unsorted, still have higher preference if they appear first
- if pref.quality > max_pref {
+
+ let rank = encoding_rank(pref);
+
+ if (pref.quality, rank) > (max_pref, max_rank) {
max_pref = pref.quality;
max_item = Some(pref.item.clone());
+ max_rank = rank;
}
}
@@ -203,6 +208,8 @@ impl AcceptEncoding {
/// Returns a sorted list of encodings from highest to lowest precedence, accounting
/// for [q-factor weighting].
///
+ /// If no q-factors are provided, we prefer brotli > zstd > gzip.
+ ///
/// [q-factor weighting]: https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.2
pub fn ranked(&self) -> Vec> {
self.ranked_items().map(|q| q.item).collect()
@@ -210,21 +217,44 @@ impl AcceptEncoding {
fn ranked_items(&self) -> impl Iterator- >> {
if self.0.is_empty() {
- return vec![].into_iter();
+ return Vec::new().into_iter();
}
let mut types = self.0.clone();
// use stable sort so items with equal q-factor retain listed order
types.sort_by(|a, b| {
- // sort by q-factor descending
- b.quality.cmp(&a.quality)
+ // sort by q-factor descending then server ranking descending
+
+ b.quality
+ .cmp(&a.quality)
+ .then(encoding_rank(b).cmp(&encoding_rank(a)))
});
types.into_iter()
}
}
+/// Returns server-defined encoding ranking.
+fn encoding_rank(qv: &QualityItem>) -> u8 {
+ // ensure that q=0 items are never sorted above identity encoding
+ // invariant: sorting methods calling this fn use first-on-equal approach
+ if qv.quality == Quality::ZERO {
+ return 0;
+ }
+
+ match qv.item {
+ Preference::Specific(Encoding::Known(ContentEncoding::Brotli)) => 5,
+ Preference::Specific(Encoding::Known(ContentEncoding::Zstd)) => 4,
+ Preference::Specific(Encoding::Known(ContentEncoding::Gzip)) => 3,
+ Preference::Specific(Encoding::Known(ContentEncoding::Deflate)) => 2,
+ Preference::Any => 0,
+ Preference::Specific(Encoding::Known(ContentEncoding::Identity)) => 0,
+ Preference::Specific(Encoding::Known(_)) => 1,
+ Preference::Specific(Encoding::Unknown(_)) => 1,
+ }
+}
+
/// Returns true if "identity" is an acceptable encoding.
///
/// Internal algorithm relies on item list being in descending order of quality.
@@ -377,11 +407,11 @@ mod tests {
);
assert_eq!(
test.negotiate([Encoding::gzip(), Encoding::brotli(), Encoding::identity()].iter()),
- Some(Encoding::gzip())
+ Some(Encoding::brotli())
);
assert_eq!(
test.negotiate([Encoding::brotli(), Encoding::gzip(), Encoding::identity()].iter()),
- Some(Encoding::gzip())
+ Some(Encoding::brotli())
);
}
@@ -398,6 +428,9 @@ mod tests {
let test = accept_encoding!("br", "gzip", "*");
assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
+
+ let test = accept_encoding!("gzip", "br", "*");
+ assert_eq!(test.ranked(), vec![enc("br"), enc("gzip"), enc("*")]);
}
#[test]
@@ -420,5 +453,8 @@ mod tests {
let test = accept_encoding!("br", "gzip", "*");
assert_eq!(test.preference().unwrap(), enc("br"));
+
+ let test = accept_encoding!("gzip", "br", "*");
+ assert_eq!(test.preference().unwrap(), enc("br"));
}
}
diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs
index 0606f5aef..9725cd19b 100644
--- a/actix-web/src/http/header/content_disposition.rs
+++ b/actix-web/src/http/header/content_disposition.rs
@@ -13,7 +13,10 @@
use std::fmt::{self, Write};
use once_cell::sync::Lazy;
+#[cfg(feature = "unicode")]
use regex::Regex;
+#[cfg(not(feature = "unicode"))]
+use regex_lite::Regex;
use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
use crate::http::header;
diff --git a/actix-web/src/http/header/content_length.rs b/actix-web/src/http/header/content_length.rs
index ad16dc409..557c7c9f5 100644
--- a/actix-web/src/http/header/content_length.rs
+++ b/actix-web/src/http/header/content_length.rs
@@ -126,7 +126,7 @@ mod tests {
use std::fmt;
use super::*;
- use crate::{http::header::Header, test::TestRequest, HttpRequest};
+ use crate::{test::TestRequest, HttpRequest};
fn req_from_raw_headers, V: AsRef<[u8]>>(
header_lines: I,
diff --git a/actix-web/src/info.rs b/actix-web/src/info.rs
index c5d9638f4..1b2e554f9 100644
--- a/actix-web/src/info.rs
+++ b/actix-web/src/info.rs
@@ -21,6 +21,20 @@ fn unquote(val: &str) -> &str {
val.trim().trim_start_matches('"').trim_end_matches('"')
}
+/// Remove port and IPv6 square brackets from a peer specification.
+fn bare_address(val: &str) -> &str {
+ if val.starts_with('[') {
+ val.split("]:")
+ .next()
+ .map(|s| s.trim_start_matches('[').trim_end_matches(']'))
+ // this indicates that the IPv6 address is malformed so shouldn't
+ // usually happen, but if it does, just return the original input
+ .unwrap_or(val)
+ } else {
+ val.split(':').next().unwrap_or(val)
+ }
+}
+
/// Extracts and trims first value for given header name.
fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> {
let hdr = req.headers.get(name)?.to_str().ok()?;
@@ -100,7 +114,7 @@ impl ConnectionInfo {
// --- https://datatracker.ietf.org/doc/html/rfc7239#section-5.2
match name.trim().to_lowercase().as_str() {
- "for" => realip_remote_addr.get_or_insert_with(|| unquote(val)),
+ "for" => realip_remote_addr.get_or_insert_with(|| bare_address(unquote(val))),
"proto" => scheme.get_or_insert_with(|| unquote(val)),
"host" => host.get_or_insert_with(|| unquote(val)),
"by" => {
@@ -368,16 +382,25 @@ mod tests {
.insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
.to_http_request();
let info = req.connection_info();
- assert_eq!(info.realip_remote_addr(), Some("192.0.2.60:8080"));
+ assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
}
#[test]
fn forwarded_for_ipv6() {
+ let req = TestRequest::default()
+ .insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
+ .to_http_request();
+ let info = req.connection_info();
+ assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
+ }
+
+ #[test]
+ fn forwarded_for_ipv6_with_port() {
let req = TestRequest::default()
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
.to_http_request();
let info = req.connection_info();
- assert_eq!(info.realip_remote_addr(), Some("[2001:db8:cafe::17]:4711"));
+ assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
}
#[test]
diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs
index e982a43b1..205391388 100644
--- a/actix-web/src/lib.rs
+++ b/actix-web/src/lib.rs
@@ -64,12 +64,14 @@
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
-//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
+//! - `rustls` - HTTPS support via `rustls` 0.20 crate, supports `HTTP/2`
+//! - `rustls-0_21` - HTTPS support via `rustls` 0.21 crate, supports `HTTP/2`
+//! - `rustls-0_22` - HTTPS support via `rustls` 0.22 crate, supports `HTTP/2`
+//! - `rustls-0_23` - HTTPS support via `rustls` 0.23 crate, supports `HTTP/2`
//! - `secure-cookies` - secure cookies support
#![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))]
@@ -143,5 +145,6 @@ codegen_reexport!(delete);
codegen_reexport!(trace);
codegen_reexport!(connect);
codegen_reexport!(options);
+codegen_reexport!(scope);
pub(crate) type BoxError = Box;
diff --git a/actix-web/src/middleware/compat.rs b/actix-web/src/middleware/compat.rs
index 7df510a5c..963dfdabb 100644
--- a/actix-web/src/middleware/compat.rs
+++ b/actix-web/src/middleware/compat.rs
@@ -38,15 +38,6 @@ pub struct Compat {
transform: T,
}
-#[cfg(test)]
-impl Compat {
- pub(crate) fn noop() -> Self {
- Self {
- transform: super::Noop,
- }
- }
-}
-
impl Compat {
/// Wrap a middleware to give it broader compatibility.
pub fn new(middleware: T) -> Self {
@@ -152,7 +143,7 @@ mod tests {
use crate::{
dev::ServiceRequest,
http::StatusCode,
- middleware::{self, Condition, Logger},
+ middleware::{self, Condition, Identity, Logger},
test::{self, call_service, init_service, TestRequest},
web, App, HttpResponse,
};
@@ -225,7 +216,7 @@ mod tests {
async fn compat_noop_is_noop() {
let srv = test::ok_service();
- let mw = Compat::noop()
+ let mw = Compat::new(Identity)
.new_transform(srv.into_service())
.await
.unwrap();
diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs
index a55b46264..943868d21 100644
--- a/actix-web/src/middleware/compress.rs
+++ b/actix-web/src/middleware/compress.rs
@@ -33,7 +33,7 @@ use crate::{
/// considered in this selection process.
///
/// # Pre-compressed Payload
-/// If you are serving some data is already using a compressed representation (e.g., a gzip
+/// If you are serving some data that is already using a compressed representation (e.g., a gzip
/// compressed HTML file from disk) you can signal this to `Compress` by setting an appropriate
/// `Content-Encoding` header. In addition to preventing double compressing the payload, this header
/// is required by the spec when using compressed representations and will inform the client that
@@ -373,7 +373,7 @@ mod tests {
.default_service(web::to(move || {
HttpResponse::Ok()
.insert_header((header::VARY, "x-test"))
- .finish()
+ .body(TEXT_DATA)
}))
})
.await;
@@ -429,4 +429,47 @@ mod tests {
assert_successful_identity_res_with_content_type(&res, "image/jpeg");
assert_eq!(test::read_body(res).await, TEXT_DATA.as_bytes());
}
+
+ #[actix_rt::test]
+ async fn prevents_compression_empty() {
+ let app = test::init_service({
+ App::new()
+ .wrap(Compress::default())
+ .default_service(web::to(move || HttpResponse::Ok().finish()))
+ })
+ .await;
+
+ let req = test::TestRequest::default()
+ .insert_header((header::ACCEPT_ENCODING, "gzip"))
+ .to_request();
+ let res = test::call_service(&app, req).await;
+ assert_eq!(res.status(), StatusCode::OK);
+ assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
+ assert!(test::read_body(res).await.is_empty());
+ }
+}
+
+#[cfg(feature = "compress-brotli")]
+#[cfg(test)]
+mod tests_brotli {
+ use super::*;
+ use crate::{test, web, App};
+
+ #[actix_rt::test]
+ async fn prevents_compression_empty() {
+ let app = test::init_service({
+ App::new()
+ .wrap(Compress::default())
+ .default_service(web::to(move || HttpResponse::Ok().finish()))
+ })
+ .await;
+
+ let req = test::TestRequest::default()
+ .insert_header((header::ACCEPT_ENCODING, "br"))
+ .to_request();
+ let res = test::call_service(&app, req).await;
+ assert_eq!(res.status(), StatusCode::OK);
+ assert!(!res.headers().contains_key(header::CONTENT_ENCODING));
+ assert!(test::read_body(res).await.is_empty());
+ }
}
diff --git a/actix-web/src/middleware/condition.rs b/actix-web/src/middleware/condition.rs
index 5e106c11f..5ee4467d9 100644
--- a/actix-web/src/middleware/condition.rs
+++ b/actix-web/src/middleware/condition.rs
@@ -135,13 +135,13 @@ mod tests {
use super::*;
use crate::{
body::BoxBody,
- dev::{ServiceRequest, ServiceResponse},
+ dev::ServiceRequest,
error::Result,
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
- middleware::{self, ErrorHandlerResponse, ErrorHandlers},
+ middleware::{self, ErrorHandlerResponse, ErrorHandlers, Identity},
test::{self, TestRequest},
web::Bytes,
HttpResponse,
@@ -158,7 +158,7 @@ mod tests {
#[test]
fn compat_with_builtin_middleware() {
- let _ = Condition::new(true, middleware::Compat::noop());
+ let _ = Condition::new(true, middleware::Compat::new(Identity));
let _ = Condition::new(true, middleware::Logger::default());
let _ = Condition::new(true, middleware::Compress::default());
let _ = Condition::new(true, middleware::NormalizePath::trim());
diff --git a/actix-web/src/middleware/default_headers.rs b/actix-web/src/middleware/default_headers.rs
index b5a5a6998..f21afe6eb 100644
--- a/actix-web/src/middleware/default_headers.rs
+++ b/actix-web/src/middleware/default_headers.rs
@@ -190,8 +190,6 @@ mod tests {
use super::*;
use crate::{
- dev::ServiceRequest,
- http::header::CONTENT_TYPE,
test::{self, TestRequest},
HttpResponse,
};
diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs
index e640bba08..aa6d1c8a4 100644
--- a/actix-web/src/middleware/err_handlers.rs
+++ b/actix-web/src/middleware/err_handlers.rs
@@ -407,10 +407,7 @@ mod tests {
use super::*;
use crate::{
body,
- http::{
- header::{HeaderValue, CONTENT_TYPE},
- StatusCode,
- },
+ http::header::{HeaderValue, CONTENT_TYPE},
test::{self, TestRequest},
};
diff --git a/actix-web/src/middleware/noop.rs b/actix-web/src/middleware/identity.rs
similarity index 57%
rename from actix-web/src/middleware/noop.rs
rename to actix-web/src/middleware/identity.rs
index ae7da1d81..de374a57b 100644
--- a/actix-web/src/middleware/noop.rs
+++ b/actix-web/src/middleware/identity.rs
@@ -2,35 +2,39 @@
use actix_utils::future::{ready, Ready};
-use crate::dev::{Service, Transform};
+use crate::dev::{forward_ready, Service, Transform};
/// A no-op middleware that passes through request and response untouched.
-pub(crate) struct Noop;
+#[derive(Debug, Clone, Default)]
+#[non_exhaustive]
+pub struct Identity;
-impl, Req> Transform
for Noop {
+impl, Req> Transform for Identity {
type Response = S::Response;
type Error = S::Error;
- type Transform = NoopService;
+ type Transform = IdentityMiddleware;
type InitError = ();
type Future = Ready>;
+ #[inline]
fn new_transform(&self, service: S) -> Self::Future {
- ready(Ok(NoopService { service }))
+ ready(Ok(IdentityMiddleware { service }))
}
}
#[doc(hidden)]
-pub(crate) struct NoopService {
+pub struct IdentityMiddleware {
service: S,
}
-impl, Req> Service for NoopService {
+impl, Req> Service for IdentityMiddleware {
type Response = S::Response;
type Error = S::Error;
type Future = S::Future;
- crate::dev::forward_ready!(service);
+ forward_ready!(service);
+ #[inline]
fn call(&self, req: Req) -> Self::Future {
self.service.call(req)
}
diff --git a/actix-web/src/middleware/logger.rs b/actix-web/src/middleware/logger.rs
index 06d26617a..dc1b02399 100644
--- a/actix-web/src/middleware/logger.rs
+++ b/actix-web/src/middleware/logger.rs
@@ -18,7 +18,10 @@ use bytes::Bytes;
use futures_core::ready;
use log::{debug, warn};
use pin_project_lite::pin_project;
-use regex::{Regex, RegexSet};
+#[cfg(feature = "unicode")]
+use regex::Regex;
+#[cfg(not(feature = "unicode"))]
+use regex_lite::Regex;
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use crate::{
@@ -87,7 +90,7 @@ pub struct Logger(Rc);
struct Inner {
format: Format,
exclude: HashSet,
- exclude_regex: RegexSet,
+ exclude_regex: Vec,
log_target: Cow<'static, str>,
}
@@ -97,7 +100,7 @@ impl Logger {
Logger(Rc::new(Inner {
format: Format::new(format),
exclude: HashSet::new(),
- exclude_regex: RegexSet::empty(),
+ exclude_regex: Vec::new(),
log_target: Cow::Borrowed(module_path!()),
}))
}
@@ -114,10 +117,7 @@ impl Logger {
/// Ignore and do not log access info for paths that match regex.
pub fn exclude_regex>(mut self, path: T) -> Self {
let inner = Rc::get_mut(&mut self.0).unwrap();
- let mut patterns = inner.exclude_regex.patterns().to_vec();
- patterns.push(path.into());
- let regex_set = RegexSet::new(patterns).unwrap();
- inner.exclude_regex = regex_set;
+ inner.exclude_regex.push(Regex::new(&path.into()).unwrap());
self
}
@@ -240,7 +240,7 @@ impl Default for Logger {
Logger(Rc::new(Inner {
format: Format::default(),
exclude: HashSet::new(),
- exclude_regex: RegexSet::empty(),
+ exclude_regex: Vec::new(),
log_target: Cow::Borrowed(module_path!()),
}))
}
@@ -300,7 +300,11 @@ where
fn call(&self, req: ServiceRequest) -> Self::Future {
let excluded = self.inner.exclude.contains(req.path())
- || self.inner.exclude_regex.is_match(req.path());
+ || self
+ .inner
+ .exclude_regex
+ .iter()
+ .any(|r| r.is_match(req.path()));
if excluded {
LoggerResponse {
@@ -356,7 +360,7 @@ where
let res = match ready!(this.fut.poll(cx)) {
Ok(res) => res,
- Err(e) => return Poll::Ready(Err(e)),
+ Err(err) => return Poll::Ready(Err(err)),
};
if let Some(error) = res.response().error() {
@@ -716,7 +720,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
- use actix_service::{IntoService, Service, Transform};
+ use actix_service::IntoService;
use actix_utils::future::ok;
use super::*;
diff --git a/actix-web/src/middleware/mod.rs b/actix-web/src/middleware/mod.rs
index 8dbd1ff2c..1c27b1110 100644
--- a/actix-web/src/middleware/mod.rs
+++ b/actix-web/src/middleware/mod.rs
@@ -33,13 +33,13 @@
//!
//! # fn main() {
//! # // These aren't snake_case, because they are supposed to be unit structs.
-//! # let MiddlewareA = middleware::Compress::default();
-//! # let MiddlewareB = middleware::Compress::default();
-//! # let MiddlewareC = middleware::Compress::default();
+//! # type MiddlewareA = middleware::Compress;
+//! # type MiddlewareB = middleware::Compress;
+//! # type MiddlewareC = middleware::Compress;
//! let app = App::new()
-//! .wrap(MiddlewareA)
-//! .wrap(MiddlewareB)
-//! .wrap(MiddlewareC)
+//! .wrap(MiddlewareA::default())
+//! .wrap(MiddlewareB::default())
+//! .wrap(MiddlewareC::default())
//! .service(service);
//! # }
//! ```
@@ -72,7 +72,7 @@
//! processes the request as well and passes it to `MiddlewareA`, which then passes it to the
//! [`Service`]. In the [`Service`], the extractors will run first. They don't pass the request on,
//! but only view it (see [`FromRequest`]). After the [`Service`] responds to the request, the
-//! response it passed back through `MiddlewareA`, `MiddlewareB`, and `MiddlewareC`.
+//! response is passed back through `MiddlewareA`, `MiddlewareB`, and `MiddlewareC`.
//!
//! As you register middleware using [`wrap`][crate::App::wrap] and [`wrap_fn`][crate::App::wrap_fn]
//! in the [`App`] builder, imagine wrapping layers around an inner [`App`]. The first middleware
@@ -218,31 +218,27 @@
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
mod compat;
+#[cfg(feature = "__compress")]
+mod compress;
mod condition;
mod default_headers;
mod err_handlers;
+mod identity;
mod logger;
-#[cfg(test)]
-mod noop;
mod normalize;
-#[cfg(test)]
-pub(crate) use self::noop::Noop;
+#[cfg(feature = "__compress")]
+pub use self::compress::Compress;
pub use self::{
compat::Compat,
condition::Condition,
default_headers::DefaultHeaders,
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
+ identity::Identity,
logger::Logger,
normalize::{NormalizePath, TrailingSlash},
};
-#[cfg(feature = "__compress")]
-mod compress;
-
-#[cfg(feature = "__compress")]
-pub use self::compress::Compress;
-
#[cfg(test)]
mod tests {
use super::*;
diff --git a/actix-web/src/middleware/normalize.rs b/actix-web/src/middleware/normalize.rs
index afcc0faac..482107ecb 100644
--- a/actix-web/src/middleware/normalize.rs
+++ b/actix-web/src/middleware/normalize.rs
@@ -4,7 +4,10 @@ use actix_http::uri::{PathAndQuery, Uri};
use actix_service::{Service, Transform};
use actix_utils::future::{ready, Ready};
use bytes::Bytes;
+#[cfg(feature = "unicode")]
use regex::Regex;
+#[cfg(not(feature = "unicode"))]
+use regex_lite::Regex;
use crate::{
service::{ServiceRequest, ServiceResponse},
@@ -205,7 +208,6 @@ mod tests {
use super::*;
use crate::{
- dev::ServiceRequest,
guard::fn_guard,
test::{call_service, init_service, TestRequest},
web, App, HttpResponse,
diff --git a/actix-web/src/redirect.rs b/actix-web/src/redirect.rs
index f9e9f2d7a..bd29a1403 100644
--- a/actix-web/src/redirect.rs
+++ b/actix-web/src/redirect.rs
@@ -171,7 +171,7 @@ impl Responder for Redirect {
} else {
log::error!(
"redirect target location can not be converted to header value: {:?}",
- self.to
+ self.to,
);
}
@@ -182,7 +182,7 @@ impl Responder for Redirect {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{dev::Service, http::StatusCode, test, App};
+ use crate::{dev::Service, test, App};
#[actix_rt::test]
async fn absolute_redirects() {
diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs
index efb84b2a9..22841746d 100644
--- a/actix-web/src/request.rs
+++ b/actix-web/src/request.rs
@@ -535,7 +535,7 @@ mod tests {
use super::*;
use crate::{
- dev::{ResourceDef, ResourceMap, Service},
+ dev::{ResourceDef, Service},
http::{header, StatusCode},
test::{self, call_service, init_service, read_body, TestRequest},
web, App, HttpResponse,
diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs
index 95185b80a..00555b7b2 100644
--- a/actix-web/src/resource.rs
+++ b/actix-web/src/resource.rs
@@ -540,20 +540,14 @@ mod tests {
use std::time::Duration;
use actix_rt::time::sleep;
- use actix_service::Service;
use actix_utils::future::ok;
use super::*;
use crate::{
- guard,
- http::{
- header::{self, HeaderValue},
- Method, StatusCode,
- },
+ http::{header::HeaderValue, Method, StatusCode},
middleware::DefaultHeaders,
- service::{ServiceRequest, ServiceResponse},
test::{call_service, init_service, TestRequest},
- web, App, Error, HttpMessage, HttpResponse,
+ App, HttpMessage,
};
#[test]
@@ -777,7 +771,7 @@ mod tests {
data3: web::Data| {
assert_eq!(**data1, 10);
assert_eq!(**data2, '*');
- let error = std::f64::EPSILON;
+ let error = f64::EPSILON;
assert!((**data3 - 1.0).abs() < error);
HttpResponse::Ok()
},
diff --git a/actix-web/src/response/builder.rs b/actix-web/src/response/builder.rs
index 2c06941cd..023842ee5 100644
--- a/actix-web/src/response/builder.rs
+++ b/actix-web/src/response/builder.rs
@@ -64,7 +64,7 @@ impl HttpResponseBuilder {
Ok((key, value)) => {
parts.headers.insert(key, value);
}
- Err(e) => self.error = Some(e.into()),
+ Err(err) => self.error = Some(err.into()),
};
}
@@ -86,7 +86,7 @@ impl HttpResponseBuilder {
if let Some(parts) = self.inner() {
match header.try_into_pair() {
Ok((key, value)) => parts.headers.append(key, value),
- Err(e) => self.error = Some(e.into()),
+ Err(err) => self.error = Some(err.into()),
};
}
@@ -210,7 +210,7 @@ impl HttpResponseBuilder {
Ok(value) => {
parts.headers.insert(header::CONTENT_TYPE, value);
}
- Err(e) => self.error = Some(e.into()),
+ Err(err) => self.error = Some(err.into()),
};
}
self
@@ -408,10 +408,7 @@ mod tests {
use super::*;
use crate::{
body,
- http::{
- header::{self, HeaderValue, CONTENT_TYPE},
- StatusCode,
- },
+ http::header::{HeaderValue, CONTENT_TYPE},
test::assert_body_eq,
};
diff --git a/actix-web/src/response/customize_responder.rs b/actix-web/src/response/customize_responder.rs
index aad0039e0..6a43ac5e6 100644
--- a/actix-web/src/response/customize_responder.rs
+++ b/actix-web/src/response/customize_responder.rs
@@ -7,7 +7,7 @@ use actix_http::{
use crate::{HttpRequest, HttpResponse, Responder};
-/// Allows overriding status code and headers for a [`Responder`].
+/// Allows overriding status code and headers (including cookies) for a [`Responder`].
///
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
pub struct CustomizeResponder {
@@ -137,6 +137,29 @@ impl CustomizeResponder {
Some(&mut self.inner)
}
}
+
+ /// Appends a `cookie` to the final response.
+ ///
+ /// # Errors
+ ///
+ /// Final response will be an error if `cookie` cannot be converted into a valid header value.
+ #[cfg(feature = "cookies")]
+ pub fn add_cookie(mut self, cookie: &crate::cookie::Cookie<'_>) -> Self {
+ use actix_http::header::{TryIntoHeaderValue as _, SET_COOKIE};
+
+ if let Some(inner) = self.inner() {
+ match cookie.to_string().try_into_value() {
+ Ok(val) => {
+ inner.append_headers.append(SET_COOKIE, val);
+ }
+ Err(err) => {
+ self.error = Some(err.into());
+ }
+ }
+ }
+
+ self
+ }
}
impl Responder for CustomizeResponder
@@ -175,10 +198,8 @@ mod tests {
use super::*;
use crate::{
- http::{
- header::{HeaderValue, CONTENT_TYPE},
- StatusCode,
- },
+ cookie::Cookie,
+ http::header::{HeaderValue, CONTENT_TYPE},
test::TestRequest,
};
@@ -212,6 +233,22 @@ mod tests {
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
+
+ let res = "test"
+ .to_string()
+ .customize()
+ .add_cookie(&Cookie::new("name", "value"))
+ .respond_to(&req);
+
+ assert!(res.status().is_success());
+ assert_eq!(
+ res.cookies().collect::>>(),
+ vec![Cookie::new("name", "value")],
+ );
+ assert_eq!(
+ to_bytes(res.into_body()).await.unwrap(),
+ Bytes::from_static(b"test"),
+ );
}
#[actix_rt::test]
diff --git a/actix-web/src/response/mod.rs b/actix-web/src/response/mod.rs
index 11fd28301..16bdc619c 100644
--- a/actix-web/src/response/mod.rs
+++ b/actix-web/src/response/mod.rs
@@ -5,8 +5,6 @@ mod responder;
#[allow(clippy::module_inception)]
mod response;
-#[cfg(feature = "cookies")]
-pub use self::response::CookieIter;
pub use self::{
builder::HttpResponseBuilder, customize_responder::CustomizeResponder, responder::Responder,
response::HttpResponse,
diff --git a/actix-web/src/response/responder.rs b/actix-web/src/response/responder.rs
index 7d0b0e585..90d8f6e52 100644
--- a/actix-web/src/response/responder.rs
+++ b/actix-web/src/response/responder.rs
@@ -188,15 +188,11 @@ impl_into_string_responder!(Cow<'_, str>);
pub(crate) mod tests {
use actix_http::body::to_bytes;
use actix_service::Service;
- use bytes::{Bytes, BytesMut};
use super::*;
use crate::{
error,
- http::{
- header::{HeaderValue, CONTENT_TYPE},
- StatusCode,
- },
+ http::header::{HeaderValue, CONTENT_TYPE},
test::{assert_body_eq, init_service, TestRequest},
web, App,
};
diff --git a/actix-web/src/response/response.rs b/actix-web/src/response/response.rs
index fbd87e10c..e16dc0cd9 100644
--- a/actix-web/src/response/response.rs
+++ b/actix-web/src/response/response.rs
@@ -399,7 +399,7 @@ mod tests {
use static_assertions::assert_impl_all;
use super::*;
- use crate::http::header::{HeaderValue, COOKIE};
+ use crate::http::header::COOKIE;
assert_impl_all!(HttpResponse: Responder);
assert_impl_all!(HttpResponse: Responder);
diff --git a/actix-web/src/route.rs b/actix-web/src/route.rs
index 674d0082a..261e6b9ae 100644
--- a/actix-web/src/route.rs
+++ b/actix-web/src/route.rs
@@ -92,7 +92,8 @@ pub struct RouteService {
}
impl RouteService {
- // TODO: does this need to take &mut ?
+ // TODO(breaking): remove pass by ref mut
+ #[allow(clippy::needless_pass_by_ref_mut)]
pub fn check(&self, req: &mut ServiceRequest) -> bool {
let guard_ctx = req.guard_ctx();
diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs
index 629ffe3a0..e370e2c0b 100644
--- a/actix-web/src/rt.rs
+++ b/actix-web/src/rt.rs
@@ -5,6 +5,7 @@
//! architecture in [`actix-rt`]'s docs.
//!
//! # Running Actix Web Without Macros
+//!
//! ```no_run
//! use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer};
//!
@@ -25,6 +26,7 @@
//! ```
//!
//! # Running Actix Web Using `#[tokio::main]`
+//!
//! If you need to run something that uses Tokio's work stealing functionality alongside Actix Web,
//! you can run Actix Web under `#[tokio::main]`. The [`Server`](crate::dev::Server) object returned
//! from [`HttpServer::run`](crate::HttpServer::run) can also be [`spawn`]ed, if preferred.
@@ -32,6 +34,10 @@
//! Note that `actix` actor support (and therefore WebSocket support through `actix-web-actors`)
//! still require `#[actix_web::main]` since they require a [`System`] to be set up.
//!
+//! Also note that calls to this module's [`spawn()`] re-export require an `#[actix_web::main]`
+//! runtime (or a manually configured `LocalSet`) since it makes calls into to the current thread's
+//! `LocalSet`, which `#[tokio::main]` does not set up.
+//!
//! ```no_run
//! use actix_web::{get, middleware, rt, web, App, HttpRequest, HttpServer};
//!
diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs
index e7c4e047a..adc9f75d3 100644
--- a/actix-web/src/scope.rs
+++ b/actix-web/src/scope.rs
@@ -470,8 +470,9 @@ impl ServiceFactory for ScopeFactory {
let guards = guards.borrow_mut().take().unwrap_or_default();
let factory_fut = factory.new_service(());
async move {
- let service = factory_fut.await?;
- Ok((path, guards, service))
+ factory_fut
+ .await
+ .map(move |service| (path, guards, service))
}
}));
@@ -547,7 +548,6 @@ impl ServiceFactory for ScopeEndpoint {
#[cfg(test)]
mod tests {
- use actix_service::Service;
use actix_utils::future::ok;
use bytes::Bytes;
@@ -559,7 +559,6 @@ mod tests {
Method, StatusCode,
},
middleware::DefaultHeaders,
- service::{ServiceRequest, ServiceResponse},
test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
web, App, HttpMessage, HttpRequest, HttpResponse,
};
diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs
index a540da7c8..33b1e1894 100644
--- a/actix-web/src/server.rs
+++ b/actix-web/src/server.rs
@@ -7,7 +7,13 @@ use std::{
time::Duration,
};
-#[cfg(any(feature = "openssl", feature = "rustls"))]
+#[cfg(any(
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22",
+ feature = "rustls-0_23",
+))]
use actix_http::TlsAcceptorConfig;
use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response};
use actix_server::{Server, ServerBuilder};
@@ -16,8 +22,6 @@ use actix_service::{
};
#[cfg(feature = "openssl")]
use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorBuilder};
-#[cfg(feature = "rustls")]
-use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig;
use crate::{config::AppConfig, Error};
@@ -31,7 +35,7 @@ struct Config {
keep_alive: KeepAlive,
client_request_timeout: Duration,
client_disconnect_timeout: Duration,
- #[cfg(any(feature = "openssl", feature = "rustls"))]
+ #[allow(dead_code)] // only dead when no TLS features are enabled
tls_handshake_timeout: Option,
}
@@ -101,6 +105,12 @@ where
B: MessageBody + 'static,
{
/// Create new HTTP server with application factory
+ ///
+ /// # Worker Count
+ ///
+ /// The `factory` will be instantiated multiple times in most configurations. See
+ /// [`bind()`](Self::bind()) docs for more on how worker count and bind address resolution
+ /// causes multiple server factory instantiations.
pub fn new(factory: F) -> Self {
HttpServer {
factory,
@@ -109,7 +119,6 @@ where
keep_alive: KeepAlive::default(),
client_request_timeout: Duration::from_secs(5),
client_disconnect_timeout: Duration::from_secs(1),
- #[cfg(any(feature = "rustls", feature = "openssl"))]
tls_handshake_timeout: None,
})),
backlog: 1024,
@@ -122,7 +131,18 @@ where
/// Sets number of workers to start (per bind address).
///
- /// By default, the number of available physical CPUs is used as the worker count.
+ /// The default worker count is the determined by [`std::thread::available_parallelism()`]. See
+ /// its documentation to determine what behavior you should expect when server is run.
+ ///
+ /// Note that the server factory passed to [`new`](Self::new()) will be instantiated **at least
+ /// once per worker**. See [`bind()`](Self::bind()) docs for more on how worker count and bind
+ /// address resolution causes multiple server factory instantiations.
+ ///
+ /// `num` must be greater than 0.
+ ///
+ /// # Panics
+ ///
+ /// Panics if `num` is 0.
pub fn workers(mut self, num: usize) -> Self {
self.builder = self.builder.workers(num);
self
@@ -170,7 +190,7 @@ where
/// By default max connections is set to a 256.
#[allow(unused_variables)]
pub fn max_connection_rate(self, num: usize) -> Self {
- #[cfg(any(feature = "rustls", feature = "openssl"))]
+ #[cfg(any(feature = "rustls-0_20", feature = "rustls-0_21", feature = "openssl"))]
actix_tls::accept::max_concurrent_tls_connect(num);
self
}
@@ -222,8 +242,14 @@ where
/// 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"))]
+ /// By default, the handshake timeout is 3 seconds.
+ #[cfg(any(
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22",
+ feature = "rustls-0_23",
+ ))]
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
self.config
.lock()
@@ -247,7 +273,14 @@ where
///
/// # Connection Types
/// - `actix_tls::accept::openssl::TlsStream` when using OpenSSL.
- /// - `actix_tls::accept::rustls::TlsStream` when using Rustls.
+ /// - `actix_tls::accept::rustls_0_20::TlsStream` when using
+ /// Rustls v0.20.
+ /// - `actix_tls::accept::rustls_0_21::TlsStream` when using
+ /// Rustls v0.21.
+ /// - `actix_tls::accept::rustls_0_22::TlsStream` when using
+ /// Rustls v0.22.
+ /// - `actix_tls::accept::rustls_0_23::TlsStream` when using
+ /// Rustls v0.23.
/// - `actix_web::rt::net::TcpStream` when no encryption is used.
///
/// See the `on_connect` example for additional details.
@@ -319,23 +352,41 @@ where
/// Resolves socket address(es) and binds server to created listener(s).
///
/// # 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.
+ ///
+ /// When `addrs` 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.
///
+ /// # Worker Count
+ ///
+ /// The `factory` will be instantiated multiple times in most scenarios. The number of
+ /// instantiations is number of [`workers`](Self::workers()) × number of sockets resolved by
+ /// `addrs`.
+ ///
+ /// For example, if you've manually set [`workers`](Self::workers()) to 2, and use `127.0.0.1`
+ /// as the bind `addrs`, then `factory` will be instantiated twice. However, using `localhost`
+ /// as the bind `addrs` can often resolve to both `127.0.0.1` (IPv4) _and_ `::1` (IPv6), causing
+ /// the `factory` to be instantiated 4 times (2 workers × 2 bind addresses).
+ ///
+ /// Using a bind address of `0.0.0.0`, which signals to use all interfaces, may also multiple
+ /// the number of instantiations in a similar way.
+ ///
/// # 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<()> {
@@ -356,6 +407,8 @@ where
/// Resolves socket address(es) and binds server to created listener(s) for plaintext HTTP/1.x
/// or HTTP/2 connections.
+ ///
+ /// See [`bind()`](Self::bind()) for more details on `addrs` argument.
#[cfg(feature = "http2")]
pub fn bind_auto_h2c(mut self, addrs: A) -> io::Result {
let sockets = bind_addrs(addrs, self.backlog)?;
@@ -368,20 +421,77 @@ where
}
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
- /// using Rustls.
+ /// using Rustls v0.20.
///
- /// See [`bind()`](Self::bind) for more details on `addrs` argument.
+ /// 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(feature = "rustls-0_20")]
pub fn bind_rustls(
mut self,
addrs: A,
- config: RustlsServerConfig,
+ config: actix_tls::accept::rustls_0_20::reexports::ServerConfig,
) -> io::Result {
let sockets = bind_addrs(addrs, self.backlog)?;
for lst in sockets {
- self = self.listen_rustls_inner(lst, config.clone())?;
+ self = self.listen_rustls_0_20_inner(lst, config.clone())?;
+ }
+ Ok(self)
+ }
+
+ /// Resolves socket address(es) and binds server to created listener(s) for TLS connections
+ /// using Rustls v0.21.
+ ///
+ /// 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-0_21")]
+ pub fn bind_rustls_021(
+ mut self,
+ addrs: A,
+ config: actix_tls::accept::rustls_0_21::reexports::ServerConfig,
+ ) -> io::Result {
+ let sockets = bind_addrs(addrs, self.backlog)?;
+ for lst in sockets {
+ self = self.listen_rustls_0_21_inner(lst, config.clone())?;
+ }
+ Ok(self)
+ }
+
+ /// Resolves socket address(es) and binds server to created listener(s) for TLS connections
+ /// using Rustls v0.22.
+ ///
+ /// 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-0_22")]
+ pub fn bind_rustls_0_22(
+ mut self,
+ addrs: A,
+ config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
+ ) -> io::Result {
+ let sockets = bind_addrs(addrs, self.backlog)?;
+ for lst in sockets {
+ self = self.listen_rustls_0_22_inner(lst, config.clone())?;
+ }
+ Ok(self)
+ }
+
+ /// Resolves socket address(es) and binds server to created listener(s) for TLS connections
+ /// using Rustls v0.23.
+ ///
+ /// 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-0_23")]
+ pub fn bind_rustls_0_23(
+ mut self,
+ addrs: A,
+ config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
+ ) -> io::Result {
+ let sockets = bind_addrs(addrs, self.backlog)?;
+ for lst in sockets {
+ self = self.listen_rustls_0_23_inner(lst, config.clone())?;
}
Ok(self)
}
@@ -389,7 +499,7 @@ where
/// 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.
+ /// 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")]
@@ -497,25 +607,41 @@ where
Ok(self)
}
- /// Binds to existing listener for accepting incoming TLS connection requests using Rustls.
+ /// Binds to existing listener for accepting incoming TLS connection requests using Rustls
+ /// v0.20.
///
/// 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")]
+ #[cfg(feature = "rustls-0_20")]
pub fn listen_rustls(
self,
lst: net::TcpListener,
- config: RustlsServerConfig,
+ config: actix_tls::accept::rustls_0_20::reexports::ServerConfig,
) -> io::Result {
- self.listen_rustls_inner(lst, config)
+ self.listen_rustls_0_20_inner(lst, config)
}
- #[cfg(feature = "rustls")]
- fn listen_rustls_inner(
+ /// Binds to existing listener for accepting incoming TLS connection requests using Rustls
+ /// v0.21.
+ ///
+ /// 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-0_21")]
+ pub fn listen_rustls_0_21(
+ self,
+ lst: net::TcpListener,
+ config: actix_tls::accept::rustls_0_21::reexports::ServerConfig,
+ ) -> io::Result {
+ self.listen_rustls_0_21_inner(lst, config)
+ }
+
+ #[cfg(feature = "rustls-0_20")]
+ fn listen_rustls_0_20_inner(
mut self,
lst: net::TcpListener,
- config: RustlsServerConfig,
+ config: actix_tls::accept::rustls_0_20::reexports::ServerConfig,
) -> io::Result {
let factory = self.factory.clone();
let cfg = self.config.clone();
@@ -562,6 +688,189 @@ where
Ok(self)
}
+ #[cfg(feature = "rustls-0_21")]
+ fn listen_rustls_0_21_inner(
+ mut self,
+ lst: net::TcpListener,
+ config: actix_tls::accept::rustls_0_21::reexports::ServerConfig,
+ ) -> 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);
+
+ 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)
+ }))
+ .rustls_021_with_config(config.clone(), acceptor_config)
+ })?;
+
+ Ok(self)
+ }
+
+ /// Binds to existing listener for accepting incoming TLS connection requests using Rustls
+ /// v0.22.
+ ///
+ /// 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-0_22")]
+ pub fn listen_rustls_0_22(
+ self,
+ lst: net::TcpListener,
+ config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
+ ) -> io::Result {
+ self.listen_rustls_0_22_inner(lst, config)
+ }
+
+ #[cfg(feature = "rustls-0_22")]
+ fn listen_rustls_0_22_inner(
+ mut self,
+ lst: net::TcpListener,
+ config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
+ ) -> 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);
+
+ 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)
+ }))
+ .rustls_0_22_with_config(config.clone(), acceptor_config)
+ })?;
+
+ Ok(self)
+ }
+
+ /// Binds to existing listener for accepting incoming TLS connection requests using Rustls
+ /// v0.23.
+ ///
+ /// 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-0_23")]
+ pub fn listen_rustls_0_23(
+ self,
+ lst: net::TcpListener,
+ config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
+ ) -> io::Result {
+ self.listen_rustls_0_23_inner(lst, config)
+ }
+
+ #[cfg(feature = "rustls-0_23")]
+ fn listen_rustls_0_23_inner(
+ mut self,
+ lst: net::TcpListener,
+ config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
+ ) -> 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);
+
+ 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)
+ }))
+ .rustls_0_23_with_config(config.clone(), acceptor_config)
+ })?;
+
+ Ok(self)
+ }
+
/// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL.
///
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
@@ -767,7 +1076,7 @@ fn bind_addrs(addrs: impl net::ToSocketAddrs, backlog: u32) -> io::Result err = Some(e),
+ Err(error) => err = Some(error),
}
}
diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs
index 0e17c9949..a1672eba2 100644
--- a/actix-web/src/service.rs
+++ b/actix-web/src/service.rs
@@ -221,12 +221,9 @@ impl ServiceRequest {
/// Returns peer's socket address.
///
- /// Peer address is the directly connected peer's socket address. If a proxy is used in front of
- /// the Actix Web server, then it would be address of this proxy.
+ /// See [`HttpRequest::peer_addr`] for more details.
///
- /// To get client connection information `ConnectionInfo` should be used.
- ///
- /// Will only return None when called in unit tests.
+ /// [`HttpRequest::peer_addr`]: crate::HttpRequest::peer_addr
#[inline]
pub fn peer_addr(&self) -> Option {
self.head().peer_addr
@@ -703,7 +700,7 @@ mod tests {
use crate::{
guard, http,
test::{self, init_service, TestRequest},
- web, App, HttpResponse,
+ web, App,
};
#[actix_rt::test]
diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs
index 5491af0ac..f178d6f43 100644
--- a/actix-web/src/test/test_request.rs
+++ b/actix-web/src/test/test_request.rs
@@ -86,76 +86,77 @@ impl Default for TestRequest {
#[allow(clippy::wrong_self_convention)]
impl TestRequest {
- /// Create TestRequest and set request uri
- pub fn with_uri(path: &str) -> TestRequest {
- TestRequest::default().uri(path)
+ /// Constructs test request and sets request URI.
+ pub fn with_uri(uri: &str) -> TestRequest {
+ TestRequest::default().uri(uri)
}
- /// Create TestRequest and set method to `Method::GET`
+ /// Constructs test request with GET method.
pub fn get() -> TestRequest {
TestRequest::default().method(Method::GET)
}
- /// Create TestRequest and set method to `Method::POST`
+ /// Constructs test request with POST method.
pub fn post() -> TestRequest {
TestRequest::default().method(Method::POST)
}
- /// Create TestRequest and set method to `Method::PUT`
+ /// Constructs test request with PUT method.
pub fn put() -> TestRequest {
TestRequest::default().method(Method::PUT)
}
- /// Create TestRequest and set method to `Method::PATCH`
+ /// Constructs test request with PATCH method.
pub fn patch() -> TestRequest {
TestRequest::default().method(Method::PATCH)
}
- /// Create TestRequest and set method to `Method::DELETE`
+ /// Constructs test request with DELETE method.
pub fn delete() -> TestRequest {
TestRequest::default().method(Method::DELETE)
}
- /// Set HTTP version of this request
+ /// Sets HTTP version of this request.
pub fn version(mut self, ver: Version) -> Self {
self.req.version(ver);
self
}
- /// Set HTTP method of this request
+ /// Sets method of this request.
pub fn method(mut self, meth: Method) -> Self {
self.req.method(meth);
self
}
- /// Set HTTP URI of this request
+ /// Sets URI of this request.
pub fn uri(mut self, path: &str) -> Self {
self.req.uri(path);
self
}
- /// Insert a header, replacing any that were set with an equivalent field name.
+ /// Inserts a header, replacing any that were set with an equivalent field name.
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
self.req.insert_header(header);
self
}
- /// Append a header, keeping any that were set with an equivalent field name.
+ /// Appends a header, keeping any that were set with an equivalent field name.
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
self.req.append_header(header);
self
}
- /// Set cookie for this request.
+ /// Sets cookie for this request.
#[cfg(feature = "cookies")]
pub fn cookie(mut self, cookie: Cookie<'_>) -> Self {
self.cookies.add(cookie.into_owned());
self
}
- /// Set request path pattern parameter.
+ /// Sets request path pattern parameter.
///
/// # Examples
+ ///
/// ```
/// use actix_web::test::TestRequest;
///
@@ -171,19 +172,19 @@ impl TestRequest {
self
}
- /// Set peer addr.
+ /// Sets peer address.
pub fn peer_addr(mut self, addr: SocketAddr) -> Self {
self.peer_addr = Some(addr);
self
}
- /// Set request payload.
+ /// Sets request payload.
pub fn set_payload(mut self, data: impl Into) -> Self {
self.req.set_payload(data);
self
}
- /// Serialize `data` to a URL encoded form and set it as the request payload.
+ /// Serializes `data` to a URL encoded form and set it as the request payload.
///
/// The `Content-Type` header is set to `application/x-www-form-urlencoded`.
pub fn set_form(mut self, data: impl Serialize) -> Self {
@@ -194,7 +195,7 @@ impl TestRequest {
self
}
- /// Serialize `data` to JSON and set it as the request payload.
+ /// Serializes `data` to JSON and set it as the request payload.
///
/// The `Content-Type` header is set to `application/json`.
pub fn set_json(mut self, data: impl Serialize) -> Self {
@@ -204,27 +205,33 @@ impl TestRequest {
self
}
- /// Set application data. This is equivalent of `App::data()` method
- /// for testing purpose.
- pub fn data(mut self, data: T) -> Self {
- self.app_data.insert(Data::new(data));
- self
- }
-
- /// Set application data. This is equivalent of `App::app_data()` method
- /// for testing purpose.
+ /// Inserts application data.
+ ///
+ /// This is equivalent of `App::app_data()` method for testing purpose.
pub fn app_data(mut self, data: T) -> Self {
self.app_data.insert(data);
self
}
+ /// Inserts application data.
+ ///
+ /// This is equivalent of `App::data()` method for testing purpose.
+ #[doc(hidden)]
+ pub fn data(mut self, data: T) -> Self {
+ self.app_data.insert(Data::new(data));
+ self
+ }
+
+ /// Sets resource map.
#[cfg(test)]
- /// Set request config
pub(crate) fn rmap(mut self, rmap: ResourceMap) -> Self {
self.rmap = rmap;
self
}
+ /// Finalizes test request.
+ ///
+ /// This request builder will be useless after calling `finish()`.
fn finish(&mut self) -> Request {
// mut used when cookie feature is enabled
#[allow(unused_mut)]
@@ -251,14 +258,14 @@ impl TestRequest {
req
}
- /// Complete request creation and generate `Request` instance
+ /// Finalizes request creation and returns `Request` instance.
pub fn to_request(mut self) -> Request {
let mut req = self.finish();
req.head_mut().peer_addr = self.peer_addr;
req
}
- /// Complete request creation and generate `ServiceRequest` instance
+ /// Finalizes request creation and returns `ServiceRequest` instance.
pub fn to_srv_request(mut self) -> ServiceRequest {
let (mut head, payload) = self.finish().into_parts();
head.peer_addr = self.peer_addr;
@@ -279,12 +286,12 @@ impl TestRequest {
)
}
- /// Complete request creation and generate `ServiceResponse` instance
+ /// Finalizes request creation and returns `ServiceResponse` instance.
pub fn to_srv_response(self, res: HttpResponse) -> ServiceResponse {
self.to_srv_request().into_response(res)
}
- /// Complete request creation and generate `HttpRequest` instance
+ /// Finalizes request creation and returns `HttpRequest` instance.
pub fn to_http_request(mut self) -> HttpRequest {
let (mut head, _) = self.finish().into_parts();
head.peer_addr = self.peer_addr;
@@ -302,7 +309,7 @@ impl TestRequest {
)
}
- /// Complete request creation and generate `HttpRequest` and `Payload` instances
+ /// Finalizes request creation and returns `HttpRequest` and `Payload` pair.
pub fn to_http_parts(mut self) -> (HttpRequest, Payload) {
let (mut head, payload) = self.finish().into_parts();
head.peer_addr = self.peer_addr;
@@ -322,7 +329,7 @@ impl TestRequest {
(req, payload)
}
- /// Complete request creation, calls service and waits for response future completion.
+ /// Finalizes request creation, calls service, and waits for response future completion.
pub async fn send_request(self, app: &S) -> S::Response
where
S: Service, Error = E>,
@@ -343,7 +350,7 @@ mod tests {
use std::time::SystemTime;
use super::*;
- use crate::{http::header, test::init_service, web, App, Error, HttpResponse, Responder};
+ use crate::{http::header, test::init_service, web, App, Error, Responder};
#[actix_rt::test]
async fn test_basics() {
diff --git a/actix-web/src/types/either.rs b/actix-web/src/types/either.rs
index db244fd9a..7883e89f6 100644
--- a/actix-web/src/types/either.rs
+++ b/actix-web/src/types/either.rs
@@ -287,10 +287,7 @@ mod tests {
use serde::{Deserialize, Serialize};
use super::*;
- use crate::{
- test::TestRequest,
- web::{Form, Json},
- };
+ use crate::test::TestRequest;
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestForm {
diff --git a/actix-web/src/types/form.rs b/actix-web/src/types/form.rs
index 7096b1e9c..d6381b990 100644
--- a/actix-web/src/types/form.rs
+++ b/actix-web/src/types/form.rs
@@ -418,7 +418,7 @@ mod tests {
use super::*;
use crate::{
http::{
- header::{HeaderValue, CONTENT_LENGTH, CONTENT_TYPE},
+ header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::{assert_body_eq, TestRequest},
diff --git a/actix-web/src/types/header.rs b/actix-web/src/types/header.rs
index 8b1740e6c..977dc032e 100644
--- a/actix-web/src/types/header.rs
+++ b/actix-web/src/types/header.rs
@@ -2,7 +2,7 @@
use std::{fmt, ops};
-use actix_utils::future::{err, ok, Ready};
+use actix_utils::future::{ready, Ready};
use crate::{
dev::Payload, error::ParseError, extract::FromRequest, http::header::Header as ParseHeader,
@@ -66,8 +66,8 @@ where
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
match ParseHeader::parse(req) {
- Ok(header) => ok(Header(header)),
- Err(e) => err(e),
+ Ok(header) => ready(Ok(Header(header))),
+ Err(err) => ready(Err(err)),
}
}
}
diff --git a/actix-web/src/types/json.rs b/actix-web/src/types/json.rs
index 59c95da4c..6b75c0cfe 100644
--- a/actix-web/src/types/json.rs
+++ b/actix-web/src/types/json.rs
@@ -328,14 +328,19 @@ impl JsonBody {
ctype_required: bool,
) -> Self {
// check content-type
- let can_parse_json = if let Ok(Some(mime)) = req.mime_type() {
- mime.subtype() == mime::JSON
- || mime.suffix() == Some(mime::JSON)
- || ctype_fn.map_or(false, |predicate| predicate(mime))
- } else {
- // if `ctype_required` is false, assume payload is
- // json even when content-type header is missing
- !ctype_required
+ let can_parse_json = match (ctype_required, req.mime_type()) {
+ (true, Ok(Some(mime))) => {
+ mime.subtype() == mime::JSON
+ || mime.suffix() == Some(mime::JSON)
+ || ctype_fn.map_or(false, |predicate| predicate(mime))
+ }
+
+ // if content-type is expected but not parsable as mime type, bail
+ (true, _) => false,
+
+ // if content-type validation is disabled, assume payload is JSON
+ // even when content-type header is missing or invalid mime type
+ (false, _) => true,
};
if !can_parse_json {
@@ -725,6 +730,25 @@ mod tests {
assert!(s.is_ok())
}
+ #[actix_rt::test]
+ async fn test_json_ignoring_content_type() {
+ let (req, mut pl) = TestRequest::default()
+ .insert_header((
+ header::CONTENT_LENGTH,
+ header::HeaderValue::from_static("16"),
+ ))
+ .insert_header((
+ header::CONTENT_TYPE,
+ header::HeaderValue::from_static("invalid/value"),
+ ))
+ .set_payload(Bytes::from_static(b"{\"name\": \"test\"}"))
+ .app_data(JsonConfig::default().content_type_required(false))
+ .to_http_parts();
+
+ let s = Json::::from_request(&req, &mut pl).await;
+ assert!(s.is_ok());
+ }
+
#[actix_rt::test]
async fn test_with_config_in_data_wrapper() {
let (req, mut pl) = TestRequest::default()
diff --git a/actix-web/src/types/payload.rs b/actix-web/src/types/payload.rs
index abb4e6b7f..e4db37d0b 100644
--- a/actix-web/src/types/payload.rs
+++ b/actix-web/src/types/payload.rs
@@ -440,13 +440,11 @@ impl Future for HttpMessageBody {
#[cfg(test)]
mod tests {
- use bytes::Bytes;
-
use super::*;
use crate::{
- http::{header, StatusCode},
+ http::StatusCode,
test::{call_service, init_service, read_body, TestRequest},
- web, App, Responder,
+ App, Responder,
};
#[actix_rt::test]
diff --git a/actix-web/tests/compression.rs b/actix-web/tests/compression.rs
index b911b9d1f..61ff1bff5 100644
--- a/actix-web/tests/compression.rs
+++ b/actix-web/tests/compression.rs
@@ -96,7 +96,7 @@ async fn negotiate_encoding_gzip() {
let req = srv
.post("/static")
- .insert_header((header::ACCEPT_ENCODING, "gzip,br,zstd"))
+ .insert_header((header::ACCEPT_ENCODING, "gzip, br;q=0.8, zstd;q=0.5"))
.send();
let mut res = req.await.unwrap();
@@ -109,7 +109,7 @@ async fn negotiate_encoding_gzip() {
let mut res = srv
.post("/static")
.no_decompress()
- .insert_header((header::ACCEPT_ENCODING, "gzip,br,zstd"))
+ .insert_header((header::ACCEPT_ENCODING, "gzip, br;q=0.8, zstd;q=0.5"))
.send()
.await
.unwrap();
@@ -123,9 +123,11 @@ async fn negotiate_encoding_gzip() {
async fn negotiate_encoding_br() {
let srv = test_server!();
+ // check that brotli content-encoding header is returned
+
let req = srv
.post("/static")
- .insert_header((header::ACCEPT_ENCODING, "br,zstd,gzip"))
+ .insert_header((header::ACCEPT_ENCODING, "br, zstd, gzip"))
.send();
let mut res = req.await.unwrap();
@@ -135,10 +137,26 @@ async fn negotiate_encoding_br() {
let bytes = res.body().await.unwrap();
assert_eq!(bytes, Bytes::from_static(LOREM));
+ // check that brotli is preferred even when later in (q-less) list
+
+ let req = srv
+ .post("/static")
+ .insert_header((header::ACCEPT_ENCODING, "gzip, zstd, br"))
+ .send();
+
+ let mut res = req.await.unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
+ assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "br");
+
+ let bytes = res.body().await.unwrap();
+ assert_eq!(bytes, Bytes::from_static(LOREM));
+
+ // check that returned content is actually brotli encoded
+
let mut res = srv
.post("/static")
.no_decompress()
- .insert_header((header::ACCEPT_ENCODING, "br,zstd,gzip"))
+ .insert_header((header::ACCEPT_ENCODING, "br, zstd, gzip"))
.send()
.await
.unwrap();
@@ -154,7 +172,7 @@ async fn negotiate_encoding_zstd() {
let req = srv
.post("/static")
- .insert_header((header::ACCEPT_ENCODING, "zstd,gzip,br"))
+ .insert_header((header::ACCEPT_ENCODING, "zstd, gzip, br;q=0.8"))
.send();
let mut res = req.await.unwrap();
@@ -167,7 +185,7 @@ async fn negotiate_encoding_zstd() {
let mut res = srv
.post("/static")
.no_decompress()
- .insert_header((header::ACCEPT_ENCODING, "zstd,gzip,br"))
+ .insert_header((header::ACCEPT_ENCODING, "zstd, gzip, br;q=0.8"))
.send()
.await
.unwrap();
@@ -207,7 +225,7 @@ async fn gzip_no_decompress() {
// don't decompress response body
.no_decompress()
// signal that we want a compressed body
- .insert_header((header::ACCEPT_ENCODING, "gzip,br,zstd"))
+ .insert_header((header::ACCEPT_ENCODING, "gzip, br;q=0.8, zstd;q=0.5"))
.send();
let mut res = req.await.unwrap();
diff --git a/actix-web/tests/test_httpserver.rs b/actix-web/tests/test_httpserver.rs
index 861d76d93..039c0ffbc 100644
--- a/actix-web/tests/test_httpserver.rs
+++ b/actix-web/tests/test_httpserver.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
#[cfg(feature = "openssl")]
extern crate tls_openssl as openssl;
@@ -66,9 +64,11 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder {
x509::X509,
};
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
+
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
diff --git a/actix-web/tests/test_server.rs b/actix-web/tests/test_server.rs
index 8ce889396..960cf1e2b 100644
--- a/actix-web/tests/test_server.rs
+++ b/actix-web/tests/test_server.rs
@@ -1,6 +1,6 @@
#[cfg(feature = "openssl")]
extern crate tls_openssl as openssl;
-#[cfg(feature = "rustls")]
+#[cfg(feature = "rustls-0_23")]
extern crate tls_rustls as rustls;
use std::{
@@ -34,9 +34,11 @@ const STR: &str = const_str::repeat!(S, 100);
#[cfg(feature = "openssl")]
fn openssl_config() -> SslAcceptor {
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
+
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
@@ -704,34 +706,32 @@ async fn test_brotli_encoding_large_openssl() {
srv.stop().await;
}
-#[cfg(feature = "rustls")]
+#[cfg(feature = "rustls-0_23")]
mod plus_rustls {
use std::io::BufReader;
- use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig};
+ use rustls::{pki_types::PrivateKeyDer, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
use super::*;
fn tls_config() -> RustlsServerConfig {
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
- let cert_chain = certs(cert_file)
- .unwrap()
- .into_iter()
- .map(Certificate)
- .collect();
- let mut keys = pkcs8_private_keys(key_file).unwrap();
+ let cert_chain = certs(cert_file).collect::, _>>().unwrap();
+ let mut keys = pkcs8_private_keys(key_file)
+ .collect::, _>>()
+ .unwrap();
RustlsServerConfig::builder()
- .with_safe_defaults()
.with_no_client_auth()
- .with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
+ .with_single_cert(cert_chain, PrivateKeyDer::Pkcs8(keys.remove(0)))
.unwrap()
}
@@ -743,7 +743,7 @@ mod plus_rustls {
.map(char::from)
.collect::();
- let srv = actix_test::start_with(actix_test::config().rustls(tls_config()), || {
+ let srv = actix_test::start_with(actix_test::config().rustls_0_23(tls_config()), || {
App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async {
// echo decompressed request body back in response
HttpResponse::Ok()
diff --git a/awc/CHANGES.md b/awc/CHANGES.md
index 3429a84d7..54c5e9869 100644
--- a/awc/CHANGES.md
+++ b/awc/CHANGES.md
@@ -1,22 +1,44 @@
# Changes
-## Unreleased - 2023-xx-xx
+## Unreleased
+## 3.5.0
+
+- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features.
+- Add `awc::Connector::rustls_0_23()` constructor.
+- Fix `rustls-0_22-native-roots` root store lookup
+- Update `brotli` dependency to `6`.
+- Minimum supported Rust version (MSRV) is now 1.72.
+
+## 3.4.0
+
+- Add `rustls-0_22-webpki-roots` and `rustls-0_22-native-roots` crate feature.
+- Add `awc::Connector::rustls_0_22()` method.
+
+## 3.3.0
+
+- Update `trust-dns-resolver` dependency to `0.23`.
+- Updated `zstd` dependency to `0.13`.
+
+## 3.2.0
+
+- Add `awc::Connector::rustls_021()` method for Rustls v0.21 support behind new `rustls-0_21` crate feature.
+- Add `rustls-0_20` crate feature, which the existing `rustls` feature now aliases.
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
-## 3.1.1 - 2023-02-26
+## 3.1.1
### Changed
- `client::Connect` is now public to allow tunneling connection with `client::Connector`.
-## 3.1.0 - 2023-01-21
+## 3.1.0
### Changed
- Minimum supported Rust version (MSRV) is now 1.59 due to transitive `time` dependency.
-## 3.0.1 - 2022-08-25
+## 3.0.1
### Changed
@@ -28,7 +50,7 @@
[#2840]: https://github.com/actix/actix-web/pull/2840
-## 3.0.0 - 2022-03-07
+## 3.0.0
### Dependencies
@@ -130,23 +152,23 @@
3.0.0 Pre-Releases
-## 3.0.0-beta.21 - 2022-02-16
+## 3.0.0-beta.21
- No significant changes since `3.0.0-beta.20`.
-## 3.0.0-beta.20 - 2022-01-31
+## 3.0.0-beta.20
- No significant changes since `3.0.0-beta.19`.
-## 3.0.0-beta.19 - 2022-01-21
+## 3.0.0-beta.19
- No significant changes since `3.0.0-beta.18`.
-## 3.0.0-beta.18 - 2022-01-04
+## 3.0.0-beta.18
- Minimum supported Rust version (MSRV) is now 1.54.
-## 3.0.0-beta.17 - 2021-12-29
+## 3.0.0-beta.17
### Changed
@@ -159,7 +181,7 @@
[#2555]: https://github.com/actix/actix-web/pull/2555
[`rustsec-2020-0071`]: https://rustsec.org/advisories/RUSTSEC-2020-0071.html
-## 3.0.0-beta.16 - 2021-12-29
+## 3.0.0-beta.16
- `*::send_json` and `*::send_form` methods now receive `impl Serialize`. [#2553]
- `FrozenClientRequest::extra_header` now uses receives an `impl TryIntoHeaderPair`. [#2553]
@@ -167,7 +189,7 @@
[#2553]: https://github.com/actix/actix-web/pull/2553
-## 3.0.0-beta.15 - 2021-12-27
+## 3.0.0-beta.15
- Rename `Connector::{ssl => openssl}`. [#2503]
- Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
@@ -180,37 +202,37 @@
[#2503]: https://github.com/actix/actix-web/pull/2503
[#2546]: https://github.com/actix/actix-web/pull/2546
-## 3.0.0-beta.14 - 2021-12-17
+## 3.0.0-beta.14
- Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
-## 3.0.0-beta.13 - 2021-12-11
+## 3.0.0-beta.13
- No significant changes since `3.0.0-beta.12`.
-## 3.0.0-beta.12 - 2021-11-30
+## 3.0.0-beta.12
- Update `actix-tls` to `3.0.0-rc.1`. [#2474]
[#2474]: https://github.com/actix/actix-web/pull/2474
-## 3.0.0-beta.11 - 2021-11-22
+## 3.0.0-beta.11
- No significant changes from `3.0.0-beta.10`.
-## 3.0.0-beta.10 - 2021-11-15
+## 3.0.0-beta.10
- No significant changes from `3.0.0-beta.9`.
-## 3.0.0-beta.9 - 2021-10-20
+## 3.0.0-beta.9
- Updated rustls to v0.20. [#2414]
[#2414]: https://github.com/actix/actix-web/pull/2414
-## 3.0.0-beta.8 - 2021-09-09
+## 3.0.0-beta.8
### Changed
@@ -218,7 +240,7 @@
[#2310]: https://github.com/actix/actix-web/pull/2310
-## 3.0.0-beta.7 - 2021-06-26
+## 3.0.0-beta.7
### Changed
@@ -226,11 +248,11 @@
[#2250]: https://github.com/actix/actix-web/pull/2250
-## 3.0.0-beta.6 - 2021-06-17
+## 3.0.0-beta.6
- No significant changes since 3.0.0-beta.5.
-## 3.0.0-beta.5 - 2021-04-17
+## 3.0.0-beta.5
### Removed
@@ -238,7 +260,7 @@
[#2148]: https://github.com/actix/actix-web/pull/2148
-## 3.0.0-beta.4 - 2021-04-02
+## 3.0.0-beta.4
### Added
@@ -255,7 +277,7 @@
[#2114]: https://github.com/actix/actix-web/pull/2114
[#2116]: https://github.com/actix/actix-web/pull/2116
-## 3.0.0-beta.3 - 2021-03-08
+## 3.0.0-beta.3
### Added
@@ -278,7 +300,7 @@
[#2024]: https://github.com/actix/actix-web/pull/2024
[#2050]: https://github.com/actix/actix-web/pull/2050
-## 3.0.0-beta.2 - 2021-02-10
+## 3.0.0-beta.2
### Added
@@ -301,7 +323,7 @@
[#1905]: https://github.com/actix/actix-web/pull/1905
[#1969]: https://github.com/actix/actix-web/pull/1969
-## 3.0.0-beta.1 - 2021-01-07
+## 3.0.0-beta.1
### Changed
@@ -313,13 +335,13 @@
-## 2.0.3 - 2020-11-29
+## 2.0.3
### Fixed
- Ensure `actix-http` dependency uses same `serde_urlencoded`.
-## 2.0.2 - 2020-11-25
+## 2.0.2
### Changed
@@ -327,7 +349,7 @@
[#1773]: https://github.com/actix/actix-web/pull/1773
-## 2.0.1 - 2020-10-30
+## 2.0.1
### Changed
@@ -342,37 +364,37 @@
[#1760]: https://github.com/actix/actix-web/pull/1760
[#1744]: https://github.com/actix/actix-web/pull/1744
-## 2.0.0 - 2020-09-11
+## 2.0.0
### Changed
- `Client::build` was renamed to `Client::builder`.
-## 2.0.0-beta.4 - 2020-09-09
+## 2.0.0-beta.4
### Changed
- Update actix-codec & actix-tls dependencies.
-## 2.0.0-beta.3 - 2020-08-17
+## 2.0.0-beta.3
### Changed
- Update `rustls` to 0.18
-## 2.0.0-beta.2 - 2020-07-21
+## 2.0.0-beta.2
### Changed
- Update `actix-http` dependency to 2.0.0-beta.2
-## [2.0.0-beta.1] - 2020-07-14
+## 2.0.0-beta.1
### Changed
- Update `actix-http` dependency to 2.0.0-beta.1
-## [2.0.0-alpha.2] - 2020-05-21
+## 2.0.0-alpha.2
### Changed
@@ -382,42 +404,42 @@
[#1422]: https://github.com/actix/actix-web/pull/1422
-## [2.0.0-alpha.1] - 2020-03-11
+## 2.0.0-alpha.1
- Update `actix-http` dependency to 2.0.0-alpha.2
- Update `rustls` dependency to 0.17
- ClientBuilder accepts initial_window_size and initial_connection_window_size HTTP2 configuration
- ClientBuilder allowing to set max_http_version to limit HTTP version to be used
-## [1.0.1] - 2019-12-15
+## 1.0.1
- Fix compilation with default features off
-## [1.0.0] - 2019-12-13
+## 1.0.0
- Release
-## [1.0.0-alpha.3]
+## 1.0.0-alpha.3
- Migrate to `std::future`
-## [0.2.8] - 2019-11-06
+## 0.2.8
- Add support for setting query from Serialize type for client request.
-## [0.2.7] - 2019-09-25
+## 0.2.7
### Added
- Remaining getter methods for `ClientRequest`'s private `head` field #1101
-## [0.2.6] - 2019-09-12
+## 0.2.6
### Added
- Export frozen request related types.
-## [0.2.5] - 2019-09-11
+## 0.2.5
### Added
@@ -427,7 +449,7 @@
- Ensure that the `Host` header is set when initiating a WebSocket client connection.
-## [0.2.4] - 2019-08-13
+## 0.2.4
### Changed
@@ -435,13 +457,13 @@
- Update serde_urlencoded to "0.6.1"
-## [0.2.3] - 2019-08-01
+## 0.2.3
### Added
- Add `rustls` support
-## [0.2.2] - 2019-07-01
+## 0.2.2
### Changed
@@ -449,13 +471,13 @@
- Upgrade `rand` dependency version to 0.7
-## [0.2.1] - 2019-06-05
+## 0.2.1
### Added
- Add license files
-## [0.2.0] - 2019-05-12
+## 0.2.0
### Added
@@ -465,7 +487,7 @@
- Upgrade actix-http dependency.
-## [0.1.1] - 2019-04-19
+## 0.1.1
### Added
@@ -475,17 +497,17 @@
- `ClientRequest::if_true()` and `ClientRequest::if_some()` use instance instead of ref
-## [0.1.0] - 2019-04-16
+## 0.1.0
- No changes
-## [0.1.0-alpha.6] - 2019-04-14
+## 0.1.0-alpha.6
### Changed
- Do not set default headers for websocket request
-## [0.1.0-alpha.5] - 2019-04-12
+## 0.1.0-alpha.5
### Changed
@@ -495,13 +517,13 @@
- Add Debug impl for BoxedSocket
-## [0.1.0-alpha.4] - 2019-04-08
+## 0.1.0-alpha.4
### Changed
- Update actix-http dependency
-## [0.1.0-alpha.3] - 2019-04-02
+## 0.1.0-alpha.3
### Added
@@ -517,7 +539,7 @@
- Renamed `ClientRequest::close_connection()` to `ClientRequest::force_close()`
-## [0.1.0-alpha.2] - 2019-03-29
+## 0.1.0-alpha.2
### Added
@@ -535,6 +557,6 @@
- Export `ws` sub-module with websockets related types
-## [0.1.0-alpha.1] - 2019-03-28
+## 0.1.0-alpha.1
- Initial impl
diff --git a/awc/Cargo.toml b/awc/Cargo.toml
index daec84ab9..6ab408ea6 100644
--- a/awc/Cargo.toml
+++ b/awc/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "awc"
-version = "3.1.1"
+version = "3.5.0"
authors = ["Nikolay Kim "]
description = "Async HTTP and WebSocket client library"
keywords = ["actix", "http", "framework", "async", "web"]
@@ -11,7 +11,7 @@ categories = [
"web-programming::websocket",
]
homepage = "https://actix.rs"
-repository = "https://github.com/actix/actix-web.git"
+repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
@@ -20,17 +20,41 @@ name = "awc"
path = "src/lib.rs"
[package.metadata.docs.rs]
-# features that docs.rs will build with
-features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
+rustdoc-args = ["--cfg", "docsrs"]
+features = [
+ "cookies",
+ "openssl",
+ "rustls-0_20",
+ "rustls-0_21",
+ "rustls-0_22-webpki-roots",
+ "rustls-0_23-webpki-roots",
+ "compress-brotli",
+ "compress-gzip",
+ "compress-zstd",
+]
[features]
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
-# openssl
+# TLS via OpenSSL
openssl = ["tls-openssl", "actix-tls/openssl"]
-# rustls
-rustls = ["tls-rustls", "actix-tls/rustls"]
+# TLS via Rustls v0.20
+rustls = ["rustls-0_20"]
+# TLS via Rustls v0.20
+rustls-0_20 = ["tls-rustls-0_20", "actix-tls/rustls-0_20"]
+# TLS via Rustls v0.21
+rustls-0_21 = ["tls-rustls-0_21", "actix-tls/rustls-0_21"]
+# TLS via Rustls v0.22 (WebPKI roots)
+rustls-0_22-webpki-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-webpki-roots"]
+# TLS via Rustls v0.22 (Native roots)
+rustls-0_22-native-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-native-roots"]
+# TLS via Rustls v0.23
+rustls-0_23 = ["tls-rustls-0_23", "actix-tls/rustls-0_23"]
+# TLS via Rustls v0.23 (WebPKI roots)
+rustls-0_23-webpki-roots = ["rustls-0_23", "actix-tls/rustls-0_23-webpki-roots"]
+# TLS via Rustls v0.23 (Native roots)
+rustls-0_23-native-roots = ["rustls-0_23", "actix-tls/rustls-0_23-native-roots"]
# Brotli algorithm content-encoding support
compress-brotli = ["actix-http/compress-brotli", "__compress"]
@@ -39,13 +63,13 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
# Zstd algorithm content-encoding support
compress-zstd = ["actix-http/compress-zstd", "__compress"]
-# cookie parsing and cookie jar
-cookies = ["cookie"]
+# Cookie parsing and cookie jar
+cookies = ["dep:cookie"]
-# trust-dns as dns resolver
+# Use `trust-dns-resolver` crate as DNS resolver
trust-dns = ["trust-dns-resolver"]
-# Internal (PRIVATE!) features used to aid testing and cheking feature status.
+# Internal (PRIVATE!) features used to aid testing and checking feature status.
# Don't rely on these whatsoever. They may disappear at anytime.
__compress = []
@@ -57,18 +81,18 @@ dangerous-h2c = []
[dependencies]
actix-codec = "0.5"
actix-service = "2"
-actix-http = { version = "3.3", features = ["http2", "ws"] }
+actix-http = { version = "3.7", features = ["http2", "ws"] }
actix-rt = { version = "2.1", default-features = false }
-actix-tls = { version = "3", features = ["connect", "uri"] }
+actix-tls = { version = "3.4", features = ["connect", "uri"] }
actix-utils = "3"
-base64 = "0.21"
+base64 = "0.22"
bytes = "1"
cfg-if = "1"
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.17"
+h2 = "0.3.26"
http = "0.2.7"
itoa = "1"
log =" 0.4"
@@ -84,30 +108,34 @@ tokio = { version = "1.24.2", features = ["sync"] }
cookie = { version = "0.16", features = ["percent-encode"], optional = true }
tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
-tls-rustls = { package = "rustls", version = "0.20", optional = true, features = ["dangerous_configuration"] }
+tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true, features = ["dangerous_configuration"] }
+tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true, features = ["dangerous_configuration"] }
+tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
+tls-rustls-0_23 = { package = "rustls", version = "0.23", optional = true, default-features = false }
-trust-dns-resolver = { version = "0.22", optional = true }
+trust-dns-resolver = { version = "0.23", optional = true }
[dev-dependencies]
-actix-http = { version = "3", features = ["openssl"] }
+actix-http = { version = "3.7", features = ["openssl"] }
actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2"
-actix-test = { version = "0.1", features = ["openssl", "rustls"] }
-actix-tls = { version = "3", features = ["openssl", "rustls"] }
+actix-test = { version = "0.1", features = ["openssl", "rustls-0_23"] }
+actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23"] }
actix-utils = "3"
actix-web = { version = "4", features = ["openssl"] }
-brotli = "3.3.3"
+brotli = "6"
const-str = "0.5"
-env_logger = "0.10"
+env_logger = "0.11"
flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false }
static_assertions = "1.1"
-rcgen = "0.11"
-rustls-pemfile = "1"
+rcgen = "0.13"
+rustls-pemfile = "2"
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
-zstd = "0.12"
+zstd = "0.13"
+tls-rustls-0_23 = { package = "rustls", version = "0.23" } # add rustls 0.23 with default features to make aws_lc_rs work in tests
[[example]]
name = "client"
-required-features = ["rustls"]
+required-features = ["rustls-0_23-webpki-roots"]
diff --git a/awc/README.md b/awc/README.md
index f2a9c7559..8e7b42812 100644
--- a/awc/README.md
+++ b/awc/README.md
@@ -1,20 +1,22 @@
-# awc (Actix Web Client)
+# `awc` (Actix Web Client)
> Async HTTP and WebSocket client library.
+
+
[](https://crates.io/crates/awc)
-[](https://docs.rs/awc/3.1.1)
+[](https://docs.rs/awc/3.5.0)

-[](https://deps.rs/crate/awc/3.1.1)
+[](https://deps.rs/crate/awc/3.5.0)
[](https://discord.gg/NWpN5mmg3x)
-## Documentation & Resources
+
-- [API Documentation](https://docs.rs/awc)
-- [Example Project](https://github.com/actix/examples/tree/master/https-tls/awc-https)
-- Minimum Supported Rust Version (MSRV): 1.68
+## Examples
-## Example
+[Example project using TLS-enabled client →](https://github.com/actix/examples/tree/master/https-tls/awc-https)
+
+Basic usage:
```rust
use actix_rt::System;
diff --git a/awc/examples/client.rs b/awc/examples/client.rs
index 26edcfd62..16ad330b8 100644
--- a/awc/examples/client.rs
+++ b/awc/examples/client.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use std::error::Error as StdError;
#[tokio::main]
diff --git a/awc/src/builder.rs b/awc/src/builder.rs
index a54960382..5aae394f8 100644
--- a/awc/src/builder.rs
+++ b/awc/src/builder.rs
@@ -37,6 +37,12 @@ pub struct ClientBuilder {
}
impl ClientBuilder {
+ /// Create a new ClientBuilder with default settings
+ ///
+ /// Note: If the `rustls-0_23` feature is enabled and neither `rustls-0_23-native-roots` nor
+ /// `rustls-0_23-webpki-roots` are enabled, this ClientBuilder will build without TLS. In order
+ /// to enable TLS in this scenario, a custom `Connector` _must_ be added to the builder before
+ /// finishing construction.
#[allow(clippy::new_ret_no_self)]
pub fn new() -> ClientBuilder<
impl Service<
diff --git a/awc/src/client/connection.rs b/awc/src/client/connection.rs
index 64075eae8..8164e2b59 100644
--- a/awc/src/client/connection.rs
+++ b/awc/src/client/connection.rs
@@ -380,8 +380,6 @@ mod test {
use std::{
future::Future,
net,
- pin::Pin,
- task::{Context, Poll},
time::{Duration, Instant},
};
@@ -394,6 +392,8 @@ mod test {
#[actix_rt::test]
async fn test_h2_connection_drop() {
+ env_logger::try_init().ok();
+
let addr = "127.0.0.1:0".parse::().unwrap();
let listener = net::TcpListener::bind(addr).unwrap();
let local = listener.local_addr().unwrap();
@@ -428,8 +428,15 @@ mod test {
if this.start_from.elapsed() > Duration::from_secs(10) {
panic!("connection should be gone and can not be ready");
} else {
- let _ = this.interval.poll_tick(cx);
- Poll::Pending
+ match this.interval.poll_tick(cx) {
+ Poll::Ready(_) => {
+ // prevents spurious test hang
+ this.interval.reset();
+
+ Poll::Pending
+ }
+ Poll::Pending => Poll::Pending,
+ }
}
}
Err(_) => Poll::Ready(()),
diff --git a/awc/src/client/connector.rs b/awc/src/client/connector.rs
index 1ecaf9947..f3d443070 100644
--- a/awc/src/client/connector.rs
+++ b/awc/src/client/connector.rs
@@ -40,23 +40,38 @@ enum OurTlsConnector {
/// Provided because building the OpenSSL context on newer versions can be very slow.
/// This prevents unnecessary calls to `.build()` while constructing the client connector.
#[cfg(feature = "openssl")]
- #[allow(dead_code)] // false positive; used in build_ssl
+ #[allow(dead_code)] // false positive; used in build_tls
OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder),
- #[cfg(feature = "rustls")]
- Rustls(std::sync::Arc),
+ #[cfg(feature = "rustls-0_20")]
+ #[allow(dead_code)] // false positive; used in build_tls
+ Rustls020(std::sync::Arc),
+
+ #[cfg(feature = "rustls-0_21")]
+ #[allow(dead_code)] // false positive; used in build_tls
+ Rustls021(std::sync::Arc),
+
+ #[cfg(any(
+ feature = "rustls-0_22-webpki-roots",
+ feature = "rustls-0_22-native-roots",
+ ))]
+ #[allow(dead_code)] // false positive; used in build_tls
+ Rustls022(std::sync::Arc),
+
+ #[cfg(feature = "rustls-0_23")]
+ #[allow(dead_code)] // false positive; used in build_tls
+ Rustls023(std::sync::Arc),
}
/// Manages HTTP client network connectivity.
///
-/// The `Connector` type uses a builder-like combinator pattern for service
-/// construction that finishes by calling the `.finish()` method.
+/// The `Connector` type uses a builder-like combinator pattern for service construction that
+/// finishes by calling the `.finish()` method.
///
-/// ```ignore
+/// ```no_run
/// use std::time::Duration;
-/// use actix_http::client::Connector;
///
-/// let connector = Connector::new()
+/// let connector = awc::Connector::new()
/// .timeout(Duration::from_secs(5))
/// .finish();
/// ```
@@ -69,6 +84,14 @@ pub struct Connector {
}
impl Connector<()> {
+ /// Create a new connector with default TLS settings
+ ///
+ /// # Panics
+ ///
+ /// - When the `rustls-0_23-webpki-roots` or `rustls-0_23-native-roots` features are enabled
+ /// and no default crypto provider has been loaded, this method will panic.
+ /// - When the `rustls-0_23-native-roots` or `rustls-0_22-native-roots` features are enabled
+ /// and the runtime system has no native root certificates, this method will panic.
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
pub fn new() -> Connector<
impl Service<
@@ -80,56 +103,114 @@ impl Connector<()> {
Connector {
connector: TcpConnector::new(resolver::resolver()).service(),
config: ConnectorConfig::default(),
- tls: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
+ tls: Self::build_tls(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
}
}
- /// Provides an empty TLS connector when no TLS feature is enabled.
- #[cfg(not(any(feature = "openssl", feature = "rustls")))]
- fn build_ssl(_: Vec>) -> OurTlsConnector {
- OurTlsConnector::None
- }
+ cfg_if::cfg_if! {
+ if #[cfg(any(feature = "rustls-0_23-webpki-roots", feature = "rustls-0_23-native-roots"))] {
+ /// Build TLS connector with Rustls v0.23, based on supplied ALPN protocols.
+ ///
+ /// Note that if other TLS crate features are enabled, Rustls v0.23 will be used.
+ fn build_tls(protocols: Vec>) -> OurTlsConnector {
+ use actix_tls::connect::rustls_0_23::{self, reexports::ClientConfig};
- /// Build TLS connector with rustls, based on supplied ALPN protocols
- ///
- /// Note that if both `openssl` and `rustls` features are enabled, rustls will be used.
- #[cfg(feature = "rustls")]
- fn build_ssl(protocols: Vec>) -> OurTlsConnector {
- use actix_tls::connect::rustls::{reexports::ClientConfig, webpki_roots_cert_store};
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "rustls-0_23-webpki-roots")] {
+ let certs = rustls_0_23::webpki_roots_cert_store();
+ } else if #[cfg(feature = "rustls-0_23-native-roots")] {
+ let certs = rustls_0_23::native_roots_cert_store().expect("Failed to find native root certificates");
+ }
+ }
- let mut config = ClientConfig::builder()
- .with_safe_defaults()
- .with_root_certificates(webpki_roots_cert_store())
- .with_no_client_auth();
+ let mut config = ClientConfig::builder()
+ .with_root_certificates(certs)
+ .with_no_client_auth();
- config.alpn_protocols = protocols;
+ config.alpn_protocols = protocols;
- OurTlsConnector::Rustls(std::sync::Arc::new(config))
- }
+ OurTlsConnector::Rustls023(std::sync::Arc::new(config))
+ }
+ } else if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-native-roots"))] {
+ /// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols.
+ fn build_tls(protocols: Vec>) -> OurTlsConnector {
+ use actix_tls::connect::rustls_0_22::{self, reexports::ClientConfig};
- /// Build TLS connector with openssl, based on supplied ALPN protocols
- #[cfg(all(feature = "openssl", not(feature = "rustls")))]
- fn build_ssl(protocols: Vec>) -> OurTlsConnector {
- use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod};
- use bytes::{BufMut, BytesMut};
+ cfg_if::cfg_if! {
+ if #[cfg(feature = "rustls-0_22-webpki-roots")] {
+ let certs = rustls_0_22::webpki_roots_cert_store();
+ } else if #[cfg(feature = "rustls-0_22-native-roots")] {
+ let certs = rustls_0_22::native_roots_cert_store().expect("Failed to find native root certificates");
+ }
+ }
- let mut alpn = BytesMut::with_capacity(20);
- for proto in &protocols {
- alpn.put_u8(proto.len() as u8);
- alpn.put(proto.as_slice());
+ let mut config = ClientConfig::builder()
+ .with_root_certificates(certs)
+ .with_no_client_auth();
+
+ config.alpn_protocols = protocols;
+
+ OurTlsConnector::Rustls022(std::sync::Arc::new(config))
+ }
+ } else if #[cfg(feature = "rustls-0_21")] {
+ /// Build TLS connector with Rustls v0.21, based on supplied ALPN protocols.
+ fn build_tls(protocols: Vec>) -> OurTlsConnector {
+ use actix_tls::connect::rustls_0_21::{reexports::ClientConfig, webpki_roots_cert_store};
+
+ let mut config = ClientConfig::builder()
+ .with_safe_defaults()
+ .with_root_certificates(webpki_roots_cert_store())
+ .with_no_client_auth();
+
+ config.alpn_protocols = protocols;
+
+ OurTlsConnector::Rustls021(std::sync::Arc::new(config))
+ }
+ } else if #[cfg(feature = "rustls-0_20")] {
+ /// Build TLS connector with Rustls v0.20, based on supplied ALPN protocols.
+ fn build_tls(protocols: Vec>) -> OurTlsConnector {
+ use actix_tls::connect::rustls_0_20::{reexports::ClientConfig, webpki_roots_cert_store};
+
+ let mut config = ClientConfig::builder()
+ .with_safe_defaults()
+ .with_root_certificates(webpki_roots_cert_store())
+ .with_no_client_auth();
+
+ config.alpn_protocols = protocols;
+
+ OurTlsConnector::Rustls020(std::sync::Arc::new(config))
+ }
+ } else if #[cfg(feature = "openssl")] {
+ /// Build TLS connector with OpenSSL, based on supplied ALPN protocols.
+ fn build_tls(protocols: Vec>) -> OurTlsConnector {
+ use actix_tls::connect::openssl::reexports::{SslConnector, SslMethod};
+ use bytes::{BufMut, BytesMut};
+
+ let mut alpn = BytesMut::with_capacity(20);
+ for proto in &protocols {
+ alpn.put_u8(proto.len() as u8);
+ alpn.put(proto.as_slice());
+ }
+
+ let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
+ if let Err(err) = ssl.set_alpn_protos(&alpn) {
+ log::error!("Can not set ALPN protocol: {err:?}");
+ }
+
+ OurTlsConnector::OpensslBuilder(ssl)
+ }
+ } else {
+ /// Provides an empty TLS connector when no TLS feature is enabled, or when only the
+ /// `rustls-0_23` crate feature is enabled.
+ fn build_tls(_: Vec>) -> OurTlsConnector {
+ OurTlsConnector::None
+ }
}
-
- let mut ssl = SslConnector::builder(SslMethod::tls()).unwrap();
- if let Err(err) = ssl.set_alpn_protos(&alpn) {
- log::error!("Can not set ALPN protocol: {:?}", err);
- }
-
- OurTlsConnector::OpensslBuilder(ssl)
}
}
impl Connector {
- /// Use custom connector.
+ /// Sets custom connector.
pub fn connector(self, connector: S1) -> Connector
where
Io1: ActixStream + fmt::Debug + 'static,
@@ -158,21 +239,28 @@ where
+ Clone
+ 'static,
{
- /// Tcp connection timeout, i.e. max time to connect to remote host including dns name
- /// resolution. Set to 5 second by default.
+ /// Sets TCP connection timeout.
+ ///
+ /// This is the max time allowed to connect to remote host, including DNS name resolution.
+ ///
+ /// By default, the timeout is 5 seconds.
pub fn timeout(mut self, timeout: Duration) -> Self {
self.config.timeout = timeout;
self
}
- /// Tls handshake timeout, i.e. max time to do tls handshake with remote host after tcp
- /// connection established. Set to 5 second by default.
+ /// Sets TLS handshake timeout.
+ ///
+ /// This is the max time allowed to perform the TLS handshake with remote host after TCP
+ /// connection is established.
+ ///
+ /// By default, the timeout is 5 seconds.
pub fn handshake_timeout(mut self, timeout: Duration) -> Self {
self.config.handshake_timeout = timeout;
self
}
- /// Use custom OpenSSL `SslConnector` instance.
+ /// Sets custom OpenSSL `SslConnector` instance.
#[cfg(feature = "openssl")]
pub fn openssl(
mut self,
@@ -191,13 +279,54 @@ where
self
}
- /// Use custom Rustls `ClientConfig` instance.
- #[cfg(feature = "rustls")]
+ /// Sets custom Rustls v0.20 `ClientConfig` instance.
+ #[cfg(feature = "rustls-0_20")]
pub fn rustls(
mut self,
- connector: std::sync::Arc,
+ connector: std::sync::Arc,
) -> Self {
- self.tls = OurTlsConnector::Rustls(connector);
+ self.tls = OurTlsConnector::Rustls020(connector);
+ self
+ }
+
+ /// Sets custom Rustls v0.21 `ClientConfig` instance.
+ #[cfg(feature = "rustls-0_21")]
+ pub fn rustls_021(
+ mut self,
+ connector: std::sync::Arc,
+ ) -> Self {
+ self.tls = OurTlsConnector::Rustls021(connector);
+ self
+ }
+
+ /// Sets custom Rustls v0.22 `ClientConfig` instance.
+ #[cfg(any(
+ feature = "rustls-0_22-webpki-roots",
+ feature = "rustls-0_22-native-roots",
+ ))]
+ pub fn rustls_0_22(
+ mut self,
+ connector: std::sync::Arc,
+ ) -> Self {
+ self.tls = OurTlsConnector::Rustls022(connector);
+ self
+ }
+
+ /// Sets custom Rustls v0.23 `ClientConfig` instance.
+ ///
+ /// In order to enable ALPN, set the `.alpn_protocols` field on the ClientConfig to the
+ /// following:
+ ///
+ /// ```no_run
+ /// vec![b"h2".to_vec(), b"http/1.1".to_vec()]
+ /// # ;
+ /// ```
+ #[cfg(feature = "rustls-0_23")]
+ pub fn rustls_0_23(
+ mut self,
+ connector: std::sync::Arc,
+ ) -> Self {
+ self.tls = OurTlsConnector::Rustls023(connector);
self
}
@@ -212,12 +341,12 @@ where
unimplemented!("actix-http client only supports versions http/1.1 & http/2")
}
};
- self.tls = Connector::build_ssl(versions);
+ self.tls = Connector::build_tls(versions);
self
}
- /// Sets the initial window size (in octets) for HTTP/2 stream-level flow control for
- /// received data.
+ /// Sets the initial window size (in bytes) for HTTP/2 stream-level flow control for received
+ /// data.
///
/// The default value is 65,535 and is good for APIs, but not for big objects.
pub fn initial_window_size(mut self, size: u32) -> Self {
@@ -225,7 +354,7 @@ where
self
}
- /// Sets the initial window size (in octets) for HTTP/2 connection-level flow control for
+ /// Sets the initial window size (in bytes) for HTTP/2 connection-level flow control for
/// received data.
///
/// The default value is 65,535 and is good for APIs, but not for big objects.
@@ -324,6 +453,7 @@ where
use actix_tls::connect::Connection;
use actix_utils::future::{ready, Ready};
+ #[allow(non_local_definitions)]
impl IntoConnectionIo for TcpConnection> {
fn into_connection_io(self) -> (Box, Protocol) {
let io = self.into_parts().0;
@@ -374,6 +504,7 @@ where
use actix_tls::connect::openssl::{reexports::AsyncSslStream, TlsConnector};
+ #[allow(non_local_definitions)]
impl IntoConnectionIo for TcpConnection> {
fn into_connection_io(self) -> (Box, Protocol) {
let sock = self.into_parts().0;
@@ -405,12 +536,118 @@ where
unreachable!("OpenSSL builder is built before this match.");
}
- #[cfg(feature = "rustls")]
- OurTlsConnector::Rustls(tls) => {
+ #[cfg(feature = "rustls-0_20")]
+ OurTlsConnector::Rustls020(tls) => {
const H2: &[u8] = b"h2";
- use actix_tls::connect::rustls::{reexports::AsyncTlsStream, TlsConnector};
+ use actix_tls::connect::rustls_0_20::{reexports::AsyncTlsStream, TlsConnector};
+ #[allow(non_local_definitions)]
+ impl IntoConnectionIo for TcpConnection> {
+ fn into_connection_io(self) -> (Box, Protocol) {
+ let sock = self.into_parts().0;
+ let h2 = sock
+ .get_ref()
+ .1
+ .alpn_protocol()
+ .map_or(false, |protos| protos.windows(2).any(|w| w == H2));
+ if h2 {
+ (Box::new(sock), Protocol::Http2)
+ } else {
+ (Box::new(sock), Protocol::Http1)
+ }
+ }
+ }
+
+ let handshake_timeout = self.config.handshake_timeout;
+
+ let tls_service = TlsConnectorService {
+ tcp_service: tcp_service_inner,
+ tls_service: TlsConnector::service(tls),
+ timeout: handshake_timeout,
+ };
+
+ Some(actix_service::boxed::rc_service(tls_service))
+ }
+
+ #[cfg(feature = "rustls-0_21")]
+ OurTlsConnector::Rustls021(tls) => {
+ const H2: &[u8] = b"h2";
+
+ use actix_tls::connect::rustls_0_21::{reexports::AsyncTlsStream, TlsConnector};
+
+ #[allow(non_local_definitions)]
+ impl IntoConnectionIo for TcpConnection> {
+ fn into_connection_io(self) -> (Box, Protocol) {
+ let sock = self.into_parts().0;
+ let h2 = sock
+ .get_ref()
+ .1
+ .alpn_protocol()
+ .map_or(false, |protos| protos.windows(2).any(|w| w == H2));
+ if h2 {
+ (Box::new(sock), Protocol::Http2)
+ } else {
+ (Box::new(sock), Protocol::Http1)
+ }
+ }
+ }
+
+ let handshake_timeout = self.config.handshake_timeout;
+
+ let tls_service = TlsConnectorService {
+ tcp_service: tcp_service_inner,
+ tls_service: TlsConnector::service(tls),
+ timeout: handshake_timeout,
+ };
+
+ Some(actix_service::boxed::rc_service(tls_service))
+ }
+
+ #[cfg(any(
+ feature = "rustls-0_22-webpki-roots",
+ feature = "rustls-0_22-native-roots",
+ ))]
+ OurTlsConnector::Rustls022(tls) => {
+ const H2: &[u8] = b"h2";
+
+ use actix_tls::connect::rustls_0_22::{reexports::AsyncTlsStream, TlsConnector};
+
+ #[allow(non_local_definitions)]
+ impl IntoConnectionIo for TcpConnection> {
+ fn into_connection_io(self) -> (Box, Protocol) {
+ let sock = self.into_parts().0;
+ let h2 = sock
+ .get_ref()
+ .1
+ .alpn_protocol()
+ .map_or(false, |protos| protos.windows(2).any(|w| w == H2));
+ if h2 {
+ (Box::new(sock), Protocol::Http2)
+ } else {
+ (Box::new(sock), Protocol::Http1)
+ }
+ }
+ }
+
+ let handshake_timeout = self.config.handshake_timeout;
+
+ let tls_service = TlsConnectorService {
+ tcp_service: tcp_service_inner,
+ tls_service: TlsConnector::service(tls),
+ timeout: handshake_timeout,
+ };
+
+ Some(actix_service::boxed::rc_service(tls_service))
+ }
+
+ #[cfg(feature = "rustls-0_23")]
+ OurTlsConnector::Rustls023(tls) => {
+ const H2: &[u8] = b"h2";
+
+ use actix_tls::connect::rustls_0_23::{reexports::AsyncTlsStream, TlsConnector};
+
+ #[allow(non_local_definitions)]
impl IntoConnectionIo for TcpConnection> {
fn into_connection_io(self) -> (Box, Protocol) {
let sock = self.into_parts().0;
@@ -498,6 +735,17 @@ where
/// service for establish tcp connection and do client tls handshake.
/// operation is canceled when timeout limit reached.
+#[cfg(any(
+ feature = "dangerous-h2c",
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22-webpki-roots",
+ feature = "rustls-0_22-native-roots",
+ feature = "rustls-0_23",
+ feature = "rustls-0_23-webpki-roots",
+ feature = "rustls-0_23-native-roots"
+))]
struct TlsConnectorService {
/// TCP connection is canceled on `TcpConnectorInnerService`'s timeout setting.
tcp_service: Tcp,
@@ -508,6 +756,15 @@ struct TlsConnectorService {
timeout: Duration,
}
+#[cfg(any(
+ feature = "dangerous-h2c",
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22-webpki-roots",
+ feature = "rustls-0_22-native-roots",
+ feature = "rustls-0_23",
+))]
impl Service for TlsConnectorService
where
Tcp:
@@ -789,7 +1046,6 @@ mod resolver {
use std::{cell::RefCell, net::SocketAddr};
use actix_tls::connect::Resolve;
- use futures_core::future::LocalBoxFuture;
use trust_dns_resolver::{
config::{ResolverConfig, ResolverOpts},
system_conf::read_system_conf,
@@ -824,7 +1080,7 @@ mod resolver {
// resolver struct is cached in thread local so new clients can reuse the existing instance
thread_local! {
- static TRUST_DNS_RESOLVER: RefCell