,
+
flags: Flags,
}
diff --git a/actix-http/src/requests/request.rs b/actix-http/src/requests/request.rs
index 1750fb2f7..6a267a7a6 100644
--- a/actix-http/src/requests/request.rs
+++ b/actix-http/src/requests/request.rs
@@ -173,7 +173,7 @@ impl Request
{
/// Peer address is the directly connected peer's socket address. If a proxy is used in front of
/// the Actix Web server, then it would be address of this proxy.
///
- /// Will only return None when called in unit tests.
+ /// Will only return None when called in unit tests unless set manually.
#[inline]
pub fn peer_addr(&self) -> Option {
self.head().peer_addr
diff --git a/actix-http/src/service.rs b/actix-http/src/service.rs
index fb38ba636..a58be93c7 100644
--- a/actix-http/src/service.rs
+++ b/actix-http/src/service.rs
@@ -241,13 +241,25 @@ where
}
/// Configuration options used when accepting TLS connection.
-#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
+#[cfg(any(
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22",
+ feature = "rustls-0_23",
+))]
#[derive(Debug, Default)]
pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option,
}
-#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
+#[cfg(any(
+ feature = "openssl",
+ feature = "rustls-0_20",
+ feature = "rustls-0_21",
+ feature = "rustls-0_22",
+ feature = "rustls-0_23",
+))]
impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
@@ -353,12 +365,12 @@ mod openssl {
}
#[cfg(feature = "rustls-0_20")]
-mod rustls_020 {
+mod rustls_0_20 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
- rustls::{reexports::ServerConfig, Acceptor, TlsStream},
+ rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
@@ -389,7 +401,7 @@ mod rustls_020 {
U::Error: fmt::Display + Into>,
U::InitError: fmt::Debug,
{
- /// Create Rustls based service.
+ /// Create Rustls v0.20 based service.
pub fn rustls(
self,
config: ServerConfig,
@@ -403,7 +415,7 @@ mod rustls_020 {
self.rustls_with_config(config, TlsAcceptorConfig::default())
}
- /// Create Rustls based service with custom TLS acceptor configuration.
+ /// Create Rustls v0.20 based service with custom TLS acceptor configuration.
pub fn rustls_with_config(
self,
mut config: ServerConfig,
@@ -449,7 +461,7 @@ mod rustls_020 {
}
#[cfg(feature = "rustls-0_21")]
-mod rustls_021 {
+mod rustls_0_21 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@@ -485,7 +497,7 @@ mod rustls_021 {
U::Error: fmt::Display + Into>,
U::InitError: fmt::Debug,
{
- /// Create Rustls based service.
+ /// Create Rustls v0.21 based service.
pub fn rustls_021(
self,
config: ServerConfig,
@@ -499,7 +511,7 @@ mod rustls_021 {
self.rustls_021_with_config(config, TlsAcceptorConfig::default())
}
- /// Create Rustls based service with custom TLS acceptor configuration.
+ /// Create Rustls v0.21 based service with custom TLS acceptor configuration.
pub fn rustls_021_with_config(
self,
mut config: ServerConfig,
@@ -544,6 +556,198 @@ mod rustls_021 {
}
}
+#[cfg(feature = "rustls-0_22")]
+mod rustls_0_22 {
+ use std::io;
+
+ use actix_service::ServiceFactoryExt as _;
+ use actix_tls::accept::{
+ rustls_0_22::{reexports::ServerConfig, Acceptor, TlsStream},
+ TlsError,
+ };
+
+ use super::*;
+
+ impl HttpService, S, B, X, U>
+ where
+ S: ServiceFactory,
+ S::Future: 'static,
+ S::Error: Into> + 'static,
+ S::InitError: fmt::Debug,
+ S::Response: Into> + 'static,
+ >::Future: 'static,
+
+ B: MessageBody + 'static,
+
+ X: ServiceFactory,
+ X::Future: 'static,
+ X::Error: Into>,
+ X::InitError: fmt::Debug,
+
+ U: ServiceFactory<
+ (Request, Framed, h1::Codec>),
+ Config = (),
+ Response = (),
+ >,
+ U::Future: 'static,
+ U::Error: fmt::Display + Into>,
+ U::InitError: fmt::Debug,
+ {
+ /// Create Rustls v0.22 based service.
+ pub fn rustls_0_22(
+ self,
+ config: ServerConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ self.rustls_0_22_with_config(config, TlsAcceptorConfig::default())
+ }
+
+ /// Create Rustls v0.22 based service with custom TLS acceptor configuration.
+ pub fn rustls_0_22_with_config(
+ self,
+ mut config: ServerConfig,
+ tls_acceptor_config: TlsAcceptorConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
+ protos.extend_from_slice(&config.alpn_protocols);
+ config.alpn_protocols = protos;
+
+ let mut acceptor = Acceptor::new(config);
+
+ if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
+ acceptor.set_handshake_timeout(handshake_timeout);
+ }
+
+ acceptor
+ .map_init_err(|_| {
+ unreachable!("TLS acceptor service factory does not error on init")
+ })
+ .map_err(TlsError::into_service_error)
+ .and_then(|io: TlsStream| async {
+ let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
+ if protos.windows(2).any(|window| window == b"h2") {
+ Protocol::Http2
+ } else {
+ Protocol::Http1
+ }
+ } else {
+ Protocol::Http1
+ };
+ let peer_addr = io.get_ref().0.peer_addr().ok();
+ Ok((io, proto, peer_addr))
+ })
+ .and_then(self.map_err(TlsError::Service))
+ }
+ }
+}
+
+#[cfg(feature = "rustls-0_23")]
+mod rustls_0_23 {
+ use std::io;
+
+ use actix_service::ServiceFactoryExt as _;
+ use actix_tls::accept::{
+ rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
+ TlsError,
+ };
+
+ use super::*;
+
+ impl HttpService, S, B, X, U>
+ where
+ S: ServiceFactory,
+ S::Future: 'static,
+ S::Error: Into> + 'static,
+ S::InitError: fmt::Debug,
+ S::Response: Into> + 'static,
+ >::Future: 'static,
+
+ B: MessageBody + 'static,
+
+ X: ServiceFactory,
+ X::Future: 'static,
+ X::Error: Into>,
+ X::InitError: fmt::Debug,
+
+ U: ServiceFactory<
+ (Request, Framed, h1::Codec>),
+ Config = (),
+ Response = (),
+ >,
+ U::Future: 'static,
+ U::Error: fmt::Display + Into>,
+ U::InitError: fmt::Debug,
+ {
+ /// Create Rustls v0.23 based service.
+ pub fn rustls_0_23(
+ self,
+ config: ServerConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ self.rustls_0_23_with_config(config, TlsAcceptorConfig::default())
+ }
+
+ /// Create Rustls v0.23 based service with custom TLS acceptor configuration.
+ pub fn rustls_0_23_with_config(
+ self,
+ mut config: ServerConfig,
+ tls_acceptor_config: TlsAcceptorConfig,
+ ) -> impl ServiceFactory<
+ TcpStream,
+ Config = (),
+ Response = (),
+ Error = TlsError,
+ InitError = (),
+ > {
+ let mut protos = vec![b"h2".to_vec(), b"http/1.1".to_vec()];
+ protos.extend_from_slice(&config.alpn_protocols);
+ config.alpn_protocols = protos;
+
+ let mut acceptor = Acceptor::new(config);
+
+ if let Some(handshake_timeout) = tls_acceptor_config.handshake_timeout {
+ acceptor.set_handshake_timeout(handshake_timeout);
+ }
+
+ acceptor
+ .map_init_err(|_| {
+ unreachable!("TLS acceptor service factory does not error on init")
+ })
+ .map_err(TlsError::into_service_error)
+ .and_then(|io: TlsStream| async {
+ let proto = if let Some(protos) = io.get_ref().1.alpn_protocol() {
+ if protos.windows(2).any(|window| window == b"h2") {
+ Protocol::Http2
+ } else {
+ Protocol::Http1
+ }
+ } else {
+ Protocol::Http1
+ };
+ let peer_addr = io.get_ref().0.peer_addr().ok();
+ Ok((io, proto, peer_addr))
+ })
+ .and_then(self.map_err(TlsError::Service))
+ }
+ }
+}
+
impl ServiceFactory<(T, Protocol, Option)>
for HttpService
where
diff --git a/actix-http/src/ws/frame.rs b/actix-http/src/ws/frame.rs
index c9fb0cde9..35b3f8e66 100644
--- a/actix-http/src/ws/frame.rs
+++ b/actix-http/src/ws/frame.rs
@@ -178,14 +178,14 @@ impl Parser {
};
if payload_len < 126 {
- dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
+ dst.reserve(p_len + 2);
dst.put_slice(&[one, two | payload_len as u8]);
} else if payload_len <= 65_535 {
- dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
+ dst.reserve(p_len + 4);
dst.put_slice(&[one, two | 126]);
dst.put_u16(payload_len as u16);
} else {
- dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
+ dst.reserve(p_len + 10);
dst.put_slice(&[one, two | 127]);
dst.put_u64(payload_len as u64);
};
diff --git a/actix-http/src/ws/mod.rs b/actix-http/src/ws/mod.rs
index 87f9b38f3..3ed53b70a 100644
--- a/actix-http/src/ws/mod.rs
+++ b/actix-http/src/ws/mod.rs
@@ -221,7 +221,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
use super::*;
- use crate::{header, test::TestRequest, Method};
+ use crate::{header, test::TestRequest};
#[test]
fn test_handshake() {
diff --git a/actix-http/src/ws/proto.rs b/actix-http/src/ws/proto.rs
index 0653c00b0..27815eaf2 100644
--- a/actix-http/src/ws/proto.rs
+++ b/actix-http/src/ws/proto.rs
@@ -1,7 +1,4 @@
-use std::{
- convert::{From, Into},
- fmt,
-};
+use std::fmt;
use base64::prelude::*;
use tracing::error;
diff --git a/actix-http/tests/test_openssl.rs b/actix-http/tests/test_openssl.rs
index cb16a4fec..4dd22b585 100644
--- a/actix-http/tests/test_openssl.rs
+++ b/actix-http/tests/test_openssl.rs
@@ -42,9 +42,11 @@ where
}
fn tls_config() -> SslAcceptor {
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
+
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
diff --git a/actix-http/tests/test_rustls.rs b/actix-http/tests/test_rustls.rs
index c94e579e5..3ca0d94c2 100644
--- a/actix-http/tests/test_rustls.rs
+++ b/actix-http/tests/test_rustls.rs
@@ -1,6 +1,6 @@
-#![cfg(feature = "rustls-0_21")]
+#![cfg(feature = "rustls-0_23")]
-extern crate tls_rustls_021 as rustls;
+extern crate tls_rustls_023 as rustls;
use std::{
convert::Infallible,
@@ -20,13 +20,13 @@ use actix_http::{
use actix_http_test::test_server;
use actix_rt::pin;
use actix_service::{fn_factory_with_config, fn_service};
-use actix_tls::connect::rustls_0_21::webpki_roots_cert_store;
+use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error};
use futures_core::{ready, Stream};
use futures_util::stream::once;
-use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
+use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
use rustls_pemfile::{certs, pkcs8_private_keys};
async fn load_body(stream: S) -> Result
@@ -52,24 +52,25 @@ where
}
fn tls_config() -> RustlsServerConfig {
- let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
- let cert_file = cert.serialize_pem().unwrap();
- let key_file = cert.serialize_private_key_pem();
+ let rcgen::CertifiedKey { cert, key_pair } =
+ rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
+ let cert_file = cert.pem();
+ let key_file = key_pair.serialize_pem();
let cert_file = &mut BufReader::new(cert_file.as_bytes());
let key_file = &mut BufReader::new(key_file.as_bytes());
- let cert_chain = certs(cert_file)
- .unwrap()
- .into_iter()
- .map(Certificate)
- .collect();
- let mut keys = pkcs8_private_keys(key_file).unwrap();
+ let cert_chain = certs(cert_file).collect::, _>>().unwrap();
+ let mut keys = pkcs8_private_keys(key_file)
+ .collect::, _>>()
+ .unwrap();
let mut config = RustlsServerConfig::builder()
- .with_safe_defaults()
.with_no_client_auth()
- .with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
+ .with_single_cert(
+ cert_chain,
+ rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
+ )
.unwrap();
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
@@ -83,7 +84,6 @@ pub fn get_negotiated_alpn_protocol(
client_alpn_protocol: &[u8],
) -> Option> {
let mut config = rustls::ClientConfig::builder()
- .with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth();
@@ -109,7 +109,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -123,7 +123,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -141,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok())
})
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -159,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok())
})
- .rustls_021_with_config(
+ .rustls_0_23_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
@@ -180,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_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -206,7 +206,7 @@ async fn h2_content_length() {
];
ok::<_, Infallible>(Response::new(statuses[indx]))
})
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -278,7 +278,7 @@ async fn h2_headers() {
}
ok::<_, Infallible>(config.body(data.clone()))
})
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -317,7 +317,7 @@ async fn h2_body2() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -334,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_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -360,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_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -385,7 +385,7 @@ async fn h2_head_binary2() {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -411,7 +411,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
)
})
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -435,7 +435,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)),
)
})
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -464,7 +464,7 @@ async fn h2_response_http_error_handling() {
)
}))
}))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -494,7 +494,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| err::, _>(BadRequest))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -511,7 +511,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h1(|_| err::, _>(BadRequest))
- .rustls_021(tls_config())
+ .rustls_0_23(tls_config())
})
.await;
@@ -534,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_021(config)
+ .rustls_0_23(config)
})
.await;
@@ -556,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_021(config)
+ .rustls_0_23(config)
})
.await;
@@ -582,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_021(config)
+ .rustls_0_23(config)
})
.await;
diff --git a/actix-multipart-derive/CHANGES.md b/actix-multipart-derive/CHANGES.md
index e36a13d04..1b44ba4b7 100644
--- a/actix-multipart-derive/CHANGES.md
+++ b/actix-multipart-derive/CHANGES.md
@@ -2,6 +2,8 @@
## Unreleased
+- Minimum supported Rust version (MSRV) is now 1.72.
+
## 0.6.1
- Update `syn` dependency to `2`.
diff --git a/actix-multipart-derive/Cargo.toml b/actix-multipart-derive/Cargo.toml
index 2f049a3fb..e978864a3 100644
--- a/actix-multipart-derive/Cargo.toml
+++ b/actix-multipart-derive/Cargo.toml
@@ -4,10 +4,11 @@ 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"
-license = "MIT OR Apache-2.0"
-edition = "2021"
+homepage.workspace = true
+repository.workspace = true
+license.workspace = true
+edition.workspace = true
+rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
diff --git a/actix-multipart-derive/README.md b/actix-multipart-derive/README.md
index cd5780c56..ec0afffdd 100644
--- a/actix-multipart-derive/README.md
+++ b/actix-multipart-derive/README.md
@@ -1,17 +1,16 @@
-# actix-multipart-derive
+# `actix-multipart-derive`
> The derive macro implementation for actix-multipart-derive.
+
+
[![crates.io](https://img.shields.io/crates/v/actix-multipart-derive?label=latest)](https://crates.io/crates/actix-multipart-derive)
[![Documentation](https://docs.rs/actix-multipart-derive/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart-derive/0.6.1)
-![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
+![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg)
[![dependency status](https://deps.rs/crate/actix-multipart-derive/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.6.1)
[![Download](https://img.shields.io/crates/d/actix-multipart-derive.svg)](https://crates.io/crates/actix-multipart-derive)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
-## Documentation & Resources
-
-- [API Documentation](https://docs.rs/actix-multipart-derive)
-- Minimum Supported Rust Version (MSRV): 1.68
+
diff --git a/actix-multipart-derive/tests/trybuild.rs b/actix-multipart-derive/tests/trybuild.rs
index 88aa619c6..6b25d78df 100644
--- a/actix-multipart-derive/tests/trybuild.rs
+++ b/actix-multipart-derive/tests/trybuild.rs
@@ -1,4 +1,4 @@
-#[rustversion::stable(1.68)] // MSRV
+#[rustversion::stable(1.72)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();
diff --git a/actix-multipart/CHANGES.md b/actix-multipart/CHANGES.md
index 50faf7cfa..a91edf9c8 100644
--- a/actix-multipart/CHANGES.md
+++ b/actix-multipart/CHANGES.md
@@ -2,6 +2,11 @@
## Unreleased
+## 0.6.2
+
+- Add testing utilities under new module `test`.
+- Minimum supported Rust version (MSRV) is now 1.72.
+
## 0.6.1
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
diff --git a/actix-multipart/Cargo.toml b/actix-multipart/Cargo.toml
index 257d56132..5e9b78d84 100644
--- a/actix-multipart/Cargo.toml
+++ b/actix-multipart/Cargo.toml
@@ -1,6 +1,6 @@
[package]
name = "actix-multipart"
-version = "0.6.1"
+version = "0.6.2"
authors = [
"Nikolay Kim ",
"Jacob Halsey ",
@@ -16,6 +16,21 @@ edition = "2021"
rustdoc-args = ["--cfg", "docsrs"]
all-features = true
+[package.metadata.cargo_check_external_types]
+allowed_external_types = [
+ "actix_http::*",
+ "actix_multipart_derive::*",
+ "actix_utils::*",
+ "actix_web::*",
+ "bytes::*",
+ "futures_core::*",
+ "mime::*",
+ "serde_json::*",
+ "serde_plain::*",
+ "serde::*",
+ "tempfile::*",
+]
+
[features]
default = ["tempfile", "derive"]
derive = ["actix-multipart-derive"]
@@ -35,6 +50,7 @@ local-waker = "0.1"
log = "0.4"
memchr = "2.5"
mime = "0.3"
+rand = "0.8"
serde = "1"
serde_json = "1"
serde_plain = "1"
@@ -46,7 +62,9 @@ actix-http = "3"
actix-multipart-rfc7578 = "0.10"
actix-rt = "2.2"
actix-test = "0.1"
+actix-web = "4"
awc = "3"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
+multer = "3"
tokio = { version = "1.24.2", features = ["sync"] }
tokio-stream = "0.1"
diff --git a/actix-multipart/README.md b/actix-multipart/README.md
index 8fe0328ab..d61347f32 100644
--- a/actix-multipart/README.md
+++ b/actix-multipart/README.md
@@ -1,17 +1,68 @@
-# actix-multipart
+# `actix-multipart`
-> Multipart form support for Actix Web.
+
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
-[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.1)](https://docs.rs/actix-multipart/0.6.1)
-![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
+[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.2)](https://docs.rs/actix-multipart/0.6.2)
+![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
-[![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1)
+[![dependency status](https://deps.rs/crate/actix-multipart/0.6.2/status.svg)](https://deps.rs/crate/actix-multipart/0.6.2)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
-## Documentation & Resources
+
-- [API Documentation](https://docs.rs/actix-multipart)
-- Minimum Supported Rust Version (MSRV): 1.68
+
+
+Multipart form support for Actix Web.
+
+## Examples
+
+```rust
+use actix_web::{post, App, HttpServer, Responder};
+
+use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
+use serde::Deserialize;
+
+#[derive(Debug, Deserialize)]
+struct Metadata {
+ name: String,
+}
+
+#[derive(Debug, MultipartForm)]
+struct UploadForm {
+ #[multipart(limit = "100MB")]
+ file: TempFile,
+ json: MPJson,
+}
+
+#[post("/videos")]
+pub async fn post_video(MultipartForm(form): MultipartForm) -> impl Responder {
+ format!(
+ "Uploaded file {}, with size: {}",
+ form.json.name, form.file.size
+ )
+}
+
+#[actix_web::main]
+async fn main() -> std::io::Result<()> {
+ HttpServer::new(move || App::new().service(post_video))
+ .bind(("127.0.0.1", 8080))?
+ .run()
+ .await
+}
+```
+
+
+
+[More available in the examples repo →](https://github.com/actix/examples/tree/master/forms/multipart)
+
+Curl request :
+
+```bash
+curl -v --request POST \
+ --url http://localhost:8080/videos \
+ -F 'json={"name": "Cargo.lock"};type=application/json' \
+ -F file=@./Cargo.lock
+```
diff --git a/actix-multipart/src/form/json.rs b/actix-multipart/src/form/json.rs
index fb90a82b9..bb4e03bf6 100644
--- a/actix-multipart/src/form/json.rs
+++ b/actix-multipart/src/form/json.rs
@@ -131,14 +131,13 @@ impl Default for JsonConfig {
#[cfg(test)]
mod tests {
- use std::{collections::HashMap, io::Cursor};
+ use std::collections::HashMap;
- use actix_multipart_rfc7578::client::multipart;
use actix_web::{http::StatusCode, web, App, HttpResponse, Responder};
+ use bytes::Bytes;
use crate::form::{
json::{Json, JsonConfig},
- tests::send_form,
MultipartForm,
};
@@ -155,6 +154,8 @@ mod tests {
HttpResponse::Ok().finish()
}
+ const TEST_JSON: &str = r#"{"key1": "value1", "key2": "value2"}"#;
+
#[actix_rt::test]
async fn test_json_without_content_type() {
let srv = actix_test::start(|| {
@@ -163,10 +164,16 @@ mod tests {
.app_data(JsonConfig::default().validate_content_type(false))
});
- let mut form = multipart::Form::default();
- form.add_text("json", "{\"key1\": \"value1\", \"key2\": \"value2\"}");
- let response = send_form(&srv, form, "/").await;
- assert_eq!(response.status(), StatusCode::OK);
+ let (body, headers) = crate::test::create_form_data_payload_and_headers(
+ "json",
+ None,
+ None,
+ Bytes::from_static(TEST_JSON.as_bytes()),
+ );
+ let mut req = srv.post("/");
+ *req.headers_mut() = headers;
+ let res = req.send_body(body).await.unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
}
#[actix_rt::test]
@@ -178,17 +185,27 @@ mod tests {
});
// Deny because wrong content type
- let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
- let mut form = multipart::Form::default();
- form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_OCTET_STREAM);
- let response = send_form(&srv, form, "/").await;
- assert_eq!(response.status(), StatusCode::BAD_REQUEST);
+ let (body, headers) = crate::test::create_form_data_payload_and_headers(
+ "json",
+ None,
+ Some(mime::APPLICATION_OCTET_STREAM),
+ Bytes::from_static(TEST_JSON.as_bytes()),
+ );
+ let mut req = srv.post("/");
+ *req.headers_mut() = headers;
+ let res = req.send_body(body).await.unwrap();
+ assert_eq!(res.status(), StatusCode::BAD_REQUEST);
// Allow because correct content type
- let bytes = Cursor::new("{\"key1\": \"value1\", \"key2\": \"value2\"}");
- let mut form = multipart::Form::default();
- form.add_reader_file_with_mime("json", bytes, "", mime::APPLICATION_JSON);
- let response = send_form(&srv, form, "/").await;
- assert_eq!(response.status(), StatusCode::OK);
+ let (body, headers) = crate::test::create_form_data_payload_and_headers(
+ "json",
+ None,
+ Some(mime::APPLICATION_JSON),
+ Bytes::from_static(TEST_JSON.as_bytes()),
+ );
+ let mut req = srv.post("/");
+ *req.headers_mut() = headers;
+ let res = req.send_body(body).await.unwrap();
+ assert_eq!(res.status(), StatusCode::OK);
}
}
diff --git a/actix-multipart/src/form/mod.rs b/actix-multipart/src/form/mod.rs
index 67adfd4b2..68cdefec5 100644
--- a/actix-multipart/src/form/mod.rs
+++ b/actix-multipart/src/form/mod.rs
@@ -33,6 +33,14 @@ pub trait FieldReader<'t>: Sized + Any {
type Future: Future