where
T: AsyncRead + AsyncWrite + Unpin + 'static,
diff --git a/actix-http/src/header/into_pair.rs b/actix-http/src/header/into_pair.rs
index 91c3e664..0a71f857 100644
--- a/actix-http/src/header/into_pair.rs
+++ b/actix-http/src/header/into_pair.rs
@@ -1,7 +1,5 @@
//! [`TryIntoHeaderPair`] trait and implementations.
-use std::convert::TryFrom as _;
-
use super::{
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
};
diff --git a/actix-http/src/header/into_value.rs b/actix-http/src/header/into_value.rs
index 6d369ee6..25390063 100644
--- a/actix-http/src/header/into_value.rs
+++ b/actix-http/src/header/into_value.rs
@@ -1,7 +1,5 @@
//! [`TryIntoHeaderValue`] trait and implementations.
-use std::convert::TryFrom as _;
-
use bytes::Bytes;
use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
use mime::Mime;
diff --git a/actix-http/src/header/map.rs b/actix-http/src/header/map.rs
index d7b4e6dd..e8118be9 100644
--- a/actix-http/src/header/map.rs
+++ b/actix-http/src/header/map.rs
@@ -1120,9 +1120,7 @@ mod tests {
assert!(vals.next().is_none());
}
- fn owned_pair<'a>(
- (name, val): (&'a HeaderName, &'a HeaderValue),
- ) -> (HeaderName, HeaderValue) {
+ fn owned_pair<'a>((name, val): (&'a HeaderName, &'a HeaderValue)) -> (HeaderName, HeaderValue) {
(name.clone(), val.clone())
}
}
diff --git a/actix-http/src/header/mod.rs b/actix-http/src/header/mod.rs
index a63174a9..79f91afe 100644
--- a/actix-http/src/header/mod.rs
+++ b/actix-http/src/header/mod.rs
@@ -3,33 +3,30 @@
// declaring new header consts will yield this error
#![allow(clippy::declare_interior_mutable_const)]
-use percent_encoding::{AsciiSet, CONTROLS};
-
// re-export from http except header map related items
pub use ::http::header::{
HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, ToStrError,
};
-
// re-export const header names, list is explicit so that any updates to `common` module do not
// conflict with this set
pub use ::http::header::{
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
- ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
- ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
- ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE,
- ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION,
- CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
- CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE,
- DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
- IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS,
- ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS,
- PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER,
- SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
- SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
- TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING,
- WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS,
- X_XSS_PROTECTION,
+ ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_METHODS,
+ ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
+ ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
+ AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
+ CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, CONTENT_SECURITY_POLICY,
+ CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, DNT, ETAG, EXPECT, EXPIRES,
+ FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE,
+ IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA,
+ PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE,
+ REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS,
+ SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE,
+ STRICT_TRANSPORT_SECURITY, TE, TRAILER, TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS,
+ USER_AGENT, VARY, VIA, WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS,
+ X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, X_XSS_PROTECTION,
};
+use percent_encoding::{AsciiSet, CONTROLS};
use crate::{error::ParseError, HttpMessage};
@@ -43,23 +40,22 @@ mod utils;
pub use self::{
as_name::AsHeaderName,
+ // re-export list is explicit so that any updates to `http` do not conflict with this set
+ common::{
+ CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
+ CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
+ X_FORWARDED_PROTO,
+ },
into_pair::TryIntoHeaderPair,
into_value::TryIntoHeaderValue,
map::HeaderMap,
shared::{
- parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
- LanguageTag, Quality, QualityItem,
+ parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
+ Quality, QualityItem,
},
utils::{fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode},
};
-// re-export list is explicit so that any updates to `http` do not conflict with this set
-pub use self::common::{
- CACHE_STATUS, CDN_CACHE_CONTROL, CROSS_ORIGIN_EMBEDDER_POLICY, CROSS_ORIGIN_OPENER_POLICY,
- CROSS_ORIGIN_RESOURCE_POLICY, PERMISSIONS_POLICY, X_FORWARDED_FOR, X_FORWARDED_HOST,
- X_FORWARDED_PROTO,
-};
-
/// An interface for types that already represent a valid header.
pub trait Header: TryIntoHeaderValue {
/// Returns the name of the header field.
diff --git a/actix-http/src/header/shared/content_encoding.rs b/actix-http/src/header/shared/content_encoding.rs
index bd25de70..c3b4bc4c 100644
--- a/actix-http/src/header/shared/content_encoding.rs
+++ b/actix-http/src/header/shared/content_encoding.rs
@@ -1,4 +1,4 @@
-use std::{convert::TryFrom, str::FromStr};
+use std::str::FromStr;
use derive_more::{Display, Error};
use http::header::InvalidHeaderValue;
diff --git a/actix-http/src/header/shared/mod.rs b/actix-http/src/header/shared/mod.rs
index 257e54d7..889c73c4 100644
--- a/actix-http/src/header/shared/mod.rs
+++ b/actix-http/src/header/shared/mod.rs
@@ -1,5 +1,7 @@
//! Originally taken from `hyper::header::shared`.
+pub use language_tags::LanguageTag;
+
mod charset;
mod content_encoding;
mod extended;
@@ -7,10 +9,11 @@ mod http_date;
mod quality;
mod quality_item;
-pub use self::charset::Charset;
-pub use self::content_encoding::ContentEncoding;
-pub use self::extended::{parse_extended_value, ExtendedValue};
-pub use self::http_date::HttpDate;
-pub use self::quality::{q, Quality};
-pub use self::quality_item::QualityItem;
-pub use language_tags::LanguageTag;
+pub use self::{
+ charset::Charset,
+ content_encoding::ContentEncoding,
+ extended::{parse_extended_value, ExtendedValue},
+ http_date::HttpDate,
+ quality::{q, Quality},
+ quality_item::QualityItem,
+};
diff --git a/actix-http/src/header/shared/quality.rs b/actix-http/src/header/shared/quality.rs
index c80dd0a8..c2276cf1 100644
--- a/actix-http/src/header/shared/quality.rs
+++ b/actix-http/src/header/shared/quality.rs
@@ -1,7 +1,4 @@
-use std::{
- convert::{TryFrom, TryInto},
- fmt,
-};
+use std::fmt;
use derive_more::{Display, Error};
diff --git a/actix-http/src/header/shared/quality_item.rs b/actix-http/src/header/shared/quality_item.rs
index 0b35b540..a41369c2 100644
--- a/actix-http/src/header/shared/quality_item.rs
+++ b/actix-http/src/header/shared/quality_item.rs
@@ -1,8 +1,7 @@
-use std::{cmp, convert::TryFrom as _, fmt, str};
-
-use crate::error::ParseError;
+use std::{cmp, fmt, str};
use super::Quality;
+use crate::error::ParseError;
/// Represents an item with a quality value as defined
/// in [RFC 7231 §5.3.1](https://datatracker.ietf.org/doc/html/rfc7231#section-5.3.1).
diff --git a/actix-http/src/http_message.rs b/actix-http/src/http_message.rs
index 198254e0..3ba9ef75 100644
--- a/actix-http/src/http_message.rs
+++ b/actix-http/src/http_message.rs
@@ -61,9 +61,7 @@ pub trait HttpMessage: Sized {
fn encoding(&self) -> Result<&'static Encoding, ContentTypeError> {
if let Some(mime_type) = self.mime_type()? {
if let Some(charset) = mime_type.get_param("charset") {
- if let Some(enc) =
- Encoding::for_label_no_replacement(charset.as_str().as_bytes())
- {
+ if let Some(enc) = Encoding::for_label_no_replacement(charset.as_str().as_bytes()) {
Ok(enc)
} else {
Err(ContentTypeError::UnknownEncoding)
@@ -146,7 +144,7 @@ mod tests {
.finish();
assert_eq!(req.content_type(), "text/plain");
let req = TestRequest::default()
- .insert_header(("content-type", "application/json; charset=utf=8"))
+ .insert_header(("content-type", "application/json; charset=utf-8"))
.finish();
assert_eq!(req.content_type(), "application/json");
let req = TestRequest::default().finish();
diff --git a/actix-http/src/lib.rs b/actix-http/src/lib.rs
index 8bf834f7..382295fb 100644
--- a/actix-http/src/lib.rs
+++ b/actix-http/src/lib.rs
@@ -28,8 +28,7 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
-pub use ::http::{uri, uri::Uri};
-pub use ::http::{Method, StatusCode, Version};
+pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
pub mod body;
mod builder;
@@ -57,22 +56,24 @@ pub mod test;
#[cfg(feature = "ws")]
pub mod ws;
-pub use self::builder::HttpServiceBuilder;
-pub use self::config::ServiceConfig;
-pub use self::error::Error;
-pub use self::extensions::Extensions;
-pub use self::header::ContentEncoding;
-pub use self::http_message::HttpMessage;
-pub use self::keep_alive::KeepAlive;
-pub use self::message::ConnectionType;
-pub use self::message::Message;
#[allow(deprecated)]
-pub use self::payload::{BoxedPayloadStream, Payload, PayloadStream};
-pub use self::requests::{Request, RequestHead, RequestHeadType};
-pub use self::responses::{Response, ResponseBuilder, ResponseHead};
-pub use self::service::HttpService;
-#[cfg(any(feature = "openssl", feature = "rustls"))]
+pub use self::payload::PayloadStream;
+#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
pub use self::service::TlsAcceptorConfig;
+pub use self::{
+ builder::HttpServiceBuilder,
+ config::ServiceConfig,
+ error::Error,
+ extensions::Extensions,
+ header::ContentEncoding,
+ http_message::HttpMessage,
+ keep_alive::KeepAlive,
+ message::{ConnectionType, Message},
+ payload::{BoxedPayloadStream, Payload},
+ requests::{Request, RequestHead, RequestHeadType},
+ responses::{Response, ResponseBuilder, ResponseHead},
+ service::HttpService,
+};
/// A major HTTP protocol version.
#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
diff --git a/actix-http/src/requests/mod.rs b/actix-http/src/requests/mod.rs
index fc35da65..4a27818a 100644
--- a/actix-http/src/requests/mod.rs
+++ b/actix-http/src/requests/mod.rs
@@ -3,5 +3,7 @@
mod head;
mod request;
-pub use self::head::{RequestHead, RequestHeadType};
-pub use self::request::Request;
+pub use self::{
+ head::{RequestHead, RequestHeadType},
+ request::Request,
+};
diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs
index ac358e8d..1750fb2f 100644
--- a/actix-http/src/requests/request.rs
+++ b/actix-http/src/requests/request.rs
@@ -10,8 +10,7 @@ use std::{
use http::{header, Method, Uri, Version};
use crate::{
- header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload,
- RequestHead,
+ header::HeaderMap, BoxedPayloadStream, Extensions, HttpMessage, Message, Payload, RequestHead,
};
/// An HTTP request.
@@ -234,7 +233,6 @@ impl fmt::Debug for Request
{
#[cfg(test)]
mod tests {
use super::*;
- use std::convert::TryFrom;
#[test]
fn test_basics() {
diff --git a/actix-http/src/responses/builder.rs b/actix-http/src/responses/builder.rs
index 063af92d..91c69ba5 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/responses/mod.rs b/actix-http/src/responses/mod.rs
index 899232b9..d9962823 100644
--- a/actix-http/src/responses/mod.rs
+++ b/actix-http/src/responses/mod.rs
@@ -5,7 +5,5 @@ mod head;
#[allow(clippy::module_inception)]
mod response;
-pub use self::builder::ResponseBuilder;
pub(crate) use self::head::BoxedResponseHead;
-pub use self::head::ResponseHead;
-pub use self::response::Response;
+pub use self::{builder::ResponseBuilder, head::ResponseHead, response::Response};
diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs
index 22177b84..fb38ba63 100644
--- a/actix-http/src/service.rs
+++ b/actix-http/src/service.rs
@@ -30,9 +30,9 @@ use crate::{
///
/// # Automatic HTTP Version Selection
/// There are two ways to select the HTTP version of an incoming connection:
-/// - One is to rely on the ALPN information that is provided when using a TLS (HTTPS); both
-/// versions are supported automatically when using either of the `.rustls()` or `.openssl()`
-/// finalizing methods.
+/// - One is to rely on the ALPN information that is provided when using TLS (HTTPS); both versions
+/// are supported automatically when using either of the `.rustls()` or `.openssl()` finalizing
+/// methods.
/// - The other is to read the first few bytes of the TCP stream. This is the only viable approach
/// for supporting H2C, which allows the HTTP/2 protocol to work over plaintext connections. Use
/// the `.tcp_auto_h2c()` finalizing method to enable this behavior.
@@ -200,13 +200,8 @@ where
/// The resulting service only supports HTTP/1.x.
pub fn tcp(
self,
- ) -> impl ServiceFactory<
- TcpStream,
- Config = (),
- Response = (),
- Error = DispatchError,
- InitError = (),
- > {
+ ) -> impl ServiceFactory
+ {
fn_service(|io: TcpStream| async {
let peer_addr = io.peer_addr().ok();
Ok((io, Protocol::Http1, peer_addr))
@@ -219,13 +214,8 @@ where
#[cfg(feature = "http2")]
pub fn tcp_auto_h2c(
self,
- ) -> impl ServiceFactory<
- TcpStream,
- Config = (),
- Response = (),
- Error = DispatchError,
- InitError = (),
- > {
+ ) -> impl ServiceFactory
+ {
fn_service(move |io: TcpStream| async move {
// subset of HTTP/2 preface defined by RFC 9113 §3.4
// this subset was chosen to maximize likelihood that peeking only once will allow us to
@@ -251,13 +241,13 @@ 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"))]
#[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"))]
impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
@@ -362,8 +352,8 @@ mod openssl {
}
}
-#[cfg(feature = "rustls")]
-mod rustls {
+#[cfg(feature = "rustls-0_20")]
+mod rustls_020 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@@ -458,6 +448,102 @@ mod rustls {
}
}
+#[cfg(feature = "rustls-0_21")]
+mod rustls_021 {
+ 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 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 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))
+ }
+ }
+}
+
impl ServiceFactory<(T, Protocol, Option)>
for HttpService
where
@@ -563,10 +649,7 @@ where
}
}
- pub(super) fn _poll_ready(
- &self,
- cx: &mut Context<'_>,
- ) -> Poll>> {
+ pub(super) fn _poll_ready(&self, cx: &mut Context<'_>) -> Poll>> {
ready!(self.flow.expect.poll_ready(cx).map_err(Into::into))?;
ready!(self.flow.service.poll_ready(cx).map_err(Into::into))?;
@@ -625,10 +708,7 @@ where
})
}
- fn call(
- &self,
- (io, proto, peer_addr): (T, Protocol, Option),
- ) -> Self::Future {
+ fn call(&self, (io, proto, peer_addr): (T, Protocol, Option)) -> Self::Future {
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
match proto {
diff --git a/actix-http/src/ws/codec.rs b/actix-http/src/ws/codec.rs
index 681649a7..ad487e40 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/dispatcher.rs b/actix-http/src/ws/dispatcher.rs
index 396f1e86..1354d5ae 100644
--- a/actix-http/src/ws/dispatcher.rs
+++ b/actix-http/src/ws/dispatcher.rs
@@ -70,15 +70,14 @@ mod inner {
task::{Context, Poll},
};
+ use actix_codec::Framed;
use actix_service::{IntoService, Service};
use futures_core::stream::Stream;
use local_channel::mpsc;
use pin_project_lite::pin_project;
- use tracing::debug;
-
- use actix_codec::Framed;
use tokio::io::{AsyncRead, AsyncWrite};
use tokio_util::codec::{Decoder, Encoder};
+ use tracing::debug;
use crate::{body::BoxBody, Response};
@@ -413,9 +412,7 @@ mod inner {
}
State::Error(_) => {
// flush write buffer
- if !this.framed.is_write_buf_empty()
- && this.framed.flush(cx).is_pending()
- {
+ if !this.framed.is_write_buf_empty() && this.framed.flush(cx).is_pending() {
return Poll::Pending;
}
Poll::Ready(Err(this.state.take_error()))
diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs
index dddb03d1..c9fb0cde 100644
--- a/actix-http/src/ws/frame.rs
+++ b/actix-http/src/ws/frame.rs
@@ -1,5 +1,4 @@
use std::cmp::min;
-use std::convert::TryFrom;
use bytes::{Buf, BufMut, BytesMut};
use tracing::debug;
@@ -222,9 +221,10 @@ impl Parser {
#[cfg(test)]
mod tests {
- use super::*;
use bytes::Bytes;
+ use super::*;
+
struct F {
finished: bool,
opcode: OpCode,
diff --git a/actix-http/src/ws/mask.rs b/actix-http/src/ws/mask.rs
index be72e563..115a8cf9 100644
--- a/actix-http/src/ws/mask.rs
+++ b/actix-http/src/ws/mask.rs
@@ -50,7 +50,7 @@ mod tests {
#[test]
fn test_apply_mask() {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
- let unmasked = vec![
+ let unmasked = [
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
0x12, 0x03,
];
diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs
index 2a0b0a99..87f9b38f 100644
--- a/actix-http/src/ws/mod.rs
+++ b/actix-http/src/ws/mod.rs
@@ -8,8 +8,7 @@ use std::io;
use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
-use crate::body::BoxBody;
-use crate::{header::HeaderValue, RequestHead, Response, ResponseBuilder};
+use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
mod codec;
mod dispatcher;
@@ -17,10 +16,12 @@ mod frame;
mod mask;
mod proto;
-pub use self::codec::{Codec, Frame, Item, Message};
-pub use self::dispatcher::Dispatcher;
-pub use self::frame::Parser;
-pub use self::proto::{hash_key, CloseCode, CloseReason, OpCode};
+pub use self::{
+ codec::{Codec, Frame, Item, Message},
+ dispatcher::Dispatcher,
+ frame::Parser,
+ proto::{hash_key, CloseCode, CloseReason, OpCode},
+};
/// WebSocket protocol errors.
#[derive(Debug, Display, Error, From)]
@@ -219,10 +220,8 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
- use crate::{header, Method};
-
use super::*;
- use crate::test::TestRequest;
+ use crate::{header, test::TestRequest, Method};
#[test]
fn test_handshake() {
diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs
index 7464bee4..b4d8ed1a 100644
--- a/actix-http/tests/test_openssl.rs
+++ b/actix-http/tests/test_openssl.rs
@@ -321,8 +321,7 @@ async fn h2_body_length() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| async {
- let body =
- once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
+ let body = once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
Ok::<_, Infallible>(
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs
index 0b8197a6..c94e579e 100644
--- a/actix-http/tests/test_rustls.rs
+++ b/actix-http/tests/test_rustls.rs
@@ -1,10 +1,9 @@
-#![cfg(feature = "rustls")]
-#![allow(clippy::uninlined_format_args)]
+#![cfg(feature = "rustls-0_21")]
-extern crate tls_rustls as rustls;
+extern crate tls_rustls_021 as rustls;
use std::{
- convert::{Infallible, TryFrom},
+ convert::Infallible,
io::{self, BufReader, Write},
net::{SocketAddr, TcpStream as StdTcpStream},
sync::Arc,
@@ -21,7 +20,7 @@ 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_21::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error};
@@ -90,11 +89,9 @@ pub fn get_negotiated_alpn_protocol(
config.alpn_protocols.push(client_alpn_protocol.to_vec());
- let mut sess = rustls::ClientConnection::new(
- Arc::new(config),
- ServerName::try_from("localhost").unwrap(),
- )
- .unwrap();
+ let mut sess =
+ rustls::ClientConnection::new(Arc::new(config), ServerName::try_from("localhost").unwrap())
+ .unwrap();
let mut sock = StdTcpStream::connect(addr).unwrap();
let mut stream = rustls::Stream::new(&mut sess, &mut sock);
@@ -112,7 +109,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -126,7 +123,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -144,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok())
})
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -162,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok())
})
- .rustls_with_config(
+ .rustls_021_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
@@ -183,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_021(tls_config())
})
.await;
@@ -209,7 +206,7 @@ async fn h2_content_length() {
];
ok::<_, Infallible>(Response::new(statuses[indx]))
})
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -281,7 +278,7 @@ async fn h2_headers() {
}
ok::<_, Infallible>(config.body(data.clone()))
})
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -320,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_021(tls_config())
})
.await;
@@ -337,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_021(tls_config())
})
.await;
@@ -363,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_021(tls_config())
})
.await;
@@ -388,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_021(tls_config())
})
.await;
@@ -414,7 +411,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
)
})
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -438,7 +435,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)),
)
})
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -467,7 +464,7 @@ async fn h2_response_http_error_handling() {
)
}))
}))
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -497,7 +494,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| err::, _>(BadRequest))
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -514,7 +511,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h1(|_| err::, _>(BadRequest))
- .rustls(tls_config())
+ .rustls_021(tls_config())
})
.await;
@@ -537,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_021(config)
})
.await;
@@ -559,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_021(config)
})
.await;
@@ -585,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_021(config)
})
.await;
diff --git a/actix-http/tests/test_server.rs b/actix-http/tests/test_server.rs
index 2efb336a..4ba64a53 100644
--- a/actix-http/tests/test_server.rs
+++ b/actix-http/tests/test_server.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use std::{
convert::Infallible,
io::{Read, Write},
@@ -139,7 +137,7 @@ async fn expect_continue_h1() {
#[actix_rt::test]
async fn chunked_payload() {
- let chunk_sizes = vec![32768, 32, 32768];
+ let chunk_sizes = [32768, 32, 32768];
let total_size: usize = chunk_sizes.iter().sum();
let mut srv = test_server(|| {
@@ -149,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| {
@@ -166,8 +164,7 @@ async fn chunked_payload() {
for chunk_size in chunk_sizes.iter() {
let mut bytes = Vec::new();
- let random_bytes: Vec =
- (0..*chunk_size).map(|_| rand::random::()).collect();
+ let random_bytes: Vec = (0..*chunk_size).map(|_| rand::random::()).collect();
bytes.extend(format!("{:X}\r\n", chunk_size).as_bytes());
bytes.extend(&random_bytes[..]);
@@ -352,8 +349,7 @@ async fn http10_keepalive() {
.await;
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
- let _ =
- stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
+ let _ = stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
let mut data = vec![0; 1024];
let _ = stream.read(&mut data);
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
@@ -404,7 +400,7 @@ async fn content_length() {
let mut srv = test_server(|| {
HttpService::build()
.h1(|req: Request| {
- let indx: usize = req.uri().path()[1..].parse().unwrap();
+ let idx: usize = req.uri().path()[1..].parse().unwrap();
let statuses = [
StatusCode::NO_CONTENT,
StatusCode::CONTINUE,
@@ -413,7 +409,7 @@ async fn content_length() {
StatusCode::OK,
StatusCode::NOT_FOUND,
];
- ok::<_, Infallible>(Response::new(statuses[indx]))
+ ok::<_, Infallible>(Response::new(statuses[idx]))
})
.tcp()
})
@@ -795,8 +791,9 @@ async fn not_modified_spec_h1() {
.map_into_boxed_body(),
// with no content-length
- "/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
- .map_into_boxed_body(),
+ "/body" => {
+ Response::with_body(StatusCode::NOT_MODIFIED, "1234").map_into_boxed_body()
+ }
// with manual content-length header and specific None body
"/cl-none" => {
diff --git a/actix-multipart-derive/CHANGES.md b/actix-multipart-derive/CHANGES.md
index 8dd7aa4d..e36a13d0 100644
--- a/actix-multipart-derive/CHANGES.md
+++ b/actix-multipart-derive/CHANGES.md
@@ -1,5 +1,12 @@
# Changes
-## 0.6.0 - 2023-02-26
+## Unreleased
+
+## 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
- Add `MultipartForm` derive macro.
diff --git a/actix-multipart-derive/Cargo.toml b/actix-multipart-derive/Cargo.toml
index e0b78fa2..75b4c723 100644
--- a/actix-multipart-derive/Cargo.toml
+++ b/actix-multipart-derive/Cargo.toml
@@ -1,13 +1,13 @@
[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 = "2018"
+edition = "2021"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
@@ -17,11 +17,11 @@ all-features = true
proc-macro = true
[dependencies]
-darling = "0.14"
+darling = "0.20"
parse-size = "1"
proc-macro2 = "1"
quote = "1"
-syn = "1"
+syn = "2"
[dev-dependencies]
actix-multipart = "0.6"
diff --git a/actix-multipart-derive/README.md b/actix-multipart-derive/README.md
index 44f08c7b..2737410f 100644
--- a/actix-multipart-derive/README.md
+++ b/actix-multipart-derive/README.md
@@ -4,7 +4,7 @@
[](https://crates.io/crates/actix-multipart-derive)
[](https://docs.rs/actix-multipart-derive/0.5.0)
-
+

[](https://deps.rs/crate/actix-multipart-derive/0.5.0)
@@ -14,4 +14,4 @@
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart-derive)
-- Minimum Supported Rust Version (MSRV): 1.59
+- Minimum Supported Rust Version (MSRV): 1.68
diff --git a/actix-multipart-derive/src/lib.rs b/actix-multipart-derive/src/lib.rs
index 2af023ae..9552ad2d 100644
--- a/actix-multipart-derive/src/lib.rs
+++ b/actix-multipart-derive/src/lib.rs
@@ -8,7 +8,7 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
-use std::{collections::HashSet, convert::TryFrom as _};
+use std::collections::HashSet;
use darling::{FromDeriveInput, FromField, FromMeta};
use parse_size::parse_size;
diff --git a/actix-multipart-derive/tests/trybuild.rs b/actix-multipart-derive/tests/trybuild.rs
index 7b9f14ed..88aa619c 100644
--- a/actix-multipart-derive/tests/trybuild.rs
+++ b/actix-multipart-derive/tests/trybuild.rs
@@ -1,4 +1,4 @@
-#[rustversion::stable(1.59)] // MSRV
+#[rustversion::stable(1.68)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();
diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md
index 4bb120c6..50faf7cf 100644
--- a/actix-multipart/CHANGES.md
+++ b/actix-multipart/CHANGES.md
@@ -1,47 +1,51 @@
# Changes
-## Unreleased - 2023-xx-xx
+## Unreleased
-## 0.6.0 - 2023-02-26
+## 0.6.1
+
+- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
+
+## 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]
@@ -52,31 +56,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]
@@ -84,19 +88,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
@@ -104,45 +108,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 a36fbffc..455d7db7 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.1"
authors = [
"Nikolay Kim ",
"Jacob Halsey ",
@@ -10,7 +10,7 @@ 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 = "2018"
+edition = "2021"
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
@@ -19,10 +19,10 @@ all-features = true
[features]
default = ["tempfile", "derive"]
derive = ["actix-multipart-derive"]
-tempfile = ["tempfile-dep", "tokio/fs"]
+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 }
@@ -38,9 +38,8 @@ mime = "0.3"
serde = "1"
serde_json = "1"
serde_plain = "1"
-# TODO(MSRV 1.60): replace with dep: prefix
-tempfile-dep = { package = "tempfile", version = "3.4", optional = true }
-tokio = { version = "1.24.2", features = ["sync"] }
+tempfile = { version = "3.4", optional = true }
+tokio = { version = "1.24.2", features = ["sync", "io-util"] }
[dev-dependencies]
actix-http = "3"
diff --git a/actix-multipart/README.md b/actix-multipart/README.md
index c4101e1c..8fe0328a 100644
--- a/actix-multipart/README.md
+++ b/actix-multipart/README.md
@@ -3,15 +3,15 @@
> 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.1)
+

-[](https://deps.rs/crate/actix-multipart/0.6.0)
+[](https://deps.rs/crate/actix-multipart/0.6.1)
[](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.59
+- Minimum Supported Rust Version (MSRV): 1.68
diff --git a/actix-multipart/src/form/bytes.rs b/actix-multipart/src/form/bytes.rs
index 7d64fffc..3c5e2eb1 100644
--- a/actix-multipart/src/form/bytes.rs
+++ b/actix-multipart/src/form/bytes.rs
@@ -27,11 +27,7 @@ pub struct Bytes {
impl<'t> FieldReader<'t> for Bytes {
type Future = LocalBoxFuture<'t, Result>;
- fn read_field(
- _: &'t HttpRequest,
- mut field: Field,
- limits: &'t mut Limits,
- ) -> Self::Future {
+ fn read_field(_: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
Box::pin(async move {
let mut buf = BytesMut::with_capacity(131_072);
diff --git a/actix-multipart/src/form/json.rs b/actix-multipart/src/form/json.rs
index 9951eaaa..fb90a82b 100644
--- a/actix-multipart/src/form/json.rs
+++ b/actix-multipart/src/form/json.rs
@@ -7,13 +7,12 @@ use derive_more::{Deref, DerefMut, Display, Error};
use futures_core::future::LocalBoxFuture;
use serde::de::DeserializeOwned;
+use super::FieldErrorHandler;
use crate::{
form::{bytes::Bytes, FieldReader, Limits},
Field, MultipartError,
};
-use super::FieldErrorHandler;
-
/// Deserialize from JSON.
#[derive(Debug, Deref, DerefMut)]
pub struct Json(pub T);
diff --git a/actix-multipart/src/form/mod.rs b/actix-multipart/src/form/mod.rs
index 711d4aeb..67adfd4b 100644
--- a/actix-multipart/src/form/mod.rs
+++ b/actix-multipart/src/form/mod.rs
@@ -429,8 +429,7 @@ mod tests {
#[actix_rt::test]
async fn test_options() {
- let srv =
- actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
+ let srv = actix_test::start(|| App::new().route("/", web::post().to(test_options_route)));
let mut form = multipart::Form::default();
form.add_text("field1", "value");
@@ -481,9 +480,7 @@ mod tests {
field3: Text,
}
- async fn test_field_renaming_route(
- form: MultipartForm,
- ) -> impl Responder {
+ async fn test_field_renaming_route(form: MultipartForm) -> impl Responder {
assert_eq!(&*form.field1, "renamed");
assert_eq!(&*form.field2, "field1");
assert_eq!(&*form.field3, "field3");
@@ -492,9 +489,8 @@ mod tests {
#[actix_rt::test]
async fn test_field_renaming() {
- let srv = actix_test::start(|| {
- App::new().route("/", web::post().to(test_field_renaming_route))
- });
+ let srv =
+ actix_test::start(|| App::new().route("/", web::post().to(test_field_renaming_route)));
let mut form = multipart::Form::default();
form.add_text("renamed", "renamed");
@@ -623,9 +619,7 @@ mod tests {
HttpResponse::Ok().finish()
}
- async fn test_upload_limits_file(
- form: MultipartForm,
- ) -> impl Responder {
+ async fn test_upload_limits_file(form: MultipartForm) -> impl Responder {
assert!(form.field.size > 0);
HttpResponse::Ok().finish()
}
diff --git a/actix-multipart/src/form/tempfile.rs b/actix-multipart/src/form/tempfile.rs
index 3c637e71..9371a026 100644
--- a/actix-multipart/src/form/tempfile.rs
+++ b/actix-multipart/src/form/tempfile.rs
@@ -11,7 +11,7 @@ use derive_more::{Display, Error};
use futures_core::future::LocalBoxFuture;
use futures_util::TryStreamExt as _;
use mime::Mime;
-use tempfile_dep::NamedTempFile;
+use tempfile::NamedTempFile;
use tokio::io::AsyncWriteExt;
use super::FieldErrorHandler;
@@ -39,23 +39,20 @@ pub struct TempFile {
impl<'t> FieldReader<'t> for TempFile {
type Future = LocalBoxFuture<'t, Result>;
- fn read_field(
- req: &'t HttpRequest,
- mut field: Field,
- limits: &'t mut Limits,
- ) -> Self::Future {
+ fn read_field(req: &'t HttpRequest, mut field: Field, limits: &'t mut Limits) -> Self::Future {
Box::pin(async move {
let config = TempFileConfig::from_req(req);
let field_name = field.name().to_owned();
let mut size = 0;
- let file = config.create_tempfile().map_err(|err| {
- config.map_error(req, &field_name, TempFileError::FileIo(err))
- })?;
+ let file = config
+ .create_tempfile()
+ .map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
- let mut file_async = tokio::fs::File::from_std(file.reopen().map_err(|err| {
- config.map_error(req, &field_name, TempFileError::FileIo(err))
- })?);
+ let mut file_async =
+ tokio::fs::File::from_std(file.reopen().map_err(|err| {
+ config.map_error(req, &field_name, TempFileError::FileIo(err))
+ })?);
while let Some(chunk) = field.try_next().await? {
limits.try_consume_limits(chunk.len(), false)?;
@@ -65,9 +62,10 @@ impl<'t> FieldReader<'t> for TempFile {
})?;
}
- file_async.flush().await.map_err(|err| {
- config.map_error(req, &field_name, TempFileError::FileIo(err))
- })?;
+ file_async
+ .flush()
+ .await
+ .map_err(|err| config.map_error(req, &field_name, TempFileError::FileIo(err)))?;
Ok(TempFile {
file,
@@ -131,12 +129,7 @@ impl TempFileConfig {
.unwrap_or(&DEFAULT_CONFIG)
}
- fn map_error(
- &self,
- req: &HttpRequest,
- field_name: &str,
- err: TempFileError,
- ) -> MultipartError {
+ fn map_error(&self, req: &HttpRequest, field_name: &str, err: TempFileError) -> MultipartError {
let source = if let Some(ref err_handler) = self.err_handler {
(err_handler)(err, req)
} else {
diff --git a/actix-multipart/src/lib.rs b/actix-multipart/src/lib.rs
index 73e10c91..615a8e6d 100644
--- a/actix-multipart/src/lib.rs
+++ b/actix-multipart/src/lib.rs
@@ -17,5 +17,7 @@ mod server;
pub mod form;
-pub use self::error::MultipartError;
-pub use self::server::{Field, Multipart};
+pub use self::{
+ error::MultipartError,
+ server::{Field, Multipart},
+};
diff --git a/actix-multipart/src/server.rs b/actix-multipart/src/server.rs
index 6726bc9d..c08031eb 100644
--- a/actix-multipart/src/server.rs
+++ b/actix-multipart/src/server.rs
@@ -2,9 +2,7 @@
use std::{
cell::{Cell, RefCell, RefMut},
- cmp,
- convert::TryFrom,
- fmt,
+ cmp, fmt,
marker::PhantomData,
pin::Pin,
rc::Rc,
@@ -163,8 +161,8 @@ impl InnerMultipart {
for h in hdrs {
let name =
HeaderName::try_from(h.name).map_err(|_| ParseError::Header)?;
- let value = HeaderValue::try_from(h.value)
- .map_err(|_| ParseError::Header)?;
+ let value =
+ HeaderValue::try_from(h.value).map_err(|_| ParseError::Header)?;
headers.append(name, value);
}
@@ -224,8 +222,7 @@ impl InnerMultipart {
if chunk.len() < boundary.len() {
continue;
}
- if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes()
- {
+ if &chunk[..2] == b"--" && &chunk[2..chunk.len() - 2] == boundary.as_bytes() {
break;
} else {
if chunk.len() < boundary.len() + 2 {
@@ -255,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.4.0)
+
+
+[](https://deps.rs/crate/actix-web/4.4.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.59+
+- Runs on stable Rust 1.68+
## Documentation
@@ -85,7 +98,7 @@ You may consider checking out [this directory](https://github.com/actix/examples
## Benchmarks
-One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r20&test=composite).
+One of the fastest web frameworks available according to the [TechEmpower Framework Benchmark](https://www.techempower.com/benchmarks/#section=data-r21&test=composite).
## License
diff --git a/actix-web/benches/responder.rs b/actix-web/benches/responder.rs
index 20aae335..c675eadf 100644
--- a/actix-web/benches/responder.rs
+++ b/actix-web/benches/responder.rs
@@ -87,7 +87,7 @@ fn future_responder(c: &mut Criterion) {
let start = Instant::now();
- let _res = rt.block_on(async { futs.await });
+ let _res = rt.block_on(futs);
start.elapsed()
})
@@ -99,8 +99,7 @@ fn responder(c: &mut Criterion) {
let req = TestRequest::default().to_http_request();
c.bench_function("responder", move |b| {
b.iter_custom(|_| {
- let responders =
- (0..100_000).map(|_| StringResponder(String::from("Hello World!!")));
+ let responders = (0..100_000).map(|_| StringResponder(String::from("Hello World!!")));
let start = Instant::now();
let _res = rt.block_on(async {
diff --git a/actix-web/benches/service.rs b/actix-web/benches/service.rs
index 87e51f17..9b29df8e 100644
--- a/actix-web/benches/service.rs
+++ b/actix-web/benches/service.rs
@@ -1,11 +1,12 @@
-use actix_service::Service;
-use actix_web::dev::{ServiceRequest, ServiceResponse};
-use actix_web::{web, App, Error, HttpResponse};
-use criterion::{criterion_main, Criterion};
-use std::cell::RefCell;
-use std::rc::Rc;
+use std::{cell::RefCell, rc::Rc};
-use actix_web::test::{init_service, ok_service, TestRequest};
+use actix_service::Service;
+use actix_web::{
+ dev::{ServiceRequest, ServiceResponse},
+ test::{init_service, ok_service, TestRequest},
+ web, App, Error, HttpResponse,
+};
+use criterion::{criterion_main, Criterion};
/// Criterion Benchmark for async Service
/// Should be used from within criterion group:
diff --git a/actix-web/examples/basic.rs b/actix-web/examples/basic.rs
index 60715f47..b8bc0982 100644
--- a/actix-web/examples/basic.rs
+++ b/actix-web/examples/basic.rs
@@ -1,5 +1,3 @@
-#![allow(clippy::uninlined_format_args)]
-
use actix_web::{get, middleware, web, App, HttpRequest, HttpResponse, HttpServer};
#[get("/resource1/{name}/index.html")]
@@ -22,6 +20,8 @@ async fn no_params() -> &'static str {
async fn main() -> std::io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
+ log::info!("starting HTTP server at http://localhost:8080");
+
HttpServer::new(|| {
App::new()
.wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
diff --git a/actix-web/examples/on-connect.rs b/actix-web/examples/on-connect.rs
index 0d56a8f2..dc9273b4 100644
--- a/actix-web/examples/on-connect.rs
+++ b/actix-web/examples/on-connect.rs
@@ -7,8 +7,7 @@
use std::{any::Any, io, net::SocketAddr};
use actix_web::{
- dev::Extensions, rt::net::TcpStream, web, App, HttpRequest, HttpResponse, HttpServer,
- Responder,
+ dev::Extensions, rt::net::TcpStream, web, App, HttpRequest, HttpResponse, HttpServer, Responder,
};
#[allow(dead_code)]
@@ -24,9 +23,7 @@ async fn route_whoami(req: HttpRequest) -> impl Responder {
Some(info) => HttpResponse::Ok().body(format!(
"Here is some info about your connection:\n\n{info:#?}",
)),
- None => {
- HttpResponse::InternalServerError().body("Missing expected request extension data")
- }
+ None => HttpResponse::InternalServerError().body("Missing expected request extension data"),
}
}
diff --git a/actix-web/src/app.rs b/actix-web/src/app.rs
index 353b82b1..06b66f62 100644
--- a/actix-web/src/app.rs
+++ b/actix-web/src/app.rs
@@ -141,8 +141,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) => {
@@ -264,12 +264,8 @@ where
pub fn default_service(mut self, svc: F) -> Self
where
F: IntoServiceFactory,
- U: ServiceFactory<
- ServiceRequest,
- Config = (),
- Response = ServiceResponse,
- Error = Error,
- > + 'static,
+ U: ServiceFactory
+ + 'static,
U::InitError: fmt::Debug,
{
let svc = svc
@@ -323,16 +319,7 @@ where
/// Middleware can be applied similarly to individual `Scope`s and `Resource`s.
/// See [`Scope::wrap`](crate::Scope::wrap) and [`Resource::wrap`].
///
- /// # Middleware Order
- /// Notice that the keyword for registering middleware is `wrap`. As you register middleware
- /// using `wrap` in the App builder, imagine wrapping layers around an inner App. The first
- /// middleware layer exposed to a Request is the outermost layer (i.e., the *last* registered in
- /// the builder chain). Consequently, the *first* middleware registered in the builder chain is
- /// the *last* to start executing during request processing.
- ///
- /// Ordering is less obvious when wrapped services also have middleware applied. In this case,
- /// middlewares are run in reverse order for `App` _and then_ in reverse order for the
- /// wrapped service.
+ /// For more info on middleware take a look at the [`middleware` module][crate::middleware].
///
/// # Examples
/// ```
diff --git a/actix-web/src/app_service.rs b/actix-web/src/app_service.rs
index 0fc85620..f2dca954 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
@@ -348,13 +344,17 @@ impl ServiceFactory for AppEntry {
#[cfg(test)]
mod tests {
- use std::sync::atomic::{AtomicBool, Ordering};
- use std::sync::Arc;
+ use std::sync::{
+ atomic::{AtomicBool, Ordering},
+ Arc,
+ };
use actix_service::Service;
- use crate::test::{init_service, TestRequest};
- use crate::{web, App, HttpResponse};
+ use crate::{
+ test::{init_service, TestRequest},
+ web, App, HttpResponse,
+ };
struct DropData(Arc);
diff --git a/actix-web/src/config.rs b/actix-web/src/config.rs
index 11eaf872..fba0c271 100644
--- a/actix-web/src/config.rs
+++ b/actix-web/src/config.rs
@@ -232,12 +232,8 @@ impl ServiceConfig {
pub fn default_service(&mut self, f: F) -> &mut Self
where
F: IntoServiceFactory,
- U: ServiceFactory<
- ServiceRequest,
- Config = (),
- Response = ServiceResponse,
- Error = Error,
- > + 'static,
+ U: ServiceFactory
+ + 'static,
U::InitError: std::fmt::Debug,
{
let svc = f
@@ -308,9 +304,11 @@ mod tests {
use bytes::Bytes;
use super::*;
- use crate::http::{Method, StatusCode};
- use crate::test::{assert_body_eq, call_service, init_service, read_body, TestRequest};
- use crate::{web, App, HttpRequest, HttpResponse};
+ use crate::{
+ http::{Method, StatusCode},
+ test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
+ web, App, HttpRequest, HttpResponse,
+ };
// allow deprecated `ServiceConfig::data`
#[allow(deprecated)]
diff --git a/actix-web/src/data.rs b/actix-web/src/data.rs
index 89104a1a..ebb98af3 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
@@ -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;
@@ -186,12 +203,14 @@ mod tests {
#[allow(deprecated)]
#[actix_rt::test]
async fn test_data_extractor() {
- let srv = init_service(App::new().data("TEST".to_string()).service(
- web::resource("/").to(|data: web::Data| {
- assert_eq!(data.to_lowercase(), "test");
- HttpResponse::Ok()
- }),
- ))
+ let srv = init_service(
+ App::new()
+ .data("TEST".to_string())
+ .service(web::resource("/").to(|data: web::Data| {
+ assert_eq!(data.to_lowercase(), "test");
+ HttpResponse::Ok()
+ })),
+ )
.await;
let req = TestRequest::default().to_request();
@@ -286,16 +305,17 @@ mod tests {
#[allow(deprecated)]
#[actix_rt::test]
async fn test_override_data() {
- let srv =
- init_service(App::new().data(1usize).service(
- web::resource("/").data(10usize).route(web::get().to(
+ let srv = init_service(
+ App::new()
+ .data(1usize)
+ .service(web::resource("/").data(10usize).route(web::get().to(
|data: web::Data| {
assert_eq!(**data, 10);
HttpResponse::Ok()
},
- )),
- ))
- .await;
+ ))),
+ )
+ .await;
let req = TestRequest::default().to_request();
let resp = srv.call(req).await.unwrap();
diff --git a/actix-web/src/dev.rs b/actix-web/src/dev.rs
index 5c7adfda..2a0791a1 100644
--- a/actix-web/src/dev.rs
+++ b/actix-web/src/dev.rs
@@ -7,26 +7,25 @@
//! - [`ConnectionInfo`]: Connection information
//! - [`PeerAddr`]: Connection information
+#[cfg(feature = "__compress")]
+pub use actix_http::encoding::Decoder as Decompress;
pub use actix_http::{Extensions, Payload, RequestHead, Response, ResponseHead};
+use actix_router::Patterns;
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
pub use actix_server::{Server, ServerHandle};
pub use actix_service::{
always_ready, fn_factory, fn_service, forward_ready, Service, ServiceFactory, Transform,
};
-#[cfg(feature = "__compress")]
-pub use actix_http::encoding::Decoder as Decompress;
-
-pub use crate::config::{AppConfig, AppService};
#[doc(hidden)]
pub use crate::handler::Handler;
-pub use crate::info::{ConnectionInfo, PeerAddr};
-pub use crate::rmap::ResourceMap;
-pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService};
-
-pub use crate::types::{JsonBody, Readlines, UrlEncoded};
-
-use actix_router::Patterns;
+pub use crate::{
+ config::{AppConfig, AppService},
+ info::{ConnectionInfo, PeerAddr},
+ rmap::ResourceMap,
+ service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService},
+ types::{JsonBody, Readlines, UrlEncoded},
+};
pub(crate) fn ensure_leading_slash(mut patterns: Patterns) -> Patterns {
match &mut patterns {
diff --git a/actix-web/src/error/macros.rs b/actix-web/src/error/macros.rs
index 78b1ed9f..8634557c 100644
--- a/actix-web/src/error/macros.rs
+++ b/actix-web/src/error/macros.rs
@@ -42,8 +42,7 @@ macro_rules! downcast_dyn {
/// Downcasts generic body to a specific type.
#[allow(dead_code)]
pub fn downcast_ref(&self) -> Option<&T> {
- if self.__private_get_type_id__(PrivateHelper(())).0
- == std::any::TypeId::of::()
+ if self.__private_get_type_id__(PrivateHelper(())).0 == std::any::TypeId::of::()
{
// SAFETY: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
@@ -59,8 +58,7 @@ macro_rules! downcast_dyn {
/// Downcasts a generic body to a mutable specific type.
#[allow(dead_code)]
pub fn downcast_mut(&mut self) -> Option<&mut T> {
- if self.__private_get_type_id__(PrivateHelper(())).0
- == std::any::TypeId::of::()
+ if self.__private_get_type_id__(PrivateHelper(())).0 == std::any::TypeId::of::()
{
// SAFETY: external crates cannot override the default
// implementation of `__private_get_type_id__`, since
@@ -76,7 +74,8 @@ macro_rules! downcast_dyn {
};
}
-pub(crate) use {downcast_dyn, downcast_get_type_id};
+pub(crate) use downcast_dyn;
+pub(crate) use downcast_get_type_id;
#[cfg(test)]
mod tests {
diff --git a/actix-web/src/error/mod.rs b/actix-web/src/error/mod.rs
index 604c539f..91a6bcc3 100644
--- a/actix-web/src/error/mod.rs
+++ b/actix-web/src/error/mod.rs
@@ -5,14 +5,10 @@
// expanded manually.
//
// See
-pub use actix_http::error::{
- ContentTypeError, DispatchError, HttpError, ParseError, PayloadError,
-};
-
+pub use actix_http::error::{ContentTypeError, DispatchError, HttpError, ParseError, PayloadError};
use derive_more::{Display, Error, From};
use serde_json::error::Error as JsonError;
-use serde_urlencoded::de::Error as FormDeError;
-use serde_urlencoded::ser::Error as FormError;
+use serde_urlencoded::{de::Error as FormDeError, ser::Error as FormError};
use url::ParseError as UrlParseError;
use crate::http::StatusCode;
@@ -23,10 +19,8 @@ mod internal;
mod macros;
mod response_error;
-pub use self::error::Error;
-pub use self::internal::*;
-pub use self::response_error::ResponseError;
-pub(crate) use macros::{downcast_dyn, downcast_get_type_id};
+pub(crate) use self::macros::{downcast_dyn, downcast_get_type_id};
+pub use self::{error::Error, internal::*, response_error::ResponseError};
/// A convenience [`Result`](std::result::Result) for Actix Web operations.
///
diff --git a/actix-web/src/extract.rs b/actix-web/src/extract.rs
index 84904a9e..249b5611 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 { .. } => {},
@@ -429,8 +429,10 @@ mod tests {
use serde::Deserialize;
use super::*;
- use crate::test::TestRequest;
- use crate::types::{Form, FormConfig};
+ use crate::{
+ test::TestRequest,
+ types::{Form, FormConfig},
+ };
#[derive(Deserialize, Debug, PartialEq)]
struct Info {
diff --git a/actix-web/src/guard/mod.rs b/actix-web/src/guard/mod.rs
index a9173a9d..35294a3c 100644
--- a/actix-web/src/guard/mod.rs
+++ b/actix-web/src/guard/mod.rs
@@ -51,7 +51,6 @@
use std::{
cell::{Ref, RefMut},
- convert::TryFrom,
rc::Rc,
};
@@ -62,8 +61,10 @@ use crate::{http::header::Header, service::ServiceRequest, HttpMessage as _};
mod acceptable;
mod host;
-pub use self::acceptable::Acceptable;
-pub use self::host::{Host, HostGuard};
+pub use self::{
+ acceptable::Acceptable,
+ host::{Host, HostGuard},
+};
/// Provides access to request parts that are useful during routing.
#[derive(Debug)]
diff --git a/actix-web/src/handler.rs b/actix-web/src/handler.rs
index 0c5e58e2..3d6b7f03 100644
--- a/actix-web/src/handler.rs
+++ b/actix-web/src/handler.rs
@@ -167,7 +167,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.rs b/actix-web/src/http/header/accept.rs
index 1be136b1..99c95175 100644
--- a/actix-web/src/http/header/accept.rs
+++ b/actix-web/src/http/header/accept.rs
@@ -78,7 +78,7 @@ common_header! {
// Tests from the RFC
crate::http::header::common_header_test!(
test1,
- vec![b"audio/*; q=0.2, audio/basic"],
+ [b"audio/*; q=0.2, audio/basic"],
Some(Accept(vec![
QualityItem::new("audio/*".parse().unwrap(), q(0.2)),
QualityItem::max("audio/basic".parse().unwrap()),
@@ -86,7 +86,7 @@ common_header! {
crate::http::header::common_header_test!(
test2,
- vec![b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
+ [b"text/plain; q=0.5, text/html, text/x-dvi; q=0.8, text/x-c"],
Some(Accept(vec![
QualityItem::new(mime::TEXT_PLAIN, q(0.5)),
QualityItem::max(mime::TEXT_HTML),
@@ -99,13 +99,13 @@ common_header! {
// Custom tests
crate::http::header::common_header_test!(
test3,
- vec![b"text/plain; charset=utf-8"],
+ [b"text/plain; charset=utf-8"],
Some(Accept(vec![
QualityItem::max(mime::TEXT_PLAIN_UTF_8),
])));
crate::http::header::common_header_test!(
test4,
- vec![b"text/plain; charset=utf-8; q=0.5"],
+ [b"text/plain; charset=utf-8; q=0.5"],
Some(Accept(vec![
QualityItem::new(mime::TEXT_PLAIN_UTF_8, q(0.5)),
])));
diff --git a/actix-web/src/http/header/accept_charset.rs b/actix-web/src/http/header/accept_charset.rs
index c7f7e1a6..43a7861f 100644
--- a/actix-web/src/http/header/accept_charset.rs
+++ b/actix-web/src/http/header/accept_charset.rs
@@ -57,6 +57,6 @@ common_header! {
test_parse_and_format {
// Test case from RFC
- common_header_test!(test1, vec![b"iso-8859-5, unicode-1-1;q=0.8"]);
+ common_header_test!(test1, [b"iso-8859-5, unicode-1-1;q=0.8"]);
}
}
diff --git a/actix-web/src/http/header/accept_encoding.rs b/actix-web/src/http/header/accept_encoding.rs
index 8c35179b..cc80e7bb 100644
--- a/actix-web/src/http/header/accept_encoding.rs
+++ b/actix-web/src/http/header/accept_encoding.rs
@@ -50,31 +50,31 @@ common_header! {
(AcceptEncoding, header::ACCEPT_ENCODING) => (QualityItem>)*
test_parse_and_format {
- common_header_test!(no_headers, vec![b""; 0], Some(AcceptEncoding(vec![])));
- common_header_test!(empty_header, vec![b""; 1], Some(AcceptEncoding(vec![])));
+ common_header_test!(no_headers, [b""; 0], Some(AcceptEncoding(vec![])));
+ common_header_test!(empty_header, [b""; 1], Some(AcceptEncoding(vec![])));
common_header_test!(
order_of_appearance,
- vec![b"br, gzip"],
+ [b"br, gzip"],
Some(AcceptEncoding(vec![
QualityItem::max(Preference::Specific(Encoding::brotli())),
QualityItem::max(Preference::Specific(Encoding::gzip())),
]))
);
- common_header_test!(any, vec![b"*"], Some(AcceptEncoding(vec![
+ common_header_test!(any, [b"*"], Some(AcceptEncoding(vec![
QualityItem::max(Preference::Any),
])));
// Note: Removed quality 1 from gzip
- common_header_test!(implicit_quality, vec![b"gzip, identity; q=0.5, *;q=0"]);
+ common_header_test!(implicit_quality, [b"gzip, identity; q=0.5, *;q=0"]);
// Note: Removed quality 1 from gzip
- common_header_test!(implicit_quality_out_of_order, vec![b"compress;q=0.5, gzip"]);
+ common_header_test!(implicit_quality_out_of_order, [b"compress;q=0.5, gzip"]);
common_header_test!(
only_gzip_no_identity,
- vec![b"gzip, *; q=0"],
+ [b"gzip, *; q=0"],
Some(AcceptEncoding(vec![
QualityItem::max(Preference::Specific(Encoding::gzip())),
QualityItem::zero(Preference::Any),
@@ -94,10 +94,7 @@ impl AcceptEncoding {
/// includes the server's supported encodings in the body plus a [`Vary`] header.
///
/// [`Vary`]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
- pub fn negotiate<'a>(
- &self,
- supported: impl Iterator- ,
- ) -> Option {
+ pub fn negotiate<'a>(&self, supported: impl Iterator
- ) -> Option {
// 1. If no Accept-Encoding field is in the request, any content-coding is considered
// acceptable by the user agent.
@@ -375,9 +372,7 @@ mod tests {
Some(Encoding::deflate())
);
assert_eq!(
- test.negotiate(
- [Encoding::gzip(), Encoding::deflate(), Encoding::identity()].iter()
- ),
+ test.negotiate([Encoding::gzip(), Encoding::deflate(), Encoding::identity()].iter()),
Some(Encoding::gzip())
);
assert_eq!(
diff --git a/actix-web/src/http/header/accept_language.rs b/actix-web/src/http/header/accept_language.rs
index 9943e121..b1d588f8 100644
--- a/actix-web/src/http/header/accept_language.rs
+++ b/actix-web/src/http/header/accept_language.rs
@@ -58,19 +58,19 @@ common_header! {
(AcceptLanguage, header::ACCEPT_LANGUAGE) => (QualityItem>)*
test_parse_and_format {
- common_header_test!(no_headers, vec![b""; 0], Some(AcceptLanguage(vec![])));
+ common_header_test!(no_headers, [b""; 0], Some(AcceptLanguage(vec![])));
- common_header_test!(empty_header, vec![b""; 1], Some(AcceptLanguage(vec![])));
+ common_header_test!(empty_header, [b""; 1], Some(AcceptLanguage(vec![])));
common_header_test!(
example_from_rfc,
- vec![b"da, en-gb;q=0.8, en;q=0.7"]
+ [b"da, en-gb;q=0.8, en;q=0.7"]
);
common_header_test!(
not_ordered_by_weight,
- vec![b"en-US, en; q=0.5, fr"],
+ [b"en-US, en; q=0.5, fr"],
Some(AcceptLanguage(vec![
QualityItem::max("en-US".parse().unwrap()),
QualityItem::new("en".parse().unwrap(), q(0.5)),
@@ -80,7 +80,7 @@ common_header! {
common_header_test!(
has_wildcard,
- vec![b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"],
+ [b"fr-CH, fr; q=0.9, en; q=0.8, de; q=0.7, *; q=0.5"],
Some(AcceptLanguage(vec![
QualityItem::max("fr-CH".parse().unwrap()),
QualityItem::new("fr".parse().unwrap(), q(0.9)),
@@ -137,7 +137,7 @@ impl AcceptLanguage {
b.quality.cmp(&a.quality)
});
- types.into_iter().map(|qitem| qitem.item).collect()
+ types.into_iter().map(|q_item| q_item.item).collect()
}
}
diff --git a/actix-web/src/http/header/allow.rs b/actix-web/src/http/header/allow.rs
index d0ef9648..b1c35c3d 100644
--- a/actix-web/src/http/header/allow.rs
+++ b/actix-web/src/http/header/allow.rs
@@ -48,15 +48,18 @@ crate::http::header::common_header! {
(Allow, header::ALLOW) => (Method)*
test_parse_and_format {
- // From the RFC
+ // from the RFC
+
crate::http::header::common_header_test!(
test1,
- vec![b"GET, HEAD, PUT"],
+ [b"GET, HEAD, PUT"],
Some(HeaderField(vec![Method::GET, Method::HEAD, Method::PUT])));
- // Own tests
+
+ // other tests
+
crate::http::header::common_header_test!(
test2,
- vec![b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"],
+ [b"OPTIONS, GET, PUT, POST, DELETE, HEAD, TRACE, CONNECT, PATCH"],
Some(HeaderField(vec![
Method::OPTIONS,
Method::GET,
@@ -67,9 +70,10 @@ crate::http::header::common_header! {
Method::TRACE,
Method::CONNECT,
Method::PATCH])));
+
crate::http::header::common_header_test!(
test3,
- vec![b""],
+ [b""],
Some(HeaderField(Vec::::new())));
}
}
diff --git a/actix-web/src/http/header/cache_control.rs b/actix-web/src/http/header/cache_control.rs
index 37629313..77e22d1c 100644
--- a/actix-web/src/http/header/cache_control.rs
+++ b/actix-web/src/http/header/cache_control.rs
@@ -47,13 +47,13 @@ common_header! {
(CacheControl, header::CACHE_CONTROL) => (CacheDirective)+
test_parse_and_format {
- common_header_test!(no_headers, vec![b""; 0], None);
- common_header_test!(empty_header, vec![b""; 1], None);
- common_header_test!(bad_syntax, vec![b"foo="], None);
+ common_header_test!(no_headers, [b""; 0], None);
+ common_header_test!(empty_header, [b""; 1], None);
+ common_header_test!(bad_syntax, [b"foo="], None);
common_header_test!(
multiple_headers,
- vec![&b"no-cache"[..], &b"private"[..]],
+ [&b"no-cache"[..], &b"private"[..]],
Some(CacheControl(vec![
CacheDirective::NoCache,
CacheDirective::Private,
@@ -62,7 +62,7 @@ common_header! {
common_header_test!(
argument,
- vec![b"max-age=100, private"],
+ [b"max-age=100, private"],
Some(CacheControl(vec![
CacheDirective::MaxAge(100),
CacheDirective::Private,
@@ -71,7 +71,7 @@ common_header! {
common_header_test!(
extension,
- vec![b"foo, bar=baz"],
+ [b"foo, bar=baz"],
Some(CacheControl(vec![
CacheDirective::Extension("foo".to_owned(), None),
CacheDirective::Extension("bar".to_owned(), Some("baz".to_owned())),
diff --git a/actix-web/src/http/header/content_disposition.rs b/actix-web/src/http/header/content_disposition.rs
index f743302a..0606f5ae 100644
--- a/actix-web/src/http/header/content_disposition.rs
+++ b/actix-web/src/http/header/content_disposition.rs
@@ -592,9 +592,8 @@ mod tests {
fn test_from_raw_basic() {
assert!(ContentDisposition::from_raw(&HeaderValue::from_static("")).is_err());
- let a = HeaderValue::from_static(
- "form-data; dummy=3; name=upload; filename=\"sample.png\"",
- );
+ let a =
+ HeaderValue::from_static("form-data; dummy=3; name=upload; filename=\"sample.png\"");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
@@ -648,8 +647,8 @@ mod tests {
charset: Charset::Ext(String::from("UTF-8")),
language_tag: None,
value: vec![
- 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r',
- b'a', b't', b'e', b's',
+ 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
+ b't', b'e', b's',
],
})],
};
@@ -665,8 +664,8 @@ mod tests {
charset: Charset::Ext(String::from("UTF-8")),
language_tag: None,
value: vec![
- 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r',
- b'a', b't', b'e', b's',
+ 0xc2, 0xa3, 0x20, b'a', b'n', b'd', 0x20, 0xe2, 0x82, 0xac, 0x20, b'r', b'a',
+ b't', b'e', b's',
],
})],
};
@@ -742,8 +741,8 @@ mod tests {
};
assert_eq!(a, b);
- let a = ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param"))
- .unwrap();
+ let a =
+ ContentDisposition::from_raw(&HeaderValue::from_static("unknown-disp-param")).unwrap();
let b = ContentDisposition {
disposition: DispositionType::Ext(String::from("unknown-disp-param")),
parameters: vec![],
@@ -782,8 +781,7 @@ mod tests {
Mainstream browsers like Firefox (gecko) and Chrome use UTF-8 directly as above.
(And now, only UTF-8 is handled by this implementation.)
*/
- let a =
- HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap();
+ let a = HeaderValue::from_str("form-data; name=upload; filename=\"文件.webp\"").unwrap();
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
@@ -803,9 +801,7 @@ mod tests {
disposition: DispositionType::FormData,
parameters: vec![
DispositionParam::Name(String::from("upload")),
- DispositionParam::Filename(String::from(
- "余固知謇謇之為患兮,忍而不能舍也.pptx",
- )),
+ DispositionParam::Filename(String::from("余固知謇謇之為患兮,忍而不能舍也.pptx")),
],
};
assert_eq!(a, b);
@@ -870,8 +866,7 @@ mod tests {
};
assert_eq!(a, b);
- let a =
- HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\"");
+ let a = HeaderValue::from_static("form-data; name=photo; filename=\"%74%65%73%74.png\"");
let a: ContentDisposition = ContentDisposition::from_raw(&a).unwrap();
let b = ContentDisposition {
disposition: DispositionType::FormData,
diff --git a/actix-web/src/http/header/content_language.rs b/actix-web/src/http/header/content_language.rs
index ff317e1d..5b0797ef 100644
--- a/actix-web/src/http/header/content_language.rs
+++ b/actix-web/src/http/header/content_language.rs
@@ -48,7 +48,7 @@ common_header! {
(ContentLanguage, CONTENT_LANGUAGE) => (QualityItem)+
test_parse_and_format {
- crate::http::header::common_header_test!(test1, vec![b"da"]);
- crate::http::header::common_header_test!(test2, vec![b"mi, en"]);
+ crate::http::header::common_header_test!(test1, [b"da"]);
+ crate::http::header::common_header_test!(test2, [b"mi, en"]);
}
}
diff --git a/actix-web/src/http/header/content_length.rs b/actix-web/src/http/header/content_length.rs
new file mode 100644
index 00000000..ad16dc40
--- /dev/null
+++ b/actix-web/src/http/header/content_length.rs
@@ -0,0 +1,238 @@
+use std::{convert::Infallible, str};
+
+use derive_more::{Deref, DerefMut};
+
+use crate::{
+ error::ParseError,
+ http::header::{
+ from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue, CONTENT_LENGTH,
+ },
+ HttpMessage,
+};
+
+/// `Content-Length` header, defined in [RFC 9110 §8.6].
+///
+/// The Content-Length
+///
+/// # ABNF
+///
+/// ```plain
+/// Content-Length = 1*DIGIT
+/// ```
+///
+/// # Example Values
+///
+/// - `0`
+/// - `3495`
+///
+/// # Examples
+///
+/// ```
+/// use actix_web::{http::header::ContentLength, HttpResponse};
+///
+/// let res_empty = HttpResponse::Ok()
+/// .insert_header(ContentLength(0));
+///
+/// let res_fake_cl = HttpResponse::Ok()
+/// .insert_header(ContentLength(3_495));
+/// ```
+///
+/// [RFC 9110 §8.6]: https://www.rfc-editor.org/rfc/rfc9110#name-content-length
+#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut)]
+pub struct ContentLength(pub usize);
+
+impl ContentLength {
+ /// Returns Content-Length value.
+ pub fn into_inner(&self) -> usize {
+ self.0
+ }
+}
+
+impl str::FromStr for ContentLength {
+ type Err = ::Err;
+
+ #[inline]
+ fn from_str(val: &str) -> Result {
+ let val = val.trim();
+
+ // decoder prevents this case
+ debug_assert!(!val.starts_with('+'));
+
+ val.parse().map(Self)
+ }
+}
+
+impl TryIntoHeaderValue for ContentLength {
+ type Error = Infallible;
+
+ fn try_into_value(self) -> Result {
+ Ok(HeaderValue::from(self.0))
+ }
+}
+
+impl Header for ContentLength {
+ fn name() -> HeaderName {
+ CONTENT_LENGTH
+ }
+
+ fn parse(msg: &M) -> Result {
+ let val = from_one_raw_str(msg.headers().get(Self::name()))?;
+
+ // decoder prevents multiple CL headers
+ debug_assert_eq!(msg.headers().get_all(Self::name()).count(), 1);
+
+ Ok(val)
+ }
+}
+
+impl From for usize {
+ fn from(ContentLength(len): ContentLength) -> Self {
+ len
+ }
+}
+
+impl From for ContentLength {
+ fn from(len: usize) -> Self {
+ ContentLength(len)
+ }
+}
+
+impl PartialEq for ContentLength {
+ fn eq(&self, other: &usize) -> bool {
+ self.0 == *other
+ }
+}
+
+impl PartialEq for usize {
+ fn eq(&self, other: &ContentLength) -> bool {
+ *self == other.0
+ }
+}
+
+impl PartialOrd for ContentLength {
+ fn partial_cmp(&self, other: &usize) -> Option {
+ self.0.partial_cmp(other)
+ }
+}
+
+impl PartialOrd for usize {
+ fn partial_cmp(&self, other: &ContentLength) -> Option {
+ self.partial_cmp(&other.0)
+ }
+}
+
+#[cfg(test)]
+mod tests {
+ use std::fmt;
+
+ use super::*;
+ use crate::{http::header::Header, test::TestRequest, HttpRequest};
+
+ fn req_from_raw_headers, V: AsRef<[u8]>>(
+ header_lines: I,
+ ) -> HttpRequest {
+ header_lines
+ .into_iter()
+ .fold(TestRequest::default(), |req, item| {
+ req.append_header((H::name(), item.as_ref().to_vec()))
+ })
+ .to_http_request()
+ }
+
+ #[track_caller]
+ pub(crate) fn assert_parse_fail<
+ H: Header + fmt::Debug,
+ I: IntoIterator
- ,
+ V: AsRef<[u8]>,
+ >(
+ headers: I,
+ ) {
+ let req = req_from_raw_headers::(headers);
+ H::parse(&req).unwrap_err();
+ }
+
+ #[track_caller]
+ pub(crate) fn assert_parse_eq<
+ H: Header + fmt::Debug + PartialEq,
+ I: IntoIterator
- ,
+ V: AsRef<[u8]>,
+ >(
+ headers: I,
+ expect: H,
+ ) {
+ let req = req_from_raw_headers::(headers);
+ assert_eq!(H::parse(&req).unwrap(), expect);
+ }
+
+ #[test]
+ fn missing_header() {
+ assert_parse_fail::([""; 0]);
+ assert_parse_fail::([""]);
+ }
+
+ #[test]
+ fn bad_header() {
+ assert_parse_fail::(["-123"]);
+ assert_parse_fail::(["123_456"]);
+ assert_parse_fail::(["123.456"]);
+
+ // too large for u64 (2^64, 2^64 + 1)
+ assert_parse_fail::(["18446744073709551616"]);
+ assert_parse_fail::(["18446744073709551617"]);
+
+ // hex notation
+ assert_parse_fail::(["0x123"]);
+
+ // multi-value
+ assert_parse_fail::(["0, 123"]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn bad_header_plus() {
+ // prevented by HTTP decoder anyway
+ assert_parse_fail::(["+123"]);
+ }
+
+ #[test]
+ #[should_panic]
+ fn bad_multiple_value() {
+ // prevented by HTTP decoder anyway
+ assert_parse_fail::(["0", "123"]);
+ }
+
+ #[test]
+ fn good_header() {
+ assert_parse_eq::(["0"], ContentLength(0));
+ assert_parse_eq::(["1"], ContentLength(1));
+ assert_parse_eq::(["123"], ContentLength(123));
+
+ // value that looks like octal notation is not interpreted as such
+ assert_parse_eq::(["0123"], ContentLength(123));
+
+ // whitespace variations
+ assert_parse_eq::([" 0"], ContentLength(0));
+ assert_parse_eq::(["0 "], ContentLength(0));
+ assert_parse_eq::([" 0 "], ContentLength(0));
+
+ // large value (2^64 - 1)
+ assert_parse_eq::(
+ ["18446744073709551615"],
+ ContentLength(18_446_744_073_709_551_615),
+ );
+ }
+
+ #[test]
+ fn equality() {
+ assert!(ContentLength(0) == ContentLength(0));
+ assert!(ContentLength(0) == 0);
+ assert!(0 != ContentLength(123));
+ }
+
+ #[test]
+ fn ordering() {
+ assert!(ContentLength(0) < ContentLength(123));
+ assert!(ContentLength(0) < 123);
+ assert!(0 < ContentLength(123));
+ }
+}
diff --git a/actix-web/src/http/header/content_range.rs b/actix-web/src/http/header/content_range.rs
index bcbe77e6..2604f9ba 100644
--- a/actix-web/src/http/header/content_range.rs
+++ b/actix-web/src/http/header/content_range.rs
@@ -13,61 +13,60 @@ crate::http::header::common_header! {
test_parse_and_format {
crate::http::header::common_header_test!(test_bytes,
- vec![b"bytes 0-499/500"],
+ [b"bytes 0-499/500"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: Some((0, 499)),
instance_length: Some(500)
})));
crate::http::header::common_header_test!(test_bytes_unknown_len,
- vec![b"bytes 0-499/*"],
+ [b"bytes 0-499/*"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: Some((0, 499)),
instance_length: None
})));
crate::http::header::common_header_test!(test_bytes_unknown_range,
- vec![b"bytes */500"],
+ [b"bytes */500"],
Some(ContentRange(ContentRangeSpec::Bytes {
range: None,
instance_length: Some(500)
})));
crate::http::header::common_header_test!(test_unregistered,
- vec![b"seconds 1-2"],
+ [b"seconds 1-2"],
Some(ContentRange(ContentRangeSpec::Unregistered {
unit: "seconds".to_owned(),
resp: "1-2".to_owned()
})));
crate::http::header::common_header_test!(test_no_len,
- vec![b"bytes 0-499"],
+ [b"bytes 0-499"],
None::);
crate::http::header::common_header_test!(test_only_unit,
- vec![b"bytes"],
+ [b"bytes"],
None::);
crate::http::header::common_header_test!(test_end_less_than_start,
- vec![b"bytes 499-0/500"],
+ [b"bytes 499-0/500"],
None::);
crate::http::header::common_header_test!(test_blank,
- vec![b""],
+ [b""],
None::);
crate::http::header::common_header_test!(test_bytes_many_spaces,
- vec![b"bytes 1-2/500 3"],
+ [b"bytes 1-2/500 3"],
None::);
crate::http::header::common_header_test!(test_bytes_many_slashes,
- vec![b"bytes 1-2/500/600"],
+ [b"bytes 1-2/500/600"],
None::);
crate::http::header::common_header_test!(test_bytes_many_dashes,
- vec![b"bytes 1-2-3/500"],
+ [b"bytes 1-2-3/500"],
None::);
-
}
}
@@ -113,22 +112,13 @@ pub enum ContentRangeSpec {
},
}
-fn split_in_two(s: &str, separator: char) -> Option<(&str, &str)> {
- let mut iter = s.splitn(2, separator);
- match (iter.next(), iter.next()) {
- (Some(a), Some(b)) => Some((a, b)),
- _ => None,
- }
-}
-
impl FromStr for ContentRangeSpec {
type Err = ParseError;
fn from_str(s: &str) -> Result {
- let res = match split_in_two(s, ' ') {
+ let res = match s.split_once(' ') {
Some(("bytes", resp)) => {
- let (range, instance_length) =
- split_in_two(resp, '/').ok_or(ParseError::Header)?;
+ let (range, instance_length) = resp.split_once('/').ok_or(ParseError::Header)?;
let instance_length = if instance_length == "*" {
None
@@ -140,7 +130,7 @@ impl FromStr for ContentRangeSpec {
None
} else {
let (first_byte, last_byte) =
- split_in_two(range, '-').ok_or(ParseError::Header)?;
+ range.split_once('-').ok_or(ParseError::Header)?;
let first_byte = first_byte.parse().map_err(|_| ParseError::Header)?;
let last_byte = last_byte.parse().map_err(|_| ParseError::Header)?;
if last_byte < first_byte {
diff --git a/actix-web/src/http/header/content_type.rs b/actix-web/src/http/header/content_type.rs
index 1fc75d0e..c43ef8a2 100644
--- a/actix-web/src/http/header/content_type.rs
+++ b/actix-web/src/http/header/content_type.rs
@@ -1,110 +1,104 @@
-use super::CONTENT_TYPE;
use mime::Mime;
+use super::CONTENT_TYPE;
+
crate::http::header::common_header! {
- /// `Content-Type` header, defined
- /// in [RFC 7231 §3.1.1.5](https://datatracker.ietf.org/doc/html/rfc7231#section-3.1.1.5)
+ /// `Content-Type` header, defined in [RFC 9110 §8.3].
///
- /// The `Content-Type` header field indicates the media type of the
- /// associated representation: either the representation enclosed in the
- /// message payload or the selected representation, as determined by the
- /// message semantics. The indicated media type defines both the data
- /// format and how that data is intended to be processed by a recipient,
- /// within the scope of the received message semantics, after any content
- /// codings indicated by Content-Encoding are decoded.
+ /// The `Content-Type` header field indicates the media type of the associated representation:
+ /// either the representation enclosed in the message payload or the selected representation,
+ /// as determined by the message semantics. The indicated media type defines both the data
+ /// format and how that data is intended to be processed by a recipient, within the scope of the
+ /// received message semantics, after any content codings indicated by Content-Encoding are
+ /// decoded.
///
- /// Although the `mime` crate allows the mime options to be any slice, this crate
- /// forces the use of Vec. This is to make sure the same header can't have more than 1 type. If
- /// this is an issue, it's possible to implement `Header` on a custom struct.
+ /// Although the `mime` crate allows the mime options to be any slice, this crate forces the use
+ /// of Vec. This is to make sure the same header can't have more than 1 type. If this is an
+ /// issue, it's possible to implement `Header` on a custom struct.
///
/// # ABNF
+ ///
/// ```plain
/// Content-Type = media-type
/// ```
///
/// # Example Values
- /// * `text/html; charset=utf-8`
- /// * `application/json`
+ ///
+ /// - `text/html; charset=utf-8`
+ /// - `application/json`
///
/// # Examples
- /// ```
- /// use actix_web::HttpResponse;
- /// use actix_web::http::header::ContentType;
- ///
- /// let mut builder = HttpResponse::Ok();
- /// builder.insert_header(
- /// ContentType::json()
- /// );
- /// ```
///
/// ```
- /// use actix_web::HttpResponse;
- /// use actix_web::http::header::ContentType;
+ /// use actix_web::{http::header::ContentType, HttpResponse};
///
- /// let mut builder = HttpResponse::Ok();
- /// builder.insert_header(
- /// ContentType(mime::TEXT_HTML)
- /// );
+ /// let res_json = HttpResponse::Ok()
+ /// .insert_header(ContentType::json());
+ ///
+ /// let res_html = HttpResponse::Ok()
+ /// .insert_header(ContentType(mime::TEXT_HTML));
/// ```
+ ///
+ /// [RFC 9110 §8.3]: https://datatracker.ietf.org/doc/html/rfc9110#section-8.3
(ContentType, CONTENT_TYPE) => [Mime]
test_parse_and_format {
crate::http::header::common_header_test!(
- test1,
- vec![b"text/html"],
+ test_text_html,
+ [b"text/html"],
Some(HeaderField(mime::TEXT_HTML)));
+ crate::http::header::common_header_test!(
+ test_image_star,
+ [b"image/*"],
+ Some(HeaderField(mime::IMAGE_STAR)));
+
}
}
impl ContentType {
- /// A constructor to easily create a `Content-Type: application/json`
- /// header.
+ /// Constructs a `Content-Type: application/json` header.
#[inline]
pub fn json() -> ContentType {
ContentType(mime::APPLICATION_JSON)
}
- /// A constructor to easily create a `Content-Type: text/plain;
- /// charset=utf-8` header.
+ /// Constructs a `Content-Type: text/plain; charset=utf-8` header.
#[inline]
pub fn plaintext() -> ContentType {
ContentType(mime::TEXT_PLAIN_UTF_8)
}
- /// A constructor to easily create a `Content-Type: text/html; charset=utf-8`
- /// header.
+ /// Constructs a `Content-Type: text/html; charset=utf-8` header.
#[inline]
pub fn html() -> ContentType {
ContentType(mime::TEXT_HTML_UTF_8)
}
- /// A constructor to easily create a `Content-Type: text/xml` header.
+ /// Constructs a `Content-Type: text/xml` header.
#[inline]
pub fn xml() -> ContentType {
ContentType(mime::TEXT_XML)
}
- /// A constructor to easily create a `Content-Type:
- /// application/www-form-url-encoded` header.
+ /// Constructs a `Content-Type: application/www-form-url-encoded` header.
#[inline]
pub fn form_url_encoded() -> ContentType {
ContentType(mime::APPLICATION_WWW_FORM_URLENCODED)
}
- /// A constructor to easily create a `Content-Type: image/jpeg` header.
+ /// Constructs a `Content-Type: image/jpeg` header.
#[inline]
pub fn jpeg() -> ContentType {
ContentType(mime::IMAGE_JPEG)
}
- /// A constructor to easily create a `Content-Type: image/png` header.
+ /// Constructs a `Content-Type: image/png` header.
#[inline]
pub fn png() -> ContentType {
ContentType(mime::IMAGE_PNG)
}
- /// A constructor to easily create a `Content-Type:
- /// application/octet-stream` header.
+ /// Constructs a `Content-Type: application/octet-stream` header.
#[inline]
pub fn octet_stream() -> ContentType {
ContentType(mime::APPLICATION_OCTET_STREAM)
diff --git a/actix-web/src/http/header/date.rs b/actix-web/src/http/header/date.rs
index f6274021..ac30424f 100644
--- a/actix-web/src/http/header/date.rs
+++ b/actix-web/src/http/header/date.rs
@@ -1,6 +1,7 @@
-use super::{HttpDate, DATE};
use std::time::SystemTime;
+use super::{HttpDate, DATE};
+
crate::http::header::common_header! {
/// `Date` header, defined
/// in [RFC 7231 §7.1.1.2](https://datatracker.ietf.org/doc/html/rfc7231#section-7.1.1.2)
@@ -31,7 +32,7 @@ crate::http::header::common_header! {
(Date, DATE) => [HttpDate]
test_parse_and_format {
- crate::http::header::common_header_test!(test1, vec![b"Tue, 15 Nov 1994 08:12:31 GMT"]);
+ crate::http::header::common_header_test!(test1, [b"Tue, 15 Nov 1994 08:12:31 GMT"]);
}
}
diff --git a/actix-web/src/http/header/entity.rs b/actix-web/src/http/header/entity.rs
index 0eaa12b5..a5ef3c5b 100644
--- a/actix-web/src/http/header/entity.rs
+++ b/actix-web/src/http/header/entity.rs
@@ -152,9 +152,7 @@ impl FromStr for EntityTag {
return Err(crate::error::ParseError::Header);
}
// The etag is weak if its first char is not a DQUOTE.
- if slice.len() >= 2
- && slice.starts_with('"')
- && check_slice_validity(&slice[1..length - 1])
+ if slice.len() >= 2 && slice.starts_with('"') && check_slice_validity(&slice[1..length - 1])
{
// No need to check if the last char is a DQUOTE,
// we already did that above.
diff --git a/actix-web/src/http/header/etag.rs b/actix-web/src/http/header/etag.rs
index 78f5447b..b8220828 100644
--- a/actix-web/src/http/header/etag.rs
+++ b/actix-web/src/http/header/etag.rs
@@ -49,50 +49,50 @@ crate::http::header::common_header! {
test_parse_and_format {
// From the RFC
crate::http::header::common_header_test!(test1,
- vec![b"\"xyzzy\""],
+ [b"\"xyzzy\""],
Some(ETag(EntityTag::new_strong("xyzzy".to_owned()))));
crate::http::header::common_header_test!(test2,
- vec![b"W/\"xyzzy\""],
+ [b"W/\"xyzzy\""],
Some(ETag(EntityTag::new_weak("xyzzy".to_owned()))));
crate::http::header::common_header_test!(test3,
- vec![b"\"\""],
+ [b"\"\""],
Some(ETag(EntityTag::new_strong("".to_owned()))));
// Own tests
crate::http::header::common_header_test!(test4,
- vec![b"\"foobar\""],
+ [b"\"foobar\""],
Some(ETag(EntityTag::new_strong("foobar".to_owned()))));
crate::http::header::common_header_test!(test5,
- vec![b"\"\""],
+ [b"\"\""],
Some(ETag(EntityTag::new_strong("".to_owned()))));
crate::http::header::common_header_test!(test6,
- vec![b"W/\"weak-etag\""],
+ [b"W/\"weak-etag\""],
Some(ETag(EntityTag::new_weak("weak-etag".to_owned()))));
crate::http::header::common_header_test!(test7,
- vec![b"W/\"\x65\x62\""],
+ [b"W/\"\x65\x62\""],
Some(ETag(EntityTag::new_weak("\u{0065}\u{0062}".to_owned()))));
crate::http::header::common_header_test!(test8,
- vec![b"W/\"\""],
+ [b"W/\"\""],
Some(ETag(EntityTag::new_weak("".to_owned()))));
crate::http::header::common_header_test!(test9,
- vec![b"no-dquotes"],
+ [b"no-dquotes"],
None::);
crate::http::header::common_header_test!(test10,
- vec![b"w/\"the-first-w-is-case-sensitive\""],
+ [b"w/\"the-first-w-is-case-sensitive\""],
None::);
crate::http::header::common_header_test!(test11,
- vec![b""],
+ [b""],
None::);
crate::http::header::common_header_test!(test12,
- vec![b"\"unmatched-dquotes1"],
+ [b"\"unmatched-dquotes1"],
None::);
crate::http::header::common_header_test!(test13,
- vec![b"unmatched-dquotes2\""],
+ [b"unmatched-dquotes2\""],
None::);
crate::http::header::common_header_test!(test14,
- vec![b"matched-\"dquotes\""],
+ [b"matched-\"dquotes\""],
None::);
crate::http::header::common_header_test!(test15,
- vec![b"\""],
+ [b"\""],
None::);
}
}
diff --git a/actix-web/src/http/header/expires.rs b/actix-web/src/http/header/expires.rs
index 55fe5acc..b677ab52 100644
--- a/actix-web/src/http/header/expires.rs
+++ b/actix-web/src/http/header/expires.rs
@@ -36,6 +36,6 @@ crate::http::header::common_header! {
test_parse_and_format {
// Test case from RFC
- crate::http::header::common_header_test!(test1, vec![b"Thu, 01 Dec 1994 16:00:00 GMT"]);
+ crate::http::header::common_header_test!(test1, [b"Thu, 01 Dec 1994 16:00:00 GMT"]);
}
}
diff --git a/actix-web/src/http/header/if_match.rs b/actix-web/src/http/header/if_match.rs
index e299d30f..e0b46a6c 100644
--- a/actix-web/src/http/header/if_match.rs
+++ b/actix-web/src/http/header/if_match.rs
@@ -52,17 +52,17 @@ common_header! {
test_parse_and_format {
crate::http::header::common_header_test!(
test1,
- vec![b"\"xyzzy\""],
+ [b"\"xyzzy\""],
Some(HeaderField::Items(
vec![EntityTag::new_strong("xyzzy".to_owned())])));
crate::http::header::common_header_test!(
test2,
- vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""],
+ [b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""],
Some(HeaderField::Items(
vec![EntityTag::new_strong("xyzzy".to_owned()),
EntityTag::new_strong("r2d2xxxx".to_owned()),
EntityTag::new_strong("c3piozzzz".to_owned())])));
- crate::http::header::common_header_test!(test3, vec![b"*"], Some(IfMatch::Any));
+ crate::http::header::common_header_test!(test3, [b"*"], Some(IfMatch::Any));
}
}
diff --git a/actix-web/src/http/header/if_modified_since.rs b/actix-web/src/http/header/if_modified_since.rs
index 89721094..8547ff49 100644
--- a/actix-web/src/http/header/if_modified_since.rs
+++ b/actix-web/src/http/header/if_modified_since.rs
@@ -35,6 +35,6 @@ crate::http::header::common_header! {
test_parse_and_format {
// Test case from RFC
- crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
+ crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]);
}
}
diff --git a/actix-web/src/http/header/if_none_match.rs b/actix-web/src/http/header/if_none_match.rs
index 86d7da9b..1a424df9 100644
--- a/actix-web/src/http/header/if_none_match.rs
+++ b/actix-web/src/http/header/if_none_match.rs
@@ -52,11 +52,11 @@ crate::http::header::common_header! {
(IfNoneMatch, IF_NONE_MATCH) => {Any / (EntityTag)+}
test_parse_and_format {
- crate::http::header::common_header_test!(test1, vec![b"\"xyzzy\""]);
- crate::http::header::common_header_test!(test2, vec![b"W/\"xyzzy\""]);
- crate::http::header::common_header_test!(test3, vec![b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]);
- crate::http::header::common_header_test!(test4, vec![b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]);
- crate::http::header::common_header_test!(test5, vec![b"*"]);
+ crate::http::header::common_header_test!(test1, [b"\"xyzzy\""]);
+ crate::http::header::common_header_test!(test2, [b"W/\"xyzzy\""]);
+ crate::http::header::common_header_test!(test3, [b"\"xyzzy\", \"r2d2xxxx\", \"c3piozzzz\""]);
+ crate::http::header::common_header_test!(test4, [b"W/\"xyzzy\", W/\"r2d2xxxx\", W/\"c3piozzzz\""]);
+ crate::http::header::common_header_test!(test5, [b"*"]);
}
}
diff --git a/actix-web/src/http/header/if_range.rs b/actix-web/src/http/header/if_range.rs
index eb3632a4..3e8727ab 100644
--- a/actix-web/src/http/header/if_range.rs
+++ b/actix-web/src/http/header/if_range.rs
@@ -4,9 +4,7 @@ use super::{
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, InvalidHeaderValue,
TryIntoHeaderValue, Writer,
};
-use crate::error::ParseError;
-use crate::http::header;
-use crate::HttpMessage;
+use crate::{error::ParseError, http::header, HttpMessage};
/// `If-Range` header, defined
/// in [RFC 7233 §3.2](https://datatracker.ietf.org/doc/html/rfc7233#section-3.2)
@@ -113,7 +111,7 @@ mod test_parse_and_format {
use super::IfRange as HeaderField;
use crate::http::header::*;
- crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
- crate::http::header::common_header_test!(test2, vec![b"\"abc\""]);
- crate::http::header::common_header_test!(test3, vec![b"this-is-invalid"], None::);
+ crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]);
+ crate::http::header::common_header_test!(test2, [b"\"abc\""]);
+ crate::http::header::common_header_test!(test3, [b"this-is-invalid"], None::);
}
diff --git a/actix-web/src/http/header/if_unmodified_since.rs b/actix-web/src/http/header/if_unmodified_since.rs
index 2ee3160b..afa4eb8e 100644
--- a/actix-web/src/http/header/if_unmodified_since.rs
+++ b/actix-web/src/http/header/if_unmodified_since.rs
@@ -35,6 +35,6 @@ crate::http::header::common_header! {
test_parse_and_format {
// Test case from RFC
- crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
+ crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]);
}
}
diff --git a/actix-web/src/http/header/last_modified.rs b/actix-web/src/http/header/last_modified.rs
index 59e649be..724a38bb 100644
--- a/actix-web/src/http/header/last_modified.rs
+++ b/actix-web/src/http/header/last_modified.rs
@@ -34,6 +34,6 @@ crate::http::header::common_header! {
test_parse_and_format {
// Test case from RFC
- crate::http::header::common_header_test!(test1, vec![b"Sat, 29 Oct 1994 19:43:31 GMT"]);
+ crate::http::header::common_header_test!(test1, [b"Sat, 29 Oct 1994 19:43:31 GMT"]);
}
}
diff --git a/actix-web/src/http/header/macros.rs b/actix-web/src/http/header/macros.rs
index b40eca03..d9755e15 100644
--- a/actix-web/src/http/header/macros.rs
+++ b/actix-web/src/http/header/macros.rs
@@ -314,7 +314,7 @@ macro_rules! common_header {
};
}
-pub(crate) use {common_header, common_header_test_module};
-
+pub(crate) use common_header;
#[cfg(test)]
pub(crate) use common_header_test;
+pub(crate) use common_header_test_module;
diff --git a/actix-web/src/http/header/mod.rs b/actix-web/src/http/header/mod.rs
index 9807d5f5..51ac4fcf 100644
--- a/actix-web/src/http/header/mod.rs
+++ b/actix-web/src/http/header/mod.rs
@@ -6,8 +6,6 @@
use std::fmt;
-use bytes::{Bytes, BytesMut};
-
// re-export from actix-http
// - header name / value types
// - relevant traits for converting to header name / value
@@ -16,6 +14,7 @@ use bytes::{Bytes, BytesMut};
// - the few typed headers from actix-http
// - header parsing utils
pub use actix_http::header::*;
+use bytes::{Bytes, BytesMut};
mod accept;
mod accept_charset;
@@ -25,6 +24,7 @@ mod allow;
mod cache_control;
mod content_disposition;
mod content_language;
+mod content_length;
mod content_range;
mod content_type;
mod date;
@@ -43,32 +43,34 @@ mod preference;
mod range;
#[cfg(test)]
-pub(crate) use macros::common_header_test;
-pub(crate) use macros::{common_header, common_header_test_module};
-
-pub use self::accept::Accept;
-pub use self::accept_charset::AcceptCharset;
-pub use self::accept_encoding::AcceptEncoding;
-pub use self::accept_language::AcceptLanguage;
-pub use self::allow::Allow;
-pub use self::cache_control::{CacheControl, CacheDirective};
-pub use self::content_disposition::{ContentDisposition, DispositionParam, DispositionType};
-pub use self::content_language::ContentLanguage;
-pub use self::content_range::{ContentRange, ContentRangeSpec};
-pub use self::content_type::ContentType;
-pub use self::date::Date;
-pub use self::encoding::Encoding;
-pub use self::entity::EntityTag;
-pub use self::etag::ETag;
-pub use self::expires::Expires;
-pub use self::if_match::IfMatch;
-pub use self::if_modified_since::IfModifiedSince;
-pub use self::if_none_match::IfNoneMatch;
-pub use self::if_range::IfRange;
-pub use self::if_unmodified_since::IfUnmodifiedSince;
-pub use self::last_modified::LastModified;
-pub use self::preference::Preference;
-pub use self::range::{ByteRangeSpec, Range};
+pub(crate) use self::macros::common_header_test;
+pub(crate) use self::macros::{common_header, common_header_test_module};
+pub use self::{
+ accept::Accept,
+ accept_charset::AcceptCharset,
+ accept_encoding::AcceptEncoding,
+ accept_language::AcceptLanguage,
+ allow::Allow,
+ cache_control::{CacheControl, CacheDirective},
+ content_disposition::{ContentDisposition, DispositionParam, DispositionType},
+ content_language::ContentLanguage,
+ content_length::ContentLength,
+ content_range::{ContentRange, ContentRangeSpec},
+ content_type::ContentType,
+ date::Date,
+ encoding::Encoding,
+ entity::EntityTag,
+ etag::ETag,
+ expires::Expires,
+ if_match::IfMatch,
+ if_modified_since::IfModifiedSince,
+ if_none_match::IfNoneMatch,
+ if_range::IfRange,
+ if_unmodified_since::IfUnmodifiedSince,
+ last_modified::LastModified,
+ preference::Preference,
+ range::{ByteRangeSpec, Range},
+};
/// Format writer ([`fmt::Write`]) for a [`BytesMut`].
#[derive(Debug, Default)]
diff --git a/actix-web/src/lib.rs b/actix-web/src/lib.rs
index 57cdaea6..e982a43b 100644
--- a/actix-web/src/lib.rs
+++ b/actix-web/src/lib.rs
@@ -74,6 +74,11 @@
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
+pub use actix_http::{body, HttpMessage};
+#[cfg(feature = "cookies")]
+#[doc(inline)]
+pub use cookie;
+
mod app;
mod app_service;
mod config;
@@ -102,25 +107,21 @@ pub mod test;
pub(crate) mod types;
pub mod web;
-pub use crate::app::App;
#[doc(inline)]
pub use crate::error::Result;
-pub use crate::error::{Error, ResponseError};
-pub use crate::extract::FromRequest;
-pub use crate::handler::Handler;
-pub use crate::request::HttpRequest;
-pub use crate::resource::Resource;
-pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
-pub use crate::route::Route;
-pub use crate::scope::Scope;
-pub use crate::server::HttpServer;
-pub use crate::types::Either;
-
-pub use actix_http::{body, HttpMessage};
-
-#[cfg(feature = "cookies")]
-#[doc(inline)]
-pub use cookie;
+pub use crate::{
+ app::App,
+ error::{Error, ResponseError},
+ extract::FromRequest,
+ handler::Handler,
+ request::HttpRequest,
+ resource::Resource,
+ response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder},
+ route::Route,
+ scope::Scope,
+ server::HttpServer,
+ types::Either,
+};
macro_rules! codegen_reexport {
($name:ident) => {
diff --git a/actix-web/src/middleware/compat.rs b/actix-web/src/middleware/compat.rs
index ee8b8a49..7df510a5 100644
--- a/actix-web/src/middleware/compat.rs
+++ b/actix-web/src/middleware/compat.rs
@@ -146,10 +146,9 @@ mod tests {
// easier to code when cookies feature is disabled
#![allow(unused_imports)]
- use super::*;
-
use actix_service::IntoService;
+ use super::*;
use crate::{
dev::ServiceRequest,
http::StatusCode,
@@ -207,9 +206,9 @@ mod tests {
#[actix_rt::test]
async fn test_condition_scope_middleware() {
let srv = |req: ServiceRequest| {
- Box::pin(async move {
- Ok(req.into_response(HttpResponse::InternalServerError().finish()))
- })
+ Box::pin(
+ async move { Ok(req.into_response(HttpResponse::InternalServerError().finish())) },
+ )
};
let logger = Logger::default();
diff --git a/actix-web/src/middleware/compress.rs b/actix-web/src/middleware/compress.rs
index 51b44c6e..a55b4626 100644
--- a/actix-web/src/middleware/compress.rs
+++ b/actix-web/src/middleware/compress.rs
@@ -11,13 +11,14 @@ use actix_http::encoding::Encoder;
use actix_service::{Service, Transform};
use actix_utils::future::{ok, Either, Ready};
use futures_core::ready;
+use mime::Mime;
use once_cell::sync::Lazy;
use pin_project_lite::pin_project;
use crate::{
body::{EitherBody, MessageBody},
http::{
- header::{self, AcceptEncoding, Encoding, HeaderValue},
+ header::{self, AcceptEncoding, ContentEncoding, Encoding, HeaderValue},
StatusCode,
},
service::{ServiceRequest, ServiceResponse},
@@ -170,19 +171,40 @@ where
{
type Output = Result>>, Error>;
- fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
- let this = self.project();
+ fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll {
+ let this = self.as_mut().project();
match ready!(this.fut.poll(cx)) {
Ok(resp) => {
let enc = match this.encoding {
Encoding::Known(enc) => *enc,
Encoding::Unknown(enc) => {
- unimplemented!("encoding {} should not be here", enc);
+ unimplemented!("encoding '{enc}' should not be here");
}
};
Poll::Ready(Ok(resp.map_body(move |head, body| {
+ let content_type = head.headers.get(header::CONTENT_TYPE);
+
+ fn default_compress_predicate(content_type: Option<&HeaderValue>) -> bool {
+ match content_type {
+ None => true,
+ Some(hdr) => {
+ match hdr.to_str().ok().and_then(|hdr| hdr.parse::().ok()) {
+ Some(mime) if mime.type_().as_str() == "image" => false,
+ Some(mime) if mime.type_().as_str() == "video" => false,
+ _ => true,
+ }
+ }
+ }
+ }
+
+ let enc = if default_compress_predicate(content_type) {
+ enc
+ } else {
+ ContentEncoding::Identity
+ };
+
EitherBody::left(Encoder::response(enc, head, body))
})))
}
@@ -246,8 +268,18 @@ static SUPPORTED_ENCODINGS: &[Encoding] = &[
mod tests {
use std::collections::HashSet;
+ use static_assertions::assert_impl_all;
+
use super::*;
- use crate::{middleware::DefaultHeaders, test, web, App};
+ use crate::{http::header::ContentType, middleware::DefaultHeaders, test, web, App};
+
+ const HTML_DATA_PART: &str = "
hello world
) -> Vec {
use std::io::Read as _;
@@ -257,23 +289,55 @@ mod tests {
buf
}
+ #[track_caller]
+ fn assert_successful_res_with_content_type(res: &ServiceResponse, ct: &str) {
+ assert!(res.status().is_success());
+ assert!(
+ res.headers()
+ .get(header::CONTENT_TYPE)
+ .expect("content-type header should be present")
+ .to_str()
+ .expect("content-type header should be utf-8")
+ .contains(ct),
+ "response's content-type did not match {}",
+ ct
+ );
+ }
+
+ #[track_caller]
+ fn assert_successful_gzip_res_with_content_type(res: &ServiceResponse, ct: &str) {
+ assert_successful_res_with_content_type(res, ct);
+ assert_eq!(
+ res.headers()
+ .get(header::CONTENT_ENCODING)
+ .expect("response should be gzip compressed"),
+ "gzip",
+ );
+ }
+
+ #[track_caller]
+ fn assert_successful_identity_res_with_content_type(res: &ServiceResponse, ct: &str) {
+ assert_successful_res_with_content_type(res, ct);
+ assert!(
+ res.headers().get(header::CONTENT_ENCODING).is_none(),
+ "response should not be compressed",
+ );
+ }
+
#[actix_rt::test]
async fn prevents_double_compressing() {
- const D: &str = "hello world ";
- const DATA: &str = const_str::repeat!(D, 100);
-
let app = test::init_service({
App::new()
.wrap(Compress::default())
.route(
"/single",
- web::get().to(move || HttpResponse::Ok().body(DATA)),
+ web::get().to(move || HttpResponse::Ok().body(TEXT_DATA)),
)
.service(
web::resource("/double")
.wrap(Compress::default())
.wrap(DefaultHeaders::new().add(("x-double", "true")))
- .route(web::get().to(move || HttpResponse::Ok().body(DATA))),
+ .route(web::get().to(move || HttpResponse::Ok().body(TEXT_DATA))),
)
})
.await;
@@ -287,7 +351,7 @@ mod tests {
assert_eq!(res.headers().get("x-double"), None);
assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip");
let bytes = test::read_body(res).await;
- assert_eq!(gzip_decode(bytes), DATA.as_bytes());
+ assert_eq!(gzip_decode(bytes), TEXT_DATA.as_bytes());
let req = test::TestRequest::default()
.uri("/double")
@@ -298,7 +362,7 @@ mod tests {
assert_eq!(res.headers().get("x-double").unwrap(), "true");
assert_eq!(res.headers().get(header::CONTENT_ENCODING).unwrap(), "gzip");
let bytes = test::read_body(res).await;
- assert_eq!(gzip_decode(bytes), DATA.as_bytes());
+ assert_eq!(gzip_decode(bytes), TEXT_DATA.as_bytes());
}
#[actix_rt::test]
@@ -324,4 +388,45 @@ mod tests {
assert!(vary_headers.contains(&HeaderValue::from_static("x-test")));
assert!(vary_headers.contains(&HeaderValue::from_static("accept-encoding")));
}
+
+ fn configure_predicate_test(cfg: &mut web::ServiceConfig) {
+ cfg.route(
+ "/html",
+ web::get().to(|| {
+ HttpResponse::Ok()
+ .content_type(ContentType::html())
+ .body(HTML_DATA)
+ }),
+ )
+ .route(
+ "/image",
+ web::get().to(|| {
+ HttpResponse::Ok()
+ .content_type(ContentType::jpeg())
+ .body(TEXT_DATA)
+ }),
+ );
+ }
+
+ #[actix_rt::test]
+ async fn prevents_compression_jpeg() {
+ let app = test::init_service(
+ App::new()
+ .wrap(Compress::default())
+ .configure(configure_predicate_test),
+ )
+ .await;
+
+ let req =
+ test::TestRequest::with_uri("/html").insert_header((header::ACCEPT_ENCODING, "gzip"));
+ let res = test::call_service(&app, req.to_request()).await;
+ assert_successful_gzip_res_with_content_type(&res, "text/html");
+ assert_ne!(test::read_body(res).await, HTML_DATA.as_bytes());
+
+ let req =
+ test::TestRequest::with_uri("/image").insert_header((header::ACCEPT_ENCODING, "gzip"));
+ let res = test::call_service(&app, req.to_request()).await;
+ assert_successful_identity_res_with_content_type(&res, "image/jpeg");
+ assert_eq!(test::read_body(res).await, TEXT_DATA.as_bytes());
+ }
}
diff --git a/actix-web/src/middleware/default_headers.rs b/actix-web/src/middleware/default_headers.rs
index 003abd40..b5a5a699 100644
--- a/actix-web/src/middleware/default_headers.rs
+++ b/actix-web/src/middleware/default_headers.rs
@@ -1,7 +1,6 @@
//! For middleware documentation, see [`DefaultHeaders`].
use std::{
- convert::TryFrom,
future::Future,
marker::PhantomData,
pin::Pin,
diff --git a/actix-web/src/middleware/err_handlers.rs b/actix-web/src/middleware/err_handlers.rs
index 5522cc02..e640bba0 100644
--- a/actix-web/src/middleware/err_handlers.rs
+++ b/actix-web/src/middleware/err_handlers.rs
@@ -270,8 +270,8 @@ impl ErrorHandlers {
handlers
.get(status)
.map(|h| h.as_ref())
- .or_else(|| status.is_client_error().then(|| default_client).flatten())
- .or_else(|| status.is_server_error().then(|| default_server).flatten())
+ .or_else(|| status.is_client_error().then_some(default_client).flatten())
+ .or_else(|| status.is_server_error().then_some(default_server).flatten())
}
}
@@ -540,21 +540,17 @@ mod tests {
let mw_server = make_mw(StatusCode::INTERNAL_SERVER_ERROR).await;
let mw_client = make_mw(StatusCode::BAD_REQUEST).await;
- let resp =
- test::call_service(&mw_client, TestRequest::default().to_srv_request()).await;
+ let resp = test::call_service(&mw_client, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
- let resp =
- test::call_service(&mw_server, TestRequest::default().to_srv_request()).await;
+ let resp = test::call_service(&mw_server, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
#[actix_rt::test]
async fn default_handlers_separate_client_server() {
#[allow(clippy::unnecessary_wraps)]
- fn error_handler_client(
- mut res: ServiceResponse,
- ) -> Result> {
+ fn error_handler_client(mut res: ServiceResponse) -> Result> {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
@@ -562,9 +558,7 @@ mod tests {
}
#[allow(clippy::unnecessary_wraps)]
- fn error_handler_server(
- mut res: ServiceResponse,
- ) -> Result> {
+ fn error_handler_server(mut res: ServiceResponse) -> Result> {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0002"));
@@ -582,21 +576,17 @@ mod tests {
let mw_server = make_mw(StatusCode::INTERNAL_SERVER_ERROR).await;
let mw_client = make_mw(StatusCode::BAD_REQUEST).await;
- let resp =
- test::call_service(&mw_client, TestRequest::default().to_srv_request()).await;
+ let resp = test::call_service(&mw_client, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
- let resp =
- test::call_service(&mw_server, TestRequest::default().to_srv_request()).await;
+ let resp = test::call_service(&mw_server, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0002");
}
#[actix_rt::test]
async fn default_handlers_specialization() {
#[allow(clippy::unnecessary_wraps)]
- fn error_handler_client(
- mut res: ServiceResponse,
- ) -> Result> {
+ fn error_handler_client(mut res: ServiceResponse) -> Result> {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
@@ -624,12 +614,10 @@ mod tests {
let mw_client = make_mw(StatusCode::BAD_REQUEST).await;
let mw_specific = make_mw(StatusCode::UNPROCESSABLE_ENTITY).await;
- let resp =
- test::call_service(&mw_client, TestRequest::default().to_srv_request()).await;
+ let resp = test::call_service(&mw_client, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
- let resp =
- test::call_service(&mw_specific, TestRequest::default().to_srv_request()).await;
+ let resp = test::call_service(&mw_specific, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0003");
}
}
diff --git a/actix-web/src/middleware/logger.rs b/actix-web/src/middleware/logger.rs
index 5fec5a01..ce2caacd 100644
--- a/actix-web/src/middleware/logger.rs
+++ b/actix-web/src/middleware/logger.rs
@@ -3,7 +3,6 @@
use std::{
borrow::Cow,
collections::HashSet,
- convert::TryFrom,
env,
fmt::{self, Display as _},
future::Future,
@@ -357,7 +356,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() {
@@ -490,12 +489,8 @@ impl Format {
unreachable!("regex and code mismatch")
}
}
- "i" => {
- FormatText::RequestHeader(HeaderName::try_from(key.as_str()).unwrap())
- }
- "o" => {
- FormatText::ResponseHeader(HeaderName::try_from(key.as_str()).unwrap())
- }
+ "i" => FormatText::RequestHeader(HeaderName::try_from(key.as_str()).unwrap()),
+ "o" => FormatText::ResponseHeader(HeaderName::try_from(key.as_str()).unwrap()),
"e" => FormatText::EnvironHeader(key.as_str().to_owned()),
"xi" => FormatText::CustomRequest(key.as_str().to_owned(), None),
"xo" => FormatText::CustomResponse(key.as_str().to_owned(), None),
@@ -711,9 +706,7 @@ impl FormatText {
}
/// Converter to get a String from something that writes to a Formatter.
-pub(crate) struct FormatDisplay<'a>(
- &'a dyn Fn(&mut fmt::Formatter<'_>) -> Result<(), fmt::Error>,
-);
+pub(crate) struct FormatDisplay<'a>(&'a dyn Fn(&mut fmt::Formatter<'_>) -> Result<(), fmt::Error>);
impl<'a> fmt::Display for FormatDisplay<'a> {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
diff --git a/actix-web/src/middleware/mod.rs b/actix-web/src/middleware/mod.rs
index 0a61ad6c..8dbd1ff2 100644
--- a/actix-web/src/middleware/mod.rs
+++ b/actix-web/src/middleware/mod.rs
@@ -1,4 +1,221 @@
//! A collection of common middleware.
+//!
+//! # What Is Middleware?
+//!
+//! Actix Web's middleware system allows us to add additional behavior to request/response
+//! processing. Middleware can hook into incoming request and outgoing response processes, enabling
+//! us to modify requests and responses as well as halt request processing to return a response
+//! early.
+//!
+//! Typically, middleware is involved in the following actions:
+//!
+//! - Pre-process the request (e.g., [normalizing paths](NormalizePath))
+//! - Post-process a response (e.g., [logging][Logger])
+//! - Modify application state (through [`ServiceRequest`][crate::dev::ServiceRequest])
+//! - Access external services (e.g., [sessions](https://docs.rs/actix-session), etc.)
+//!
+//! Middleware is registered for each [`App`], [`Scope`](crate::Scope), or
+//! [`Resource`](crate::Resource) and executed in opposite order as registration. In general, a
+//! middleware is a pair of types that implements the [`Service`] trait and [`Transform`] trait,
+//! respectively. The [`new_transform`] and [`call`] methods must return a [`Future`], though it
+//! can often be [an immediately-ready one](actix_utils::future::Ready).
+//!
+//! # Ordering
+//!
+//! ```
+//! # use actix_web::{web, middleware, get, App, Responder};
+//! #
+//! # // some basic types to make sure this compiles
+//! # type ExtractorA = web::Json;
+//! # type ExtractorB = ExtractorA;
+//! #[get("/")]
+//! async fn service(a: ExtractorA, b: ExtractorB) -> impl Responder { "Hello, World!" }
+//!
+//! # 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();
+//! let app = App::new()
+//! .wrap(MiddlewareA)
+//! .wrap(MiddlewareB)
+//! .wrap(MiddlewareC)
+//! .service(service);
+//! # }
+//! ```
+//!
+//! ```plain
+//! Request
+//! ⭣
+//! ╭────────────────────┼────╮
+//! │ MiddlewareC │ │
+//! │ ╭──────────────────┼───╮│
+//! │ │ MiddlewareB │ ││
+//! │ │ ╭────────────────┼──╮││
+//! │ │ │ MiddlewareA │ │││
+//! │ │ │ ╭──────────────┼─╮│││
+//! │ │ │ │ ExtractorA │ ││││
+//! │ │ │ ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┤│││
+//! │ │ │ │ ExtractorB │ ││││
+//! │ │ │ ├┈┈┈┈┈┈┈┈┈┈┈┈┈┈┼┈┤│││
+//! │ │ │ │ service │ ││││
+//! │ │ │ ╰──────────────┼─╯│││
+//! │ │ ╰────────────────┼──╯││
+//! │ ╰──────────────────┼───╯│
+//! ╰────────────────────┼────╯
+//! ⭣
+//! Response
+//! ```
+//! The request _first_ gets processed by the middleware specified _last_ - `MiddlewareC`. It passes
+//! the request (modified a modified one) to the next middleware - `MiddlewareB` - _or_ directly
+//! responds to the request (e.g. when the request was invalid or an error occurred). `MiddlewareB`
+//! 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`.
+//!
+//! 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
+//! layer exposed to a Request is the outermost layer (i.e., the _last_ registered in the builder
+//! chain, in the example above: `MiddlewareC`). Consequently, the _first_ middleware registered in
+//! the builder chain is the _last_ to start executing during request processing (`MiddlewareA`).
+//! Ordering is less obvious when wrapped services also have middleware applied. In this case,
+//! middleware are run in reverse order for [`App`] _and then_ in reverse order for the wrapped
+//! service.
+//!
+//! # Middleware Traits
+//!
+//! ## `Transform`
+//!
+//! The [`Transform`] trait is the builder for the actual [`Service`]s that handle the requests. All
+//! the middleware you pass to the `wrap` methods implement this trait. During construction, each
+//! thread assembles a chain of [`Service`]s by calling [`new_transform`] and passing the next
+//! [`Service`] (`S`) in the chain. The created [`Service`] handles requests of type `Req`.
+//!
+//! In the example from the [ordering](#ordering) section, the chain would be:
+//!
+//! ```plain
+//! MiddlewareCService {
+//! next: MiddlewareBService {
+//! next: MiddlewareAService { ... }
+//! }
+//! }
+//! ```
+//!
+//! ## `Service`
+//!
+//! A [`Service`] `S` represents an asynchronous operation that turns a request of type `Req` into a
+//! response of type [`S::Response`](crate::dev::Service::Response) or an error of type
+//! [`S::Error`](crate::dev::Service::Error). You can think of the service of being roughly:
+//!
+//! ```ignore
+//! async fn(&self, req: Req) -> Result
+//! ```
+//!
+//! In most cases the [`Service`] implementation will, at some point, call the wrapped [`Service`]
+//! in its [`call`] implementation.
+//!
+//! Note that the [`Service`]s created by [`new_transform`] don't need to be [`Send`] or [`Sync`].
+//!
+//! # Example
+//!
+//! ```
+//! use std::{future::{ready, Ready, Future}, pin::Pin};
+//!
+//! use actix_web::{
+//! dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
+//! web, Error,
+//! # App
+//! };
+//!
+//! pub struct SayHi;
+//!
+//! // `S` - type of the next service
+//! // `B` - type of response's body
+//! impl Transform for SayHi
+//! where
+//! S: Service, Error = Error>,
+//! S::Future: 'static,
+//! B: 'static,
+//! {
+//! type Response = ServiceResponse;
+//! type Error = Error;
+//! type InitError = ();
+//! type Transform = SayHiMiddleware;
+//! type Future = Ready>;
+//!
+//! fn new_transform(&self, service: S) -> Self::Future {
+//! ready(Ok(SayHiMiddleware { service }))
+//! }
+//! }
+//!
+//! pub struct SayHiMiddleware {
+//! /// The next service to call
+//! service: S,
+//! }
+//!
+//! // This future doesn't have the requirement of being `Send`.
+//! // See: futures_util::future::LocalBoxFuture
+//! type LocalBoxFuture = Pin + 'static>>;
+//!
+//! // `S`: type of the wrapped service
+//! // `B`: type of the body - try to be generic over the body where possible
+//! impl Service for SayHiMiddleware
+//! where
+//! S: Service, Error = Error>,
+//! S::Future: 'static,
+//! B: 'static,
+//! {
+//! type Response = ServiceResponse;
+//! type Error = Error;
+//! type Future = LocalBoxFuture>;
+//!
+//! // This service is ready when its next service is ready
+//! forward_ready!(service);
+//!
+//! fn call(&self, req: ServiceRequest) -> Self::Future {
+//! println!("Hi from start. You requested: {}", req.path());
+//!
+//! // A more complex middleware, could return an error or an early response here.
+//!
+//! let fut = self.service.call(req);
+//!
+//! Box::pin(async move {
+//! let res = fut.await?;
+//!
+//! println!("Hi from response");
+//! Ok(res)
+//! })
+//! }
+//! }
+//!
+//! # fn main() {
+//! let app = App::new()
+//! .wrap(SayHi)
+//! .route("/", web::get().to(|| async { "Hello, middleware!" }));
+//! # }
+//! ```
+//!
+//! # Simpler Middleware
+//!
+//! In many cases, you _can_ actually use an async function via a helper that will provide a more
+//! natural flow for your behavior.
+//!
+//! The experimental `actix_web_lab` crate provides a [`from_fn`][lab_from_fn] utility which allows
+//! an async fn to be wrapped and used in the same way as other middleware. See the
+//! [`from_fn`][lab_from_fn] docs for more info and examples of it's use.
+//!
+//! While [`from_fn`][lab_from_fn] is experimental currently, it's likely this helper will graduate
+//! to Actix Web in some form, so feedback is appreciated.
+//!
+//! [`Future`]: std::future::Future
+//! [`App`]: crate::App
+//! [`FromRequest`]: crate::FromRequest
+//! [`Service`]: crate::dev::Service
+//! [`Transform`]: crate::dev::Transform
+//! [`call`]: crate::dev::Service::call()
+//! [`new_transform`]: crate::dev::Transform::new_transform()
+//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
mod compat;
mod condition;
@@ -9,14 +226,16 @@ mod logger;
mod noop;
mod normalize;
-pub use self::compat::Compat;
-pub use self::condition::Condition;
-pub use self::default_headers::DefaultHeaders;
-pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers};
-pub use self::logger::Logger;
#[cfg(test)]
pub(crate) use self::noop::Noop;
-pub use self::normalize::{NormalizePath, TrailingSlash};
+pub use self::{
+ compat::Compat,
+ condition::Condition,
+ default_headers::DefaultHeaders,
+ err_handlers::{ErrorHandlerResponse, ErrorHandlers},
+ logger::Logger,
+ normalize::{NormalizePath, TrailingSlash},
+};
#[cfg(feature = "__compress")]
mod compress;
@@ -26,9 +245,8 @@ pub use self::compress::Compress;
#[cfg(test)]
mod tests {
- use crate::{http::StatusCode, App};
-
use super::*;
+ use crate::{http::StatusCode, App};
#[test]
fn common_combinations() {
diff --git a/actix-web/src/middleware/normalize.rs b/actix-web/src/middleware/normalize.rs
index 3ab90848..afcc0faa 100644
--- a/actix-web/src/middleware/normalize.rs
+++ b/actix-web/src/middleware/normalize.rs
@@ -15,11 +15,12 @@ use crate::{
///
/// The default is `TrailingSlash::Trim`.
#[non_exhaustive]
-#[derive(Debug, Clone, Copy)]
+#[derive(Debug, Clone, Copy, Default)]
pub enum TrailingSlash {
/// Trim trailing slashes from the end of the path.
///
/// Using this will require all routes to omit trailing slashes for them to be accessible.
+ #[default]
Trim,
/// Only merge any present multiple trailing slashes.
@@ -33,12 +34,6 @@ pub enum TrailingSlash {
Always,
}
-impl Default for TrailingSlash {
- fn default() -> Self {
- TrailingSlash::Trim
- }
-}
-
/// Middleware for normalizing a request's path so that routes can be matched more flexibly.
///
/// # Normalization Steps
diff --git a/actix-web/src/redirect.rs b/actix-web/src/redirect.rs
index 5611cc36..f9e9f2d7 100644
--- a/actix-web/src/redirect.rs
+++ b/actix-web/src/redirect.rs
@@ -181,9 +181,8 @@ impl Responder for Redirect {
#[cfg(test)]
mod tests {
- use crate::{dev::Service, http::StatusCode, test, App};
-
use super::*;
+ use crate::{dev::Service, http::StatusCode, test, App};
#[actix_rt::test]
async fn absolute_redirects() {
diff --git a/actix-web/src/request.rs b/actix-web/src/request.rs
index 16a947b6..ece36a38 100644
--- a/actix-web/src/request.rs
+++ b/actix-web/src/request.rs
@@ -435,16 +435,28 @@ impl fmt::Debug for HttpRequest {
self.inner.head.method,
self.path()
)?;
+
if !self.query_string().is_empty() {
writeln!(f, " query: ?{:?}", self.query_string())?;
}
+
if !self.match_info().is_empty() {
writeln!(f, " params: {:?}", self.match_info())?;
}
+
writeln!(f, " headers:")?;
+
for (key, val) in self.headers().iter() {
- writeln!(f, " {:?}: {:?}", key, val)?;
+ match key {
+ // redact sensitive header values from debug output
+ &crate::http::header::AUTHORIZATION
+ | &crate::http::header::PROXY_AUTHORIZATION
+ | &crate::http::header::COOKIE => writeln!(f, " {:?}: {:?}", key, "*redacted*")?,
+
+ _ => writeln!(f, " {:?}: {:?}", key, val)?,
+ }
}
+
Ok(())
}
}
@@ -653,13 +665,13 @@ mod tests {
#[actix_rt::test]
async fn test_drop_http_request_pool() {
- let srv = init_service(App::new().service(web::resource("/").to(
- |req: HttpRequest| {
+ let srv = init_service(
+ App::new().service(web::resource("/").to(|req: HttpRequest| {
HttpResponse::Ok()
.insert_header(("pool_cap", req.app_state().pool().cap))
.finish()
- },
- )))
+ })),
+ )
.await;
let req = TestRequest::default().to_request();
@@ -807,10 +819,7 @@ mod tests {
web::scope("/user/{id}")
.service(web::resource("/profile").route(web::get().to(
move |req: HttpRequest| {
- assert_eq!(
- req.match_pattern(),
- Some("/user/{id}/profile".to_owned())
- );
+ assert_eq!(req.match_pattern(), Some("/user/{id}/profile".to_owned()));
HttpResponse::Ok().finish()
},
@@ -911,4 +920,47 @@ mod tests {
let body = read_body(bar_resp).await;
assert_eq!(body, "http://localhost:8080/bar/nested");
}
+
+ #[test]
+ fn authorization_header_hidden_in_debug() {
+ let authorization_header = "Basic bXkgdXNlcm5hbWU6bXkgcGFzc3dvcmQK";
+ let req = TestRequest::get()
+ .insert_header((crate::http::header::AUTHORIZATION, authorization_header))
+ .to_http_request();
+
+ assert!(!format!("{:?}", req).contains(authorization_header));
+ }
+
+ #[test]
+ fn proxy_authorization_header_hidden_in_debug() {
+ let proxy_authorization_header = "secret value";
+ let req = TestRequest::get()
+ .insert_header((
+ crate::http::header::PROXY_AUTHORIZATION,
+ proxy_authorization_header,
+ ))
+ .to_http_request();
+
+ assert!(!format!("{:?}", req).contains(proxy_authorization_header));
+ }
+
+ #[test]
+ fn cookie_header_hidden_in_debug() {
+ let cookie_header = "secret";
+ let req = TestRequest::get()
+ .insert_header((crate::http::header::COOKIE, cookie_header))
+ .to_http_request();
+
+ assert!(!format!("{:?}", req).contains(cookie_header));
+ }
+
+ #[test]
+ fn other_header_visible_in_debug() {
+ let location_header = "192.0.0.1";
+ let req = TestRequest::get()
+ .insert_header((crate::http::header::LOCATION, location_header))
+ .to_http_request();
+
+ assert!(format!("{:?}", req).contains(location_header));
+ }
}
diff --git a/actix-web/src/request_data.rs b/actix-web/src/request_data.rs
index 719e6551..bffbf74d 100644
--- a/actix-web/src/request_data.rs
+++ b/actix-web/src/request_data.rs
@@ -27,7 +27,6 @@ use crate::{
/// # Examples
/// ```no_run
/// # use actix_web::{web, HttpResponse, HttpRequest, Responder, HttpMessage as _};
-///
/// #[derive(Debug, Clone, PartialEq)]
/// struct FlagFromMiddleware(String);
///
diff --git a/actix-web/src/resource.rs b/actix-web/src/resource.rs
index 5d2c9706..95185b80 100644
--- a/actix-web/src/resource.rs
+++ b/actix-web/src/resource.rs
@@ -353,12 +353,8 @@ where
pub fn default_service(mut self, f: F) -> Self
where
F: IntoServiceFactory,
- U: ServiceFactory<
- ServiceRequest,
- Config = (),
- Response = ServiceResponse,
- Error = Error,
- > + 'static,
+ U: ServiceFactory
+ + 'static,
U::InitError: fmt::Debug,
{
// create and configure default resource
@@ -625,10 +621,8 @@ mod tests {
let fut = srv.call(req);
async {
fut.await.map(|mut res| {
- res.headers_mut().insert(
- header::CONTENT_TYPE,
- HeaderValue::from_static("0001"),
- );
+ res.headers_mut()
+ .insert(header::CONTENT_TYPE, HeaderValue::from_static("0001"));
res
})
}
@@ -660,12 +654,9 @@ mod tests {
#[actix_rt::test]
async fn test_pattern() {
- let srv = init_service(
- App::new().service(
- web::resource(["/test", "/test2"])
- .to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }),
- ),
- )
+ let srv = init_service(App::new().service(
+ web::resource(["/test", "/test2"]).to(|| async { Ok::<_, Error>(HttpResponse::Ok()) }),
+ ))
.await;
let req = TestRequest::with_uri("/test").to_request();
let resp = call_service(&srv, req).await;
@@ -804,17 +795,18 @@ mod tests {
#[allow(deprecated)]
#[actix_rt::test]
async fn test_data_default_service() {
- let srv = init_service(
- App::new().data(1usize).service(
- web::resource("/test")
- .data(10usize)
- .default_service(web::to(|data: web::Data| {
- assert_eq!(**data, 10);
- HttpResponse::Ok()
- })),
- ),
- )
- .await;
+ let srv =
+ init_service(
+ App::new().data(1usize).service(
+ web::resource("/test")
+ .data(10usize)
+ .default_service(web::to(|data: web::Data| {
+ assert_eq!(**data, 10);
+ HttpResponse::Ok()
+ })),
+ ),
+ )
+ .await;
let req = TestRequest::get().uri("/test").to_request();
let resp = call_service(&srv, req).await;
diff --git a/actix-web/src/response/builder.rs b/actix-web/src/response/builder.rs
index 120d4c35..28a0adff 100644
--- a/actix-web/src/response/builder.rs
+++ b/actix-web/src/response/builder.rs
@@ -1,6 +1,5 @@
use std::{
cell::{Ref, RefMut},
- convert::TryInto,
future::Future,
pin::Pin,
task::{Context, Poll},
@@ -15,8 +14,10 @@ use crate::{
body::{BodyStream, BoxBody, MessageBody},
dev::Extensions,
error::{Error, JsonPayloadError},
- http::header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
- http::{ConnectionType, StatusCode},
+ http::{
+ header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
+ ConnectionType, StatusCode,
+ },
BoxError, HttpRequest, HttpResponse, Responder,
};
@@ -63,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()),
};
}
@@ -85,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()),
};
}
@@ -209,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
@@ -473,9 +474,8 @@ mod tests {
#[actix_rt::test]
async fn test_serde_json_in_body() {
- let resp = HttpResponse::Ok().body(
- serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap(),
- );
+ let resp = HttpResponse::Ok()
+ .body(serde_json::to_vec(&serde_json::json!({ "test-key": "test-value" })).unwrap());
assert_eq!(
body::to_bytes(resp.into_body()).await.unwrap().as_ref(),
diff --git a/actix-web/src/response/customize_responder.rs b/actix-web/src/response/customize_responder.rs
index f6f4b923..aad0039e 100644
--- a/actix-web/src/response/customize_responder.rs
+++ b/actix-web/src/response/customize_responder.rs
@@ -1,5 +1,7 @@
use actix_http::{
- body::EitherBody, error::HttpError, header::HeaderMap, header::TryIntoHeaderPair,
+ body::EitherBody,
+ error::HttpError,
+ header::{HeaderMap, TryIntoHeaderPair},
StatusCode,
};
@@ -168,9 +170,8 @@ where
#[cfg(test)]
mod tests {
- use bytes::Bytes;
-
use actix_http::body::to_bytes;
+ use bytes::Bytes;
use super::*;
use crate::{
diff --git a/actix-web/src/response/http_codes.rs b/actix-web/src/response/http_codes.rs
index 98673534..db5f392c 100644
--- a/actix-web/src/response/http_codes.rs
+++ b/actix-web/src/response/http_codes.rs
@@ -25,12 +25,12 @@ impl HttpResponse {
NonAuthoritativeInformation,
StatusCode::NON_AUTHORITATIVE_INFORMATION
);
-
static_resp!(NoContent, StatusCode::NO_CONTENT);
static_resp!(ResetContent, StatusCode::RESET_CONTENT);
static_resp!(PartialContent, StatusCode::PARTIAL_CONTENT);
static_resp!(MultiStatus, StatusCode::MULTI_STATUS);
static_resp!(AlreadyReported, StatusCode::ALREADY_REPORTED);
+ static_resp!(ImUsed, StatusCode::IM_USED);
static_resp!(MultipleChoices, StatusCode::MULTIPLE_CHOICES);
static_resp!(MovedPermanently, StatusCode::MOVED_PERMANENTLY);
@@ -42,10 +42,10 @@ impl HttpResponse {
static_resp!(PermanentRedirect, StatusCode::PERMANENT_REDIRECT);
static_resp!(BadRequest, StatusCode::BAD_REQUEST);
- static_resp!(NotFound, StatusCode::NOT_FOUND);
static_resp!(Unauthorized, StatusCode::UNAUTHORIZED);
static_resp!(PaymentRequired, StatusCode::PAYMENT_REQUIRED);
static_resp!(Forbidden, StatusCode::FORBIDDEN);
+ static_resp!(NotFound, StatusCode::NOT_FOUND);
static_resp!(MethodNotAllowed, StatusCode::METHOD_NOT_ALLOWED);
static_resp!(NotAcceptable, StatusCode::NOT_ACCEPTABLE);
static_resp!(
@@ -57,13 +57,18 @@ impl HttpResponse {
static_resp!(Gone, StatusCode::GONE);
static_resp!(LengthRequired, StatusCode::LENGTH_REQUIRED);
static_resp!(PreconditionFailed, StatusCode::PRECONDITION_FAILED);
- static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED);
static_resp!(PayloadTooLarge, StatusCode::PAYLOAD_TOO_LARGE);
static_resp!(UriTooLong, StatusCode::URI_TOO_LONG);
static_resp!(UnsupportedMediaType, StatusCode::UNSUPPORTED_MEDIA_TYPE);
static_resp!(RangeNotSatisfiable, StatusCode::RANGE_NOT_SATISFIABLE);
static_resp!(ExpectationFailed, StatusCode::EXPECTATION_FAILED);
+ static_resp!(ImATeapot, StatusCode::IM_A_TEAPOT);
+ static_resp!(MisdirectedRequest, StatusCode::MISDIRECTED_REQUEST);
static_resp!(UnprocessableEntity, StatusCode::UNPROCESSABLE_ENTITY);
+ static_resp!(Locked, StatusCode::LOCKED);
+ static_resp!(FailedDependency, StatusCode::FAILED_DEPENDENCY);
+ static_resp!(UpgradeRequired, StatusCode::UPGRADE_REQUIRED);
+ static_resp!(PreconditionRequired, StatusCode::PRECONDITION_REQUIRED);
static_resp!(TooManyRequests, StatusCode::TOO_MANY_REQUESTS);
static_resp!(
RequestHeaderFieldsTooLarge,
@@ -83,12 +88,16 @@ impl HttpResponse {
static_resp!(VariantAlsoNegotiates, StatusCode::VARIANT_ALSO_NEGOTIATES);
static_resp!(InsufficientStorage, StatusCode::INSUFFICIENT_STORAGE);
static_resp!(LoopDetected, StatusCode::LOOP_DETECTED);
+ static_resp!(NotExtended, StatusCode::NOT_EXTENDED);
+ static_resp!(
+ NetworkAuthenticationRequired,
+ StatusCode::NETWORK_AUTHENTICATION_REQUIRED
+ );
}
#[cfg(test)]
mod tests {
- use crate::http::StatusCode;
- use crate::HttpResponse;
+ use crate::{http::StatusCode, HttpResponse};
#[test]
fn test_build() {
diff --git a/actix-web/src/response/mod.rs b/actix-web/src/response/mod.rs
index 97714710..11fd2830 100644
--- a/actix-web/src/response/mod.rs
+++ b/actix-web/src/response/mod.rs
@@ -5,10 +5,9 @@ mod responder;
#[allow(clippy::module_inception)]
mod response;
-pub use self::builder::HttpResponseBuilder;
-pub use self::customize_responder::CustomizeResponder;
-pub use self::responder::Responder;
-pub use self::response::HttpResponse;
-
#[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 965163a1..7d0b0e58 100644
--- a/actix-web/src/response/responder.rs
+++ b/actix-web/src/response/responder.rs
@@ -186,11 +186,10 @@ impl_into_string_responder!(Cow<'_, str>);
#[cfg(test)]
pub(crate) mod tests {
+ use actix_http::body::to_bytes;
use actix_service::Service;
use bytes::{Bytes, BytesMut};
- use actix_http::body::to_bytes;
-
use super::*;
use crate::{
error,
diff --git a/actix-web/src/response/response.rs b/actix-web/src/response/response.rs
index ead8badb..fbd87e10 100644
--- a/actix-web/src/response/response.rs
+++ b/actix-web/src/response/response.rs
@@ -8,7 +8,6 @@ use actix_http::{
header::HeaderMap,
Extensions, Response, ResponseHead, StatusCode,
};
-
#[cfg(feature = "cookies")]
use {
actix_http::{
diff --git a/actix-web/src/rmap.rs b/actix-web/src/rmap.rs
index 6e10717c..462f3b31 100644
--- a/actix-web/src/rmap.rs
+++ b/actix-web/src/rmap.rs
@@ -81,7 +81,7 @@ impl ResourceMap {
"`pattern` and `nested` mismatch"
);
// parents absorb references to the named resources of children
- self.named.extend(new_node.named.clone().into_iter());
+ self.named.extend(new_node.named.clone());
self.nodes.as_mut().unwrap().push(new_node);
} else {
let new_node = Rc::new(ResourceMap {
@@ -136,7 +136,7 @@ impl ResourceMap {
.root_rmap_fn(String::with_capacity(AVG_PATH_LEN), |mut acc, node| {
node.pattern
.resource_path_from_iter(&mut acc, &mut elements)
- .then(|| acc)
+ .then_some(acc)
})
.ok_or(UrlGenerationError::NotEnoughElements)?;
@@ -149,7 +149,7 @@ impl ResourceMap {
// external resource; third slash would be the root slash in the path
let third_slash_index = path
.char_indices()
- .filter_map(|(i, c)| (c == '/').then(|| i))
+ .filter_map(|(i, c)| (c == '/').then_some(i))
.nth(2)
.unwrap_or(path.len());
diff --git a/actix-web/src/route.rs b/actix-web/src/route.rs
index b37128f2..a46c1fdd 100644
--- a/actix-web/src/route.rs
+++ b/actix-web/src/route.rs
@@ -92,7 +92,7 @@ pub struct RouteService {
}
impl RouteService {
- // TODO: does this need to take &mut ?
+ #[allow(clippy::needless_pass_by_ref_mut)]
pub fn check(&self, req: &mut ServiceRequest) -> bool {
let guard_ctx = req.guard_ctx();
@@ -290,31 +290,32 @@ mod tests {
#[actix_rt::test]
async fn test_route() {
- let srv = init_service(
- App::new()
- .service(
- web::resource("/test")
- .route(web::get().to(HttpResponse::Ok))
- .route(web::put().to(|| async {
- Err::(error::ErrorBadRequest("err"))
- }))
- .route(web::post().to(|| async {
- sleep(Duration::from_millis(100)).await;
- Ok::<_, Infallible>(HttpResponse::Created())
- }))
- .route(web::delete().to(|| async {
- sleep(Duration::from_millis(100)).await;
- Err::(error::ErrorBadRequest("err"))
- })),
- )
- .service(web::resource("/json").route(web::get().to(|| async {
- sleep(Duration::from_millis(25)).await;
- web::Json(MyObject {
- name: "test".to_string(),
- })
- }))),
- )
- .await;
+ let srv =
+ init_service(
+ App::new()
+ .service(
+ web::resource("/test")
+ .route(web::get().to(HttpResponse::Ok))
+ .route(web::put().to(|| async {
+ Err::(error::ErrorBadRequest("err"))
+ }))
+ .route(web::post().to(|| async {
+ sleep(Duration::from_millis(100)).await;
+ Ok::<_, Infallible>(HttpResponse::Created())
+ }))
+ .route(web::delete().to(|| async {
+ sleep(Duration::from_millis(100)).await;
+ Err::(error::ErrorBadRequest("err"))
+ })),
+ )
+ .service(web::resource("/json").route(web::get().to(|| async {
+ sleep(Duration::from_millis(25)).await;
+ web::Json(MyObject {
+ name: "test".to_string(),
+ })
+ }))),
+ )
+ .await;
let req = TestRequest::with_uri("/test")
.method(Method::GET)
diff --git a/actix-web/src/rt.rs b/actix-web/src/rt.rs
index 7973da73..629ffe3a 100644
--- a/actix-web/src/rt.rs
+++ b/actix-web/src/rt.rs
@@ -66,8 +66,7 @@
// - Re-export but hide the runtime macros because they won't work directly but are required for
// `#[actix_web::main]` and `#[actix_web::test]` to work.
-pub use actix_rt::{net, pin, signal, spawn, task, time, Runtime, System, SystemRunner};
-
#[cfg(feature = "macros")]
#[doc(hidden)]
pub use actix_macros::{main, test};
+pub use actix_rt::{net, pin, signal, spawn, task, time, Runtime, System, SystemRunner};
diff --git a/actix-web/src/scope.rs b/actix-web/src/scope.rs
index 9af05674..e7c4e047 100644
--- a/actix-web/src/scope.rs
+++ b/actix-web/src/scope.rs
@@ -3,8 +3,8 @@ use std::{cell::RefCell, fmt, future::Future, mem, rc::Rc};
use actix_http::{body::MessageBody, Extensions};
use actix_router::{ResourceDef, Router};
use actix_service::{
- apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory,
- ServiceFactoryExt, Transform,
+ apply, apply_fn_factory, boxed, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt,
+ Transform,
};
use futures_core::future::LocalBoxFuture;
use futures_util::future::join_all;
@@ -273,12 +273,8 @@ where
pub fn default_service(mut self, f: F) -> Self
where
F: IntoServiceFactory,
- U: ServiceFactory<
- ServiceRequest,
- Config = (),
- Response = ServiceResponse,
- Error = Error,
- > + 'static,
+ U: ServiceFactory
+ + 'static,
U::InitError: fmt::Debug,
{
// create and configure default resource
@@ -604,11 +600,11 @@ mod tests {
#[actix_rt::test]
async fn test_scope() {
- let srv =
- init_service(App::new().service(
- web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok)),
- ))
- .await;
+ let srv = init_service(
+ App::new()
+ .service(web::scope("/app").service(web::resource("/path1").to(HttpResponse::Ok))),
+ )
+ .await;
let req = TestRequest::with_uri("/app/path1").to_request();
let resp = srv.call(req).await.unwrap();
@@ -638,8 +634,7 @@ mod tests {
#[actix_rt::test]
async fn test_scope_root2() {
let srv = init_service(
- App::new()
- .service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))),
+ App::new().service(web::scope("/app/").service(web::resource("").to(HttpResponse::Ok))),
)
.await;
@@ -784,10 +779,11 @@ mod tests {
#[actix_rt::test]
async fn test_nested_scope_no_slash() {
- let srv = init_service(App::new().service(web::scope("/app").service(
- web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
- )))
- .await;
+ let srv =
+ init_service(App::new().service(web::scope("/app").service(
+ web::scope("t1").service(web::resource("/path1").to(HttpResponse::Created)),
+ )))
+ .await;
let req = TestRequest::with_uri("/app/t1/path1").to_request();
let resp = srv.call(req).await.unwrap();
@@ -845,12 +841,9 @@ mod tests {
#[actix_rt::test]
async fn test_nested_scope_with_variable_segment() {
let srv = init_service(App::new().service(web::scope("/app").service(
- web::scope("/{project_id}").service(web::resource("/path1").to(
- |r: HttpRequest| {
- HttpResponse::Created()
- .body(format!("project: {}", &r.match_info()["project_id"]))
- },
- )),
+ web::scope("/{project_id}").service(web::resource("/path1").to(|r: HttpRequest| {
+ HttpResponse::Created().body(format!("project: {}", &r.match_info()["project_id"]))
+ })),
)))
.await;
@@ -1065,15 +1058,16 @@ mod tests {
#[allow(deprecated)]
#[actix_rt::test]
async fn test_override_data_default_service() {
- let srv = init_service(App::new().data(1usize).service(
- web::scope("app").data(10usize).default_service(web::to(
- |data: web::Data| {
- assert_eq!(**data, 10);
- HttpResponse::Ok()
- },
- )),
- ))
- .await;
+ let srv =
+ init_service(App::new().data(1usize).service(
+ web::scope("app").data(10usize).default_service(web::to(
+ |data: web::Data| {
+ assert_eq!(**data, 10);
+ HttpResponse::Ok()
+ },
+ )),
+ ))
+ .await;
let req = TestRequest::with_uri("/app/t").to_request();
let resp = call_service(&srv, req).await;
@@ -1150,11 +1144,11 @@ mod tests {
#[actix_rt::test]
async fn test_url_for_nested() {
let srv = init_service(App::new().service(web::scope("/a").service(
- web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(
- web::get().to(|req: HttpRequest| {
+ web::scope("/b").service(web::resource("/c/{stuff}").name("c").route(web::get().to(
+ |req: HttpRequest| {
HttpResponse::Ok().body(format!("{}", req.url_for("c", ["12345"]).unwrap()))
- }),
- )),
+ },
+ ))),
)))
.await;
diff --git a/actix-web/src/server.rs b/actix-web/src/server.rs
index c11d0ef5..e92985d3 100644
--- a/actix-web/src/server.rs
+++ b/actix-web/src/server.rs
@@ -7,19 +7,15 @@ use std::{
time::Duration,
};
+#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
+use actix_http::TlsAcceptorConfig;
use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response};
use actix_server::{Server, ServerBuilder};
use actix_service::{
map_config, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
};
-
#[cfg(feature = "openssl")]
use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorBuilder};
-#[cfg(feature = "rustls")]
-use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig;
-
-#[cfg(any(feature = "openssl", feature = "rustls"))]
-use actix_http::TlsAcceptorConfig;
use crate::{config::AppConfig, Error};
@@ -33,7 +29,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,
}
@@ -111,7 +107,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,
@@ -172,7 +167,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
}
@@ -224,8 +219,8 @@ 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"))]
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
self.config
.lock()
@@ -249,7 +244,10 @@ 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_web::rt::net::TcpStream` when no encryption is used.
///
/// See the `on_connect` example for additional details.
@@ -358,6 +356,7 @@ where
/// Resolves socket address(es) and binds server to created listener(s) for plaintext HTTP/1.x
/// or HTTP/2 connections.
+ #[cfg(feature = "http2")]
pub fn bind_auto_h2c(mut self, addrs: A) -> io::Result {
let sockets = bind_addrs(addrs, self.backlog)?;
@@ -369,20 +368,39 @@ 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.
///
/// 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)
}
@@ -437,9 +455,8 @@ where
.local_addr(addr);
if let Some(handler) = on_connect_fn.clone() {
- svc = svc.on_connect_ext(move |io: &_, ext: _| {
- (handler)(io as &dyn Any, ext)
- })
+ svc =
+ svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
};
let fac = factory()
@@ -456,6 +473,7 @@ where
}
/// Binds to existing listener for accepting incoming plaintext HTTP/1.x or HTTP/2 connections.
+ #[cfg(feature = "http2")]
pub fn listen_auto_h2c(mut self, lst: net::TcpListener) -> io::Result {
let cfg = self.config.clone();
let factory = self.factory.clone();
@@ -481,9 +499,8 @@ where
.local_addr(addr);
if let Some(handler) = on_connect_fn.clone() {
- svc = svc.on_connect_ext(move |io: &_, ext: _| {
- (handler)(io as &dyn Any, ext)
- })
+ svc =
+ svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
};
let fac = factory()
@@ -499,25 +516,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();
@@ -564,6 +597,57 @@ 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 OpenSSL.
///
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
@@ -715,8 +799,7 @@ where
.client_disconnect_timeout(c.client_disconnect_timeout);
if let Some(handler) = on_connect_fn.clone() {
- svc = svc
- .on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
+ svc = svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext));
}
let fac = factory()
@@ -759,10 +842,7 @@ where
}
/// Bind TCP listeners to socket addresses resolved from `addrs` with options.
-fn bind_addrs(
- addrs: impl net::ToSocketAddrs,
- backlog: u32,
-) -> io::Result> {
+fn bind_addrs(addrs: impl net::ToSocketAddrs, backlog: u32) -> io::Result> {
let mut err = None;
let mut success = false;
let mut sockets = Vec::new();
@@ -773,7 +853,7 @@ fn bind_addrs(
success = true;
sockets.push(lst);
}
- Err(e) => err = Some(e),
+ Err(error) => err = Some(error),
}
}
diff --git a/actix-web/src/service.rs b/actix-web/src/service.rs
index f7692ce1..0e17c994 100644
--- a/actix-web/src/service.rs
+++ b/actix-web/src/service.rs
@@ -696,30 +696,36 @@ service_tuple! { A B C D E F G H I J K L }
#[cfg(test)]
mod tests {
- use super::*;
- use crate::test::{self, init_service, TestRequest};
- use crate::{guard, http, web, App, HttpResponse};
use actix_service::Service;
use actix_utils::future::ok;
+ use super::*;
+ use crate::{
+ guard, http,
+ test::{self, init_service, TestRequest},
+ web, App, HttpResponse,
+ };
+
#[actix_rt::test]
async fn test_service() {
- let srv = init_service(
- App::new().service(web::service("/test").name("test").finish(
- |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())),
- )),
- )
- .await;
+ let srv =
+ init_service(
+ App::new().service(web::service("/test").name("test").finish(
+ |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())),
+ )),
+ )
+ .await;
let req = TestRequest::with_uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), http::StatusCode::OK);
- let srv = init_service(
- App::new().service(web::service("/test").guard(guard::Get()).finish(
- |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())),
- )),
- )
- .await;
+ let srv =
+ init_service(
+ App::new().service(web::service("/test").guard(guard::Get()).finish(
+ |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish())),
+ )),
+ )
+ .await;
let req = TestRequest::with_uri("/test")
.method(http::Method::PUT)
.to_request();
@@ -731,18 +737,19 @@ mod tests {
#[allow(deprecated)]
#[actix_rt::test]
async fn test_service_data() {
- let srv =
- init_service(
- App::new()
- .data(42u32)
- .service(web::service("/test").name("test").finish(
- |req: ServiceRequest| {
+ let srv = init_service(
+ App::new()
+ .data(42u32)
+ .service(
+ web::service("/test")
+ .name("test")
+ .finish(|req: ServiceRequest| {
assert_eq!(req.app_data::>().unwrap().as_ref(), &42);
ok(req.into_response(HttpResponse::Ok().finish()))
- },
- )),
- )
- .await;
+ }),
+ ),
+ )
+ .await;
let req = TestRequest::with_uri("/test").to_request();
let resp = srv.call(req).await.unwrap();
assert_eq!(resp.status(), http::StatusCode::OK);
@@ -773,9 +780,7 @@ mod tests {
async fn test_services_macro() {
let scoped = services![
web::service("/scoped_test1").name("scoped_test1").finish(
- |req: ServiceRequest| async {
- Ok(req.into_response(HttpResponse::Ok().finish()))
- }
+ |req: ServiceRequest| async { Ok(req.into_response(HttpResponse::Ok().finish())) }
),
web::resource("/scoped_test2").to(|| async { "test2" }),
];
@@ -861,9 +866,7 @@ mod tests {
svc.call(req)
})
.route("/", web::get().to(|| async { "" }))
- .service(
- web::resource("/resource1/{name}/index.html").route(web::get().to(index)),
- ),
+ .service(web::resource("/resource1/{name}/index.html").route(web::get().to(index))),
)
.await;
diff --git a/actix-web/src/test/mod.rs b/actix-web/src/test/mod.rs
index 5d9367b8..5e647956 100644
--- a/actix-web/src/test/mod.rs
+++ b/actix-web/src/test/mod.rs
@@ -29,18 +29,20 @@ mod test_request;
mod test_services;
mod test_utils;
-pub use self::test_request::TestRequest;
#[allow(deprecated)]
pub use self::test_services::{default_service, ok_service, simple_service, status_service};
-#[allow(deprecated)]
-pub use self::test_utils::{
- call_and_read_body, call_and_read_body_json, call_service, init_service, read_body,
- read_body_json, read_response, read_response_json, try_call_and_read_body_json,
- try_call_service, try_read_body, try_read_body_json,
-};
-
#[cfg(test)]
pub(crate) use self::test_utils::try_init_service;
+#[allow(deprecated)]
+pub use self::test_utils::{read_response, read_response_json};
+pub use self::{
+ test_request::TestRequest,
+ test_utils::{
+ call_and_read_body, call_and_read_body_json, call_service, init_service, read_body,
+ read_body_json, try_call_and_read_body_json, try_call_service, try_read_body,
+ try_read_body_json,
+ },
+};
/// Reduces boilerplate code when testing expected response payloads.
///
diff --git a/actix-web/src/test/test_request.rs b/actix-web/src/test/test_request.rs
index e81561d1..5491af0a 100644
--- a/actix-web/src/test/test_request.rs
+++ b/actix-web/src/test/test_request.rs
@@ -3,13 +3,17 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc};
use actix_http::{test::TestRequest as HttpTestRequest, Request};
use serde::Serialize;
+#[cfg(feature = "cookies")]
+use crate::cookie::{Cookie, CookieJar};
use crate::{
app_service::AppInitServiceState,
config::AppConfig,
data::Data,
dev::{Extensions, Path, Payload, ResourceDef, Service, Url},
- http::header::ContentType,
- http::{header::TryIntoHeaderPair, Method, Uri, Version},
+ http::{
+ header::{ContentType, TryIntoHeaderPair},
+ Method, Uri, Version,
+ },
rmap::ResourceMap,
service::{ServiceRequest, ServiceResponse},
test,
@@ -17,9 +21,6 @@ use crate::{
HttpRequest, HttpResponse,
};
-#[cfg(feature = "cookies")]
-use crate::cookie::{Cookie, CookieJar};
-
/// Test `Request` builder.
///
/// For unit testing, actix provides a request builder type and a simple handler runner. TestRequest implements a builder-like pattern.
@@ -197,8 +198,7 @@ impl TestRequest {
///
/// The `Content-Type` header is set to `application/json`.
pub fn set_json(mut self, data: impl Serialize) -> Self {
- let bytes =
- serde_json::to_string(&data).expect("Failed to serialize test data to json");
+ let bytes = serde_json::to_string(&data).expect("Failed to serialize test data to json");
self.req.set_payload(bytes);
self.req.insert_header(ContentType::json());
self
diff --git a/actix-web/src/test/test_utils.rs b/actix-web/src/test/test_utils.rs
index b985c3b3..4540d8a6 100644
--- a/actix-web/src/test/test_utils.rs
+++ b/actix-web/src/test/test_utils.rs
@@ -201,9 +201,7 @@ where
}
/// Fallible version of [`read_body`] that allows testing MessageBody reading errors.
-pub async fn try_read_body(
- res: ServiceResponse,
-) -> Result::Error>
+pub async fn try_read_body(res: ServiceResponse) -> Result::Error>
where
B: MessageBody,
{
@@ -359,13 +357,11 @@ where
#[cfg(test)]
mod tests {
-
use serde::{Deserialize, Serialize};
use super::*;
use crate::{
- dev::ServiceRequest, http::header, test::TestRequest, web, App, HttpMessage,
- HttpResponse,
+ dev::ServiceRequest, http::header, test::TestRequest, web, App, HttpMessage, HttpResponse,
};
#[actix_rt::test]
@@ -409,10 +405,11 @@ mod tests {
#[actix_rt::test]
async fn test_response_json() {
- let app = init_service(App::new().service(web::resource("/people").route(
- web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
- )))
- .await;
+ let app =
+ init_service(App::new().service(web::resource("/people").route(
+ web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
+ )))
+ .await;
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
@@ -428,10 +425,11 @@ mod tests {
#[actix_rt::test]
async fn test_try_response_json_error() {
- let app = init_service(App::new().service(web::resource("/people").route(
- web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
- )))
- .await;
+ let app =
+ init_service(App::new().service(web::resource("/people").route(
+ web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
+ )))
+ .await;
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
@@ -448,10 +446,11 @@ mod tests {
#[actix_rt::test]
async fn test_body_json() {
- let app = init_service(App::new().service(web::resource("/people").route(
- web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
- )))
- .await;
+ let app =
+ init_service(App::new().service(web::resource("/people").route(
+ web::post().to(|person: web::Json| HttpResponse::Ok().json(person)),
+ )))
+ .await;
let payload = r#"{"id":"12345","name":"User name"}"#.as_bytes();
@@ -468,10 +467,11 @@ mod tests {
#[actix_rt::test]
async fn test_try_body_json_error() {
- let app = init_service(App::new().service(web::resource("/people").route(
- web::post().to(|person: web::Json