Compare commits

..

No commits in common. "ab73c72596298acf6d5128b9790c7da18dfac086" and "16c84c280546859abbb0f75648e670edb6aa880b" have entirely different histories.

117 changed files with 654 additions and 1554 deletions

View File

@ -1,6 +1,6 @@
[alias]
lint = "clippy --workspace --all-targets -- -Dclippy::todo"
lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
lint = "clippy --workspace --tests --examples --bins -- -Dclippy::todo"
lint-all = "clippy --workspace --all-features --tests --examples --bins -- -Dclippy::todo"
# lib checking
ci-check-min = "hack --workspace check --no-default-features"

View File

@ -32,22 +32,22 @@ jobs:
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
run: choco install openssl -y --forcex64 --no-progress
- name: Set OpenSSL dir in env
if: matrix.target.os == 'windows-latest'
run: |
set -e
choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.32.0
- name: Install cargo-hack
uses: taiki-e/install-action@v2.25.9
with:
tool: cargo-hack,cargo-ci-cache-clean
tool: cargo-hack
- name: check minimal
run: cargo ci-check-min
@ -57,12 +57,10 @@ jobs:
- name: tests
timeout-minutes: 60
shell: bash
run: |
set -e
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
@ -71,8 +69,10 @@ jobs:
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
- name: CI cache clean
run: cargo-ci-cache-clean
- name: Clear the cargo caches
run: |
cargo --locked install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
cargo-cache
ci_feature_powerset_check:
name: Verify Feature Combinations
@ -81,14 +81,11 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Free Disk Space
run: ./scripts/free-disk-space.sh
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install cargo-hack
uses: taiki-e/install-action@v2.32.0
uses: taiki-e/install-action@v2.25.9
with:
tool: cargo-hack
@ -109,7 +106,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install nextest
uses: taiki-e/install-action@v2.32.0
uses: taiki-e/install-action@v2.25.9
with:
tool: nextest

View File

@ -26,7 +26,7 @@ jobs:
- { name: macOS, os: macos-latest, triple: x86_64-apple-darwin }
- { name: Windows, os: windows-latest, triple: x86_64-pc-windows-msvc }
version:
- { name: msrv, version: 1.72.0 }
- { name: msrv, version: 1.68.0 }
- { name: stable, version: stable }
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
@ -37,27 +37,29 @@ jobs:
- name: Install OpenSSL
if: matrix.target.os == 'windows-latest'
shell: bash
run: choco install openssl -y --forcex64 --no-progress
- name: Set OpenSSL dir in env
if: matrix.target.os == 'windows-latest'
run: |
set -e
choco install openssl --version=1.1.1.2100 -y --no-progress
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL-Win64' | Out-File -FilePath $env:GITHUB_ENV -Append
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' | Out-File -FilePath $env:GITHUB_ENV -Append
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install cargo-hack and cargo-ci-cache-clean
uses: taiki-e/install-action@v2.32.0
- name: Install cargo-hack
uses: taiki-e/install-action@v2.25.9
with:
tool: cargo-hack,cargo-ci-cache-clean
tool: cargo-hack
- name: workaround MSRV issues
if: matrix.version.name == 'msrv'
run: |
cargo update -p=clap --precise=4.4.18
cargo update -p=clap --precise=4.3.24
cargo update -p=clap_lex --precise=0.5.0
cargo update -p=anstyle --precise=1.0.2
- name: check minimal
run: cargo ci-check-min
@ -67,13 +69,10 @@ jobs:
- name: tests
timeout-minutes: 60
shell: bash
run: |
set -e
cargo test --lib --tests -p=actix-router --no-default-features
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,rustls-0_22,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web --features=rustls-0_20,rustls-0_21,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
@ -82,8 +81,10 @@ jobs:
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
- name: CI cache clean
run: cargo-ci-cache-clean
- name: Clear the cargo caches
run: |
cargo --locked install cargo-cache --version 0.8.3 --no-default-features --features ci-autoclean
cargo-cache
io-uring:
name: io-uring tests

View File

@ -23,7 +23,7 @@ jobs:
components: llvm-tools-preview
- name: Install cargo-llvm-cov
uses: taiki-e/install-action@v2.32.0
uses: taiki-e/install-action@v2.25.9
with:
tool: cargo-llvm-cov
@ -31,9 +31,7 @@ jobs:
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.1.1
uses: codecov/codecov-action@v3.1.4
with:
files: codecov.json
fail_ci_if_error: true
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}

View File

@ -82,7 +82,7 @@ jobs:
toolchain: nightly-2023-08-25
- name: Install cargo-public-api
uses: taiki-e/install-action@v2.32.0
uses: taiki-e/install-action@v2.24.1
with:
tool: cargo-public-api

View File

@ -17,7 +17,7 @@ members = [
[workspace.package]
license = "MIT OR Apache-2.0"
edition = "2021"
rust-version = "1.72"
rust-version = "1.68"
[profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.

View File

@ -2,8 +2,6 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.5
- Fix handling of special characters in filenames.

View File

@ -47,5 +47,5 @@ actix-server = { version = "2.2", optional = true } # ensure matching tokio-urin
actix-rt = "2.7"
actix-test = "0.1"
actix-web = "4"
env_logger = "0.11"
env_logger = "0.10"
tempfile = "3.2"

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.5)](https://docs.rs/actix-files/0.6.5)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-files.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.5/status.svg)](https://deps.rs/crate/actix-files/0.6.5)

View File

@ -75,7 +75,7 @@ mod tests {
dev::ServiceFactory,
guard,
http::{
header::{self, ContentDisposition, DispositionParam},
header::{self, ContentDisposition, DispositionParam, DispositionType},
Method, StatusCode,
},
middleware::Compress,
@ -568,7 +568,6 @@ mod tests {
assert_eq!(bytes, data);
}
#[cfg(not(target_os = "windows"))]
#[actix_rt::test]
async fn test_static_files_with_special_characters() {
// Create the file we want to test against ad-hoc. We can't check it in as otherwise

View File

@ -2,10 +2,6 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.2.0
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 3.1.0

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "3.2.0"
version = "3.1.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"]

View File

@ -5,12 +5,17 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.2.0)](https://docs.rs/actix-http-test/3.2.0)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.1.0)](https://docs.rs/actix-http-test/3.1.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.2.0/status.svg)](https://deps.rs/crate/actix-http-test/3.2.0)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.1.0/status.svg)](https://deps.rs/crate/actix-http-test/3.1.0)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http-test)
- Minimum Supported Rust Version (MSRV): 1.68

View File

@ -2,18 +2,6 @@
## Unreleased
### Changed
- Minimum supported Rust version (MSRV) is now 1.72.
## 3.6.0
### Added
- Add `rustls-0_22` crate feature.
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_22()` and `HttpService::rustls_0_22_with_config()` service constructors.
- Implement `From<&HeaderMap>` for `http::HeaderMap`.
## 3.5.1
### Fixed

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.6.0"
version = "3.5.1"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@ -20,18 +20,8 @@ edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
features = [
"http2",
"ws",
"openssl",
"rustls-0_20",
"rustls-0_21",
"rustls-0_22",
"compress-brotli",
"compress-gzip",
"compress-zstd",
]
# features that docs.rs will build with
features = ["http2", "ws", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd"]
[lib]
name = "actix_http"
@ -63,9 +53,6 @@ rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
# Compression codecs
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
@ -106,12 +93,12 @@ h2 = { version = "0.3.24", optional = true }
# websockets
local-channel = { version = "0.1", optional = true }
base64 = { version = "0.22", optional = true }
base64 = { version = "0.21", optional = true }
rand = { version = "0.8", optional = true }
sha1 = { version = "0.10", optional = true }
# openssl/rustls
actix-tls = { version = "3.3", default-features = false, optional = true }
actix-tls = { version = "3.1", default-features = false, optional = true }
# compress-*
brotli = { version = "3.3.3", optional = true }
@ -121,40 +108,35 @@ zstd = { version = "0.13", optional = true }
[dev-dependencies]
actix-http-test = { version = "3", features = ["openssl"] }
actix-server = "2"
actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22-webpki-roots"] }
actix-tls = { version = "3.1", features = ["openssl"] }
actix-web = "4"
async-stream = "0.3"
criterion = { version = "0.5", features = ["html_reports"] }
divan = "0.1.8"
env_logger = "0.11"
env_logger = "0.10"
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
memchr = "2.4"
once_cell = "1.9"
rcgen = "0.12"
rcgen = "0.11"
regex = "1.3"
rustversion = "1"
rustls-pemfile = "2"
rustls-pemfile = "1"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls_022 = { package = "rustls", version = "0.22" }
tls-rustls_021 = { package = "rustls", version = "0.21" }
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
[[example]]
name = "ws"
required-features = ["ws", "rustls-0_22"]
required-features = ["ws", "rustls-0_21"]
[[example]]
name = "tls_rustls"
required-features = ["http2", "rustls-0_22"]
required-features = ["http2", "rustls-0_21"]
[[bench]]
name = "response-body-compression"
harness = false
required-features = ["compress-brotli", "compress-gzip", "compress-zstd"]
[[bench]]
name = "date-formatting"
harness = false

View File

@ -5,16 +5,21 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.6.0)](https://docs.rs/actix-http/3.6.0)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.5.1)](https://docs.rs/actix-http/3.5.1)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.6.0/status.svg)](https://deps.rs/crate/actix-http/3.6.0)
[![dependency status](https://deps.rs/crate/actix-http/3.5.1/status.svg)](https://deps.rs/crate/actix-http/3.5.1)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-http)
- Minimum Supported Rust Version (MSRV): 1.68
## Examples
```rust

View File

@ -1,20 +0,0 @@
use std::time::SystemTime;
use actix_http::header::HttpDate;
use divan::{black_box, AllocProfiler, Bencher};
#[global_allocator]
static ALLOC: AllocProfiler = AllocProfiler::system();
#[divan::bench]
fn date_formatting(b: Bencher<'_, '_>) {
let now = SystemTime::now();
b.bench(|| {
black_box(HttpDate::from(black_box(now)).to_string());
})
}
fn main() {
divan::main();
}

View File

@ -8,7 +8,7 @@
use std::{convert::Infallible, io};
use actix_http::{body::BodyStream, HttpService, Request, Response, StatusCode};
use actix_http::{HttpService, Request, Response, StatusCode};
use actix_server::Server;
#[tokio::main(flavor = "current_thread")]
@ -19,12 +19,7 @@ async fn main() -> io::Result<()> {
.bind("h2c-detect", ("127.0.0.1", 8080), || {
HttpService::build()
.finish(|_req: Request| async move {
Ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(
futures_util::stream::iter([
Ok::<_, String>("123".into()),
Err("wertyuikmnbvcxdfty6t".to_owned()),
]),
)))
Ok::<_, Infallible>(Response::build(StatusCode::OK).body("Hello!"))
})
.tcp_auto_h2c()
})?

View File

@ -12,7 +12,7 @@
//! Protocol: HTTP/1.1
//! ```
extern crate tls_rustls_022 as rustls;
extern crate tls_rustls_021 as rustls;
use std::io;
@ -36,7 +36,7 @@ async fn main() -> io::Result<()> {
);
ok::<_, Error>(Response::ok().set_body(body))
})
.rustls_0_22(rustls_config())
.rustls_021(rustls_config())
})?
.run()
.await
@ -51,18 +51,16 @@ fn rustls_config() -> rustls::ServerConfig {
let key_file = &mut io::BufReader::new(key_file.as_bytes());
let cert_chain = rustls_pemfile::certs(cert_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
.unwrap()
.into_iter()
.map(rustls::Certificate)
.collect();
let mut keys = rustls_pemfile::pkcs8_private_keys(key_file).unwrap();
let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.with_single_cert(cert_chain, rustls::PrivateKey(keys.remove(0)))
.unwrap();
const H1_ALPN: &[u8] = b"http/1.1";

View File

@ -1,7 +1,7 @@
//! Sets up a WebSocket server over TCP and TLS.
//! Sends a heartbeat message every 4 seconds but does not respond to any incoming frames.
extern crate tls_rustls_022 as rustls;
extern crate tls_rustls_021 as rustls;
use std::{
io,
@ -30,7 +30,7 @@ async fn main() -> io::Result<()> {
.bind("tls", ("127.0.0.1", 8443), || {
HttpService::build()
.finish(handler)
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})?
.run()
.await
@ -85,6 +85,7 @@ impl Stream for Heartbeat {
fn tls_config() -> rustls::ServerConfig {
use std::io::BufReader;
use rustls::{Certificate, PrivateKey};
use rustls_pemfile::{certs, pkcs8_private_keys};
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
@ -94,17 +95,17 @@ fn tls_config() -> rustls::ServerConfig {
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).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = certs(cert_file)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
let mut config = rustls::ServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
.unwrap();
config.alpn_protocols.push(b"http/1.1".to_vec());

View File

@ -531,6 +531,7 @@ where
mod tests {
use actix_rt::pin;
use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut};
use futures_util::stream;
use super::*;

View File

@ -28,7 +28,7 @@ impl Date {
fn update(&mut self) {
self.pos = 0;
write!(self, "{}", httpdate::HttpDate::from(SystemTime::now())).unwrap();
write!(self, "{}", httpdate::fmt_http_date(SystemTime::now())).unwrap();
}
}

View File

@ -399,7 +399,9 @@ pub enum ContentTypeError {
#[cfg(test)]
mod tests {
use http::Error as HttpError;
use std::io;
use http::{Error as HttpError, StatusCode};
use super::*;

View File

@ -198,6 +198,9 @@ impl Encoder<Message<(Response<()>, BodySize)>> for Codec {
#[cfg(test)]
mod tests {
use bytes::BytesMut;
use http::Method;
use super::*;
use crate::HttpMessage as _;

View File

@ -563,8 +563,15 @@ impl Decoder for PayloadDecoder {
#[cfg(test)]
mod tests {
use bytes::{Bytes, BytesMut};
use http::{Method, Version};
use super::*;
use crate::{header::SET_COOKIE, HttpMessage as _};
use crate::{
error::ParseError,
header::{HeaderName, SET_COOKIE},
HttpMessage as _,
};
impl PayloadType {
pub(crate) fn unwrap(self) -> PayloadDecoder {

View File

@ -512,10 +512,8 @@ where
}
Poll::Ready(Some(Err(err))) => {
let err = err.into();
tracing::error!("Response payload stream error: {err:?}");
this.flags.insert(Flags::FINISHED);
return Err(DispatchError::Body(err));
return Err(DispatchError::Body(err.into()));
}
Poll::Pending => return Ok(PollResponse::DoNothing),
@ -551,7 +549,6 @@ where
}
Poll::Ready(Some(Err(err))) => {
tracing::error!("Response payload stream error: {err:?}");
this.flags.insert(Flags::FINISHED);
return Err(DispatchError::Body(
Error::new_body().with_cause(err).into(),
@ -706,7 +703,7 @@ where
req.head_mut().peer_addr = *this.peer_addr;
req.conn_data = this.conn_data.clone();
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
match this.codec.message_type() {
// request has no payload

View File

@ -153,7 +153,7 @@ mod openssl {
}
#[cfg(feature = "rustls-0_20")]
mod rustls_0_20 {
mod rustls_020 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@ -214,7 +214,7 @@ mod rustls_0_20 {
}
#[cfg(feature = "rustls-0_21")]
mod rustls_0_21 {
mod rustls_021 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@ -274,67 +274,6 @@ mod rustls_0_21 {
}
}
#[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<S, B, X, U> H1Service<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
B: MessageBody,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
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<io::Error, DispatchError>,
InitError = (),
> {
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B, X, U> H1Service<T, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,

View File

@ -4,7 +4,7 @@ use std::{
future::Future,
marker::PhantomData,
net,
pin::{pin, Pin},
pin::Pin,
rc::Rc,
task::{Context, Poll},
};
@ -20,6 +20,7 @@ use h2::{
Ping, PingPong,
};
use pin_project_lite::pin_project;
use tracing::{error, trace, warn};
use crate::{
body::{BodySize, BoxBody, MessageBody},
@ -126,7 +127,7 @@ where
head.headers = parts.headers.into();
head.peer_addr = this.peer_addr;
req.conn_data = this.conn_data.clone();
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
let fut = this.flow.service.call(req);
let config = this.config.clone();
@ -146,13 +147,11 @@ where
if let Err(err) = res {
match err {
DispatchError::SendResponse(err) => {
tracing::trace!("Error sending response: {err:?}");
}
DispatchError::SendData(err) => {
tracing::warn!("Send data error: {err:?}");
trace!("Error sending HTTP/2 response: {:?}", err)
}
DispatchError::SendData(err) => warn!("{:?}", err),
DispatchError::ResponseBody(err) => {
tracing::error!("Response payload stream error: {err:?}");
error!("Response payload stream error: {:?}", err)
}
}
}
@ -229,9 +228,9 @@ where
return Ok(());
}
let mut body = pin!(body);
// poll response body and send chunks to client
actix_rt::pin!(body);
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;

View File

@ -141,7 +141,7 @@ mod openssl {
}
#[cfg(feature = "rustls-0_20")]
mod rustls_0_20 {
mod rustls_020 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@ -192,7 +192,7 @@ mod rustls_0_20 {
}
#[cfg(feature = "rustls-0_21")]
mod rustls_0_21 {
mod rustls_021 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@ -242,57 +242,6 @@ mod rustls_0_21 {
}
}
#[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<S, B> H2Service<TlsStream<TcpStream>, S, B>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
{
/// Create Rustls v0.22 based service.
pub fn rustls_0_22(
self,
mut config: ServerConfig,
) -> impl ServiceFactory<
TcpStream,
Config = (),
Response = (),
Error = TlsError<io::Error, DispatchError>,
InitError = S::InitError,
> {
let mut protos = vec![b"h2".to_vec()];
protos.extend_from_slice(&config.alpn_protocols);
config.alpn_protocols = protos;
Acceptor::new(config)
.map_init_err(|_| {
unreachable!("TLS acceptor service factory does not error on init")
})
.map_err(TlsError::into_service_error)
.map(|io: TlsStream<TcpStream>| {
let peer_addr = io.get_ref().0.peer_addr().ok();
(io, peer_addr)
})
.and_then(self.map_err(TlsError::Service))
}
}
}
impl<T, S, B> ServiceFactory<(T, Option<net::SocketAddr>)> for H2Service<T, S, B>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,

View File

@ -650,13 +650,6 @@ impl From<HeaderMap> for http::HeaderMap {
}
}
/// Convert our `&HeaderMap` to a `http::HeaderMap`.
impl From<&HeaderMap> for http::HeaderMap {
fn from(map: &HeaderMap) -> Self {
map.to_owned().into()
}
}
/// Iterator over removed, owned values with the same associated name.
///
/// Returned from methods that remove or replace items. See [`HeaderMap::insert`]

View File

@ -24,7 +24,8 @@ impl FromStr for HttpDate {
impl fmt::Display for HttpDate {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
httpdate::HttpDate::from(self.0).fmt(f)
let date_str = httpdate::fmt_http_date(self.0);
f.write_str(&date_str)
}
}
@ -36,7 +37,7 @@ impl TryIntoHeaderValue for HttpDate {
let mut wrt = MutWriter(&mut buf);
// unwrap: date output is known to be well formed and of known length
write!(wrt, "{}", self).unwrap();
write!(wrt, "{}", httpdate::fmt_http_date(self.0)).unwrap();
HeaderValue::from_maybe_shared(buf.split().freeze())
}

View File

@ -80,18 +80,18 @@ mod tests {
#[test]
fn comma_delimited_parsing() {
let headers = [];
let headers = vec![];
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
assert_eq!(res, vec![0; 0]);
let headers = [
let headers = vec![
HeaderValue::from_static("1, 2"),
HeaderValue::from_static("3,4"),
];
let res: Vec<usize> = from_comma_delimited(headers.iter()).unwrap();
assert_eq!(res, vec![1, 2, 3, 4]);
let headers = [
let headers = vec![
HeaderValue::from_static(""),
HeaderValue::from_static(","),
HeaderValue::from_static(" "),

View File

@ -58,12 +58,7 @@ pub mod ws;
#[allow(deprecated)]
pub use self::payload::PayloadStream;
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
pub use self::service::TlsAcceptorConfig;
pub use self::{
builder::HttpServiceBuilder,

View File

@ -5,7 +5,7 @@
use std::cell::RefCell;
thread_local! {
static NOTIFY_DROPPED: RefCell<Option<bool>> = const { RefCell::new(None) };
static NOTIFY_DROPPED: RefCell<Option<bool>> = RefCell::new(None);
}
/// Check if the spawned task is dropped.

View File

@ -241,23 +241,13 @@ where
}
/// Configuration options used when accepting TLS connection.
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
#[derive(Debug, Default)]
pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option<std::time::Duration>,
}
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
#[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 {
@ -363,12 +353,12 @@ mod openssl {
}
#[cfg(feature = "rustls-0_20")]
mod rustls_0_20 {
mod rustls_020 {
use std::io;
use actix_service::ServiceFactoryExt as _;
use actix_tls::accept::{
rustls_0_20::{reexports::ServerConfig, Acceptor, TlsStream},
rustls::{reexports::ServerConfig, Acceptor, TlsStream},
TlsError,
};
@ -399,7 +389,7 @@ mod rustls_0_20 {
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.20 based service.
/// Create Rustls based service.
pub fn rustls(
self,
config: ServerConfig,
@ -413,7 +403,7 @@ mod rustls_0_20 {
self.rustls_with_config(config, TlsAcceptorConfig::default())
}
/// Create Rustls v0.20 based service with custom TLS acceptor configuration.
/// Create Rustls based service with custom TLS acceptor configuration.
pub fn rustls_with_config(
self,
mut config: ServerConfig,
@ -459,7 +449,7 @@ mod rustls_0_20 {
}
#[cfg(feature = "rustls-0_21")]
mod rustls_0_21 {
mod rustls_021 {
use std::io;
use actix_service::ServiceFactoryExt as _;
@ -495,7 +485,7 @@ mod rustls_0_21 {
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
{
/// Create Rustls v0.21 based service.
/// Create Rustls based service.
pub fn rustls_021(
self,
config: ServerConfig,
@ -509,7 +499,7 @@ mod rustls_0_21 {
self.rustls_021_with_config(config, TlsAcceptorConfig::default())
}
/// Create Rustls v0.21 based service with custom TLS acceptor configuration.
/// Create Rustls based service with custom TLS acceptor configuration.
pub fn rustls_021_with_config(
self,
mut config: ServerConfig,
@ -554,102 +544,6 @@ mod rustls_0_21 {
}
}
#[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<S, B, X, U> HttpService<TlsStream<TcpStream>, S, B, X, U>
where
S: ServiceFactory<Request, Config = ()>,
S::Future: 'static,
S::Error: Into<Response<BoxBody>> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
B: MessageBody + 'static,
X: ServiceFactory<Request, Config = (), Response = Request>,
X::Future: 'static,
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TlsStream<TcpStream>, h1::Codec>),
Config = (),
Response = (),
>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
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<io::Error, DispatchError>,
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<io::Error, DispatchError>,
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<TcpStream>| 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<T, S, B, X, U> ServiceFactory<(T, Protocol, Option<net::SocketAddr>)>
for HttpService<T, S, B, X, U>
where

View File

@ -221,7 +221,7 @@ pub fn handshake_response(req: &RequestHead) -> ResponseBuilder {
#[cfg(test)]
mod tests {
use super::*;
use crate::{header, test::TestRequest};
use crate::{header, test::TestRequest, Method};
#[test]
fn test_handshake() {

View File

@ -1,4 +1,7 @@
use std::fmt;
use std::{
convert::{From, Into},
fmt,
};
use base64::prelude::*;
use tracing::error;

View File

@ -1,6 +1,6 @@
#![cfg(feature = "rustls-0_22")]
#![cfg(feature = "rustls-0_21")]
extern crate tls_rustls_022 as rustls;
extern crate tls_rustls_021 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_22::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};
use futures_core::{ready, Stream};
use futures_util::stream::once;
use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
use rustls::{Certificate, PrivateKey, ServerConfig as RustlsServerConfig, ServerName};
use rustls_pemfile::{certs, pkcs8_private_keys};
async fn load_body<S>(stream: S) -> Result<BytesMut, PayloadError>
@ -59,17 +59,17 @@ fn tls_config() -> RustlsServerConfig {
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).collect::<Result<Vec<_>, _>>().unwrap();
let mut keys = pkcs8_private_keys(key_file)
.collect::<Result<Vec<_>, _>>()
.unwrap();
let cert_chain = certs(cert_file)
.unwrap()
.into_iter()
.map(Certificate)
.collect();
let mut keys = pkcs8_private_keys(key_file).unwrap();
let mut config = RustlsServerConfig::builder()
.with_safe_defaults()
.with_no_client_auth()
.with_single_cert(
cert_chain,
rustls::pki_types::PrivateKeyDer::Pkcs8(keys.remove(0)),
)
.with_single_cert(cert_chain, PrivateKey(keys.remove(0)))
.unwrap();
config.alpn_protocols.push(HTTP1_1_ALPN_PROTOCOL.to_vec());
@ -83,6 +83,7 @@ pub fn get_negotiated_alpn_protocol(
client_alpn_protocol: &[u8],
) -> Option<Vec<u8>> {
let mut config = rustls::ClientConfig::builder()
.with_safe_defaults()
.with_root_certificates(webpki_roots_cert_store())
.with_no_client_auth();
@ -108,7 +109,7 @@ async fn h1() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h1(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -122,7 +123,7 @@ async fn h2() -> io::Result<()> {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Error>(Response::ok()))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -140,7 +141,7 @@ async fn h1_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_11);
ok::<_, Error>(Response::ok())
})
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -158,7 +159,7 @@ async fn h2_1() -> io::Result<()> {
assert_eq!(req.version(), Version::HTTP_2);
ok::<_, Error>(Response::ok())
})
.rustls_0_22_with_config(
.rustls_021_with_config(
tls_config(),
TlsAcceptorConfig::default().handshake_timeout(Duration::from_secs(5)),
)
@ -179,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_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -205,7 +206,7 @@ async fn h2_content_length() {
];
ok::<_, Infallible>(Response::new(statuses[indx]))
})
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -277,7 +278,7 @@ async fn h2_headers() {
}
ok::<_, Infallible>(config.body(data.clone()))
})
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -316,7 +317,7 @@ async fn h2_body2() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -333,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_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -359,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_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -384,7 +385,7 @@ async fn h2_head_binary2() {
let srv = test_server(move || {
HttpService::build()
.h2(|_| ok::<_, Infallible>(Response::ok().set_body(STR)))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -410,7 +411,7 @@ async fn h2_body_length() {
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
)
})
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -434,7 +435,7 @@ async fn h2_body_chunked_explicit() {
.body(BodyStream::new(body)),
)
})
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -463,7 +464,7 @@ async fn h2_response_http_error_handling() {
)
}))
}))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -493,7 +494,7 @@ async fn h2_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -510,7 +511,7 @@ async fn h1_service_error() {
let mut srv = test_server(move || {
HttpService::build()
.h1(|_| err::<Response<BoxBody>, _>(BadRequest))
.rustls_0_22(tls_config())
.rustls_021(tls_config())
})
.await;
@ -533,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_0_22(config)
.rustls_021(config)
})
.await;
@ -555,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_0_22(config)
.rustls_021(config)
})
.await;
@ -581,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_0_22(config)
.rustls_021(config)
})
.await;

View File

@ -2,8 +2,6 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.6.1
- Update `syn` dependency to `2`.

View File

@ -6,7 +6,7 @@
[![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.72+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart-derive.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-multipart-derive/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart-derive/0.6.1)
@ -14,3 +14,8 @@
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart-derive)
- Minimum Supported Rust Version (MSRV): 1.68

View File

@ -1,4 +1,4 @@
#[rustversion::stable(1.72)] // MSRV
#[rustversion::stable(1.68)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();

View File

@ -2,9 +2,6 @@
## Unreleased
- 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.

View File

@ -35,7 +35,6 @@ local-waker = "0.1"
log = "0.4"
memchr = "2.5"
mime = "0.3"
rand = "0.8"
serde = "1"
serde_json = "1"
serde_plain = "1"
@ -47,9 +46,7 @@ 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"

View File

@ -6,7 +6,7 @@
[![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.72+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1)
@ -14,3 +14,8 @@
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-multipart)
- Minimum Supported Rust Version (MSRV): 1.68

View File

@ -131,13 +131,14 @@ impl Default for JsonConfig {
#[cfg(test)]
mod tests {
use std::collections::HashMap;
use std::{collections::HashMap, io::Cursor};
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,
};
@ -154,8 +155,6 @@ 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(|| {
@ -164,16 +163,10 @@ mod tests {
.app_data(JsonConfig::default().validate_content_type(false))
});
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);
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);
}
#[actix_rt::test]
@ -185,27 +178,17 @@ mod tests {
});
// Deny because wrong content type
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);
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);
// Allow because correct content type
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);
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);
}
}

View File

@ -13,14 +13,11 @@ extern crate self as actix_multipart;
mod error;
mod extractor;
pub mod form;
mod server;
pub mod test;
pub mod form;
pub use self::{
error::MultipartError,
server::{Field, Multipart},
test::{
create_form_data_payload_and_headers, create_form_data_payload_and_headers_with_boundary,
},
};

View File

@ -863,15 +863,13 @@ mod tests {
test::TestRequest,
FromRequest,
};
use bytes::BufMut as _;
use bytes::Bytes;
use futures_util::{future::lazy, StreamExt as _};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
use super::*;
const BOUNDARY: &str = "abbc761f78ff4d7cb7573b5a23f96ef0";
#[actix_rt::test]
async fn test_boundary() {
let headers = HeaderMap::new();
@ -967,26 +965,6 @@ mod tests {
}
fn create_simple_request_with_header() -> (Bytes, HeaderMap) {
let (body, headers) = crate::test::create_form_data_payload_and_headers_with_boundary(
BOUNDARY,
"file",
Some("fn.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"data"),
);
let mut buf = BytesMut::with_capacity(body.len() + 14);
// add junk before form to test pre-boundary data rejection
buf.put("testasdadsad\r\n".as_bytes());
buf.put(body);
(buf.freeze(), headers)
}
// TODO: use test utility when multi-file support is introduced
fn create_double_request_with_header() -> (Bytes, HeaderMap) {
let bytes = Bytes::from(
"testasdadsad\r\n\
--abbc761f78ff4d7cb7573b5a23f96ef0\r\n\
@ -1012,7 +990,7 @@ mod tests {
#[actix_rt::test]
async fn test_multipart_no_end_crlf() {
let (sender, payload) = create_stream();
let (mut bytes, headers) = create_double_request_with_header();
let (mut bytes, headers) = create_simple_request_with_header();
let bytes_stripped = bytes.split_to(bytes.len()); // strip crlf
sender.send(Ok(bytes_stripped)).unwrap();
@ -1039,7 +1017,7 @@ mod tests {
#[actix_rt::test]
async fn test_multipart() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_double_request_with_header();
let (bytes, headers) = create_simple_request_with_header();
sender.send(Ok(bytes)).unwrap();
@ -1102,7 +1080,7 @@ mod tests {
#[actix_rt::test]
async fn test_stream() {
let (bytes, headers) = create_double_request_with_header();
let (bytes, headers) = create_simple_request_with_header();
let payload = SlowStream::new(bytes);
let mut multipart = Multipart::new(&headers, payload);
@ -1341,7 +1319,7 @@ mod tests {
#[actix_rt::test]
async fn test_drop_field_awaken_multipart() {
let (sender, payload) = create_stream();
let (bytes, headers) = create_double_request_with_header();
let (bytes, headers) = create_simple_request_with_header();
sender.send(Ok(bytes)).unwrap();
drop(sender); // eof

View File

@ -1,217 +0,0 @@
use actix_web::http::header::{self, HeaderMap};
use bytes::{BufMut as _, Bytes, BytesMut};
use mime::Mime;
use rand::{
distributions::{Alphanumeric, DistString as _},
thread_rng,
};
const CRLF: &[u8] = b"\r\n";
const CRLF_CRLF: &[u8] = b"\r\n\r\n";
const HYPHENS: &[u8] = b"--";
const BOUNDARY_PREFIX: &str = "------------------------";
/// Constructs a `multipart/form-data` payload from bytes and metadata.
///
/// Returned header map can be extended or merged with existing headers.
///
/// Multipart boundary used is a random alphanumeric string.
///
/// # Examples
///
/// ```
/// use actix_multipart::test::create_form_data_payload_and_headers;
/// use actix_web::test::TestRequest;
/// use bytes::Bytes;
/// use memchr::memmem::find;
///
/// let (body, headers) = create_form_data_payload_and_headers(
/// "foo",
/// Some("lorem.txt".to_owned()),
/// Some(mime::TEXT_PLAIN_UTF_8),
/// Bytes::from_static(b"Lorem ipsum."),
/// );
///
/// assert!(find(&body, b"foo").is_some());
/// assert!(find(&body, b"lorem.txt").is_some());
/// assert!(find(&body, b"text/plain; charset=utf-8").is_some());
/// assert!(find(&body, b"Lorem ipsum.").is_some());
///
/// let req = TestRequest::default();
///
/// // merge header map into existing test request and set multipart body
/// let req = headers
/// .into_iter()
/// .fold(req, |req, hdr| req.insert_header(hdr))
/// .set_payload(body)
/// .to_http_request();
///
/// assert!(
/// req.headers()
/// .get("content-type")
/// .unwrap()
/// .to_str()
/// .unwrap()
/// .starts_with("multipart/form-data; boundary=\"")
/// );
/// ```
pub fn create_form_data_payload_and_headers(
name: &str,
filename: Option<String>,
content_type: Option<Mime>,
file: Bytes,
) -> (Bytes, HeaderMap) {
let boundary = Alphanumeric.sample_string(&mut thread_rng(), 32);
create_form_data_payload_and_headers_with_boundary(
&boundary,
name,
filename,
content_type,
file,
)
}
/// Constructs a `multipart/form-data` payload from bytes and metadata with a fixed boundary.
///
/// See [`create_form_data_payload_and_headers`] for more details.
pub fn create_form_data_payload_and_headers_with_boundary(
boundary: &str,
name: &str,
filename: Option<String>,
content_type: Option<Mime>,
file: Bytes,
) -> (Bytes, HeaderMap) {
let mut buf = BytesMut::with_capacity(file.len() + 128);
let boundary_str = [BOUNDARY_PREFIX, boundary].concat();
let boundary = boundary_str.as_bytes();
buf.put(HYPHENS);
buf.put(boundary);
buf.put(CRLF);
buf.put(format!("Content-Disposition: form-data; name=\"{name}\"").as_bytes());
if let Some(filename) = filename {
buf.put(format!("; filename=\"{filename}\"").as_bytes());
}
buf.put(CRLF);
if let Some(ct) = content_type {
buf.put(format!("Content-Type: {ct}").as_bytes());
buf.put(CRLF);
}
buf.put(format!("Content-Length: {}", file.len()).as_bytes());
buf.put(CRLF_CRLF);
buf.put(file);
buf.put(CRLF);
buf.put(HYPHENS);
buf.put(boundary);
buf.put(HYPHENS);
buf.put(CRLF);
let mut headers = HeaderMap::new();
headers.insert(
header::CONTENT_TYPE,
format!("multipart/form-data; boundary=\"{boundary_str}\"")
.parse()
.unwrap(),
);
(buf.freeze(), headers)
}
#[cfg(test)]
mod tests {
use std::convert::Infallible;
use futures_util::stream;
use super::*;
fn find_boundary(headers: &HeaderMap) -> String {
headers
.get("content-type")
.unwrap()
.to_str()
.unwrap()
.parse::<mime::Mime>()
.unwrap()
.get_param(mime::BOUNDARY)
.unwrap()
.as_str()
.to_owned()
}
#[test]
fn wire_format() {
let (pl, headers) = create_form_data_payload_and_headers_with_boundary(
"qWeRtYuIoP",
"foo",
None,
None,
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
assert_eq!(
find_boundary(&headers),
"------------------------qWeRtYuIoP",
);
assert_eq!(
std::str::from_utf8(&pl).unwrap(),
"--------------------------qWeRtYuIoP\r\n\
Content-Disposition: form-data; name=\"foo\"\r\n\
Content-Length: 26\r\n\
\r\n\
Lorem ipsum dolor\n\
sit ame.\r\n\
--------------------------qWeRtYuIoP--\r\n",
);
let (pl, _headers) = create_form_data_payload_and_headers_with_boundary(
"qWeRtYuIoP",
"foo",
Some("Lorem.txt".to_owned()),
Some(mime::TEXT_PLAIN_UTF_8),
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
assert_eq!(
std::str::from_utf8(&pl).unwrap(),
"--------------------------qWeRtYuIoP\r\n\
Content-Disposition: form-data; name=\"foo\"; filename=\"Lorem.txt\"\r\n\
Content-Type: text/plain; charset=utf-8\r\n\
Content-Length: 26\r\n\
\r\n\
Lorem ipsum dolor\n\
sit ame.\r\n\
--------------------------qWeRtYuIoP--\r\n",
);
}
/// Test using an external library to prevent the two-wrongs-make-a-right class of errors.
#[actix_web::test]
async fn ecosystem_compat() {
let (pl, headers) = create_form_data_payload_and_headers(
"foo",
None,
None,
Bytes::from_static(b"Lorem ipsum dolor\nsit ame."),
);
let boundary = find_boundary(&headers);
let pl = stream::once(async { Ok::<_, Infallible>(pl) });
let mut form = multer::Multipart::new(pl, boundary);
let field = form.next_field().await.unwrap().unwrap();
assert_eq!(field.name().unwrap(), "foo");
assert_eq!(field.file_name(), None);
assert_eq!(field.content_type(), None);
assert!(field.bytes().await.unwrap().starts_with(b"Lorem"));
}
}

View File

@ -2,9 +2,6 @@
## Unreleased
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.5.2
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.

View File

@ -17,16 +17,12 @@ name = "actix_router"
path = "src/lib.rs"
[features]
default = ["http", "unicode"]
http = ["dep:http"]
unicode = ["dep:regex"]
default = ["http"]
[dependencies]
bytestring = ">=0.1.5, <2"
cfg-if = "1"
http = { version = "0.2.7", optional = true }
regex = { version = "1.5", optional = true }
regex-lite = "0.1"
regex = "1.5"
serde = "1"
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
@ -39,7 +35,6 @@ percent-encoding = "2.1"
[[bench]]
name = "router"
harness = false
required-features = ["unicode"]
[[bench]]
name = "quoter"

View File

@ -4,7 +4,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-router?label=latest)](https://crates.io/crates/actix-router)
[![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.2)](https://docs.rs/actix-router/0.5.2)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-router.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-router/0.5.2/status.svg)](https://deps.rs/crate/actix-router/0.5.2)

View File

@ -500,10 +500,10 @@ impl<'de> de::VariantAccess<'de> for UnitVariant {
#[cfg(test)]
mod tests {
use serde::Deserialize;
use serde::{de, Deserialize};
use super::*;
use crate::{router::Router, ResourceDef};
use crate::{path::Path, router::Router, ResourceDef};
#[derive(Deserialize)]
struct MyStruct {

View File

@ -10,7 +10,6 @@ mod de;
mod path;
mod pattern;
mod quoter;
mod regex_set;
mod resource;
mod resource_path;
mod router;

View File

@ -1,66 +0,0 @@
//! Abstraction over `regex` and `regex-lite` depending on whether we have `unicode` crate feature
//! enabled.
use cfg_if::cfg_if;
#[cfg(feature = "unicode")]
pub(crate) use regex::{escape, Regex};
#[cfg(not(feature = "unicode"))]
pub(crate) use regex_lite::{escape, Regex};
#[cfg(feature = "unicode")]
#[derive(Debug, Clone)]
pub(crate) struct RegexSet(regex::RegexSet);
#[cfg(not(feature = "unicode"))]
#[derive(Debug, Clone)]
pub(crate) struct RegexSet(Vec<regex_lite::Regex>);
impl RegexSet {
/// Create a new regex set.
///
/// # Panics
///
/// Panics if any path patterns are malformed.
pub(crate) fn new(re_set: Vec<String>) -> Self {
cfg_if! {
if #[cfg(feature = "unicode")] {
Self(regex::RegexSet::new(re_set).unwrap())
} else {
Self(re_set.iter().map(|re| Regex::new(re).unwrap()).collect())
}
}
}
/// Create a new empty regex set.
pub(crate) fn empty() -> Self {
cfg_if! {
if #[cfg(feature = "unicode")] {
Self(regex::RegexSet::empty())
} else {
Self(Vec::new())
}
}
}
/// Returns true if regex set matches `path`.
pub(crate) fn is_match(&self, path: &str) -> bool {
cfg_if! {
if #[cfg(feature = "unicode")] {
self.0.is_match(path)
} else {
self.0.iter().any(|re| re.is_match(path))
}
}
}
/// Returns index within `path` of first match.
pub(crate) fn first_match_idx(&self, path: &str) -> Option<usize> {
cfg_if! {
if #[cfg(feature = "unicode")] {
self.0.matches(path).into_iter().next()
} else {
Some(self.0.iter().enumerate().find(|(_, re)| re.is_match(path))?.0)
}
}
}
}

View File

@ -5,13 +5,10 @@ use std::{
mem,
};
use regex::{escape, Regex, RegexSet};
use tracing::error;
use crate::{
path::PathItem,
regex_set::{escape, Regex, RegexSet},
IntoPatterns, Patterns, Resource, ResourcePath,
};
use crate::{path::PathItem, IntoPatterns, Patterns, Resource, ResourcePath};
const MAX_DYNAMIC_SEGMENTS: usize = 16;
@ -236,7 +233,7 @@ enum PatternSegment {
Var(String),
}
#[derive(Debug, Clone)]
#[derive(Clone, Debug)]
#[allow(clippy::large_enum_variant)]
enum PatternType {
/// Single constant/literal segment.
@ -606,7 +603,7 @@ impl ResourceDef {
PatternType::Dynamic(re, _) => Some(re.captures(path)?[1].len()),
PatternType::DynamicSet(re, params) => {
let idx = re.first_match_idx(path)?;
let idx = re.matches(path).into_iter().next()?;
let (ref pattern, _) = params[idx];
Some(pattern.captures(path)?[1].len())
}
@ -709,7 +706,7 @@ impl ResourceDef {
PatternType::DynamicSet(re, params) => {
let path = path.unprocessed();
let (pattern, names) = match re.first_match_idx(path) {
let (pattern, names) = match re.matches(path).into_iter().next() {
Some(idx) => &params[idx],
_ => return false,
};
@ -873,7 +870,7 @@ impl ResourceDef {
}
}
let pattern_re_set = RegexSet::new(re_set);
let pattern_re_set = RegexSet::new(re_set).unwrap();
let segments = segments.unwrap_or_default();
(

View File

@ -2,12 +2,6 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 0.1.3
- Add `TestServerConfig::rustls_0_22()` method for Rustls v0.22 support behind new `rustls-0_22` crate feature.
## 0.1.2
- Add `TestServerConfig::rustls_021()` method for Rustls v0.21 support behind new `rustls-0_21` crate feature.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-test"
version = "0.1.3"
version = "0.1.2"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@ -27,21 +27,19 @@ rustls = ["rustls-0_20"]
rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"]
# TLS via Rustls v0.21
rustls-0_21 = ["tls-rustls-0_21", "actix-http/rustls-0_21", "awc/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["tls-rustls-0_22", "actix-http/rustls-0_22", "awc/rustls-0_22-webpki-roots"]
# TLS via OpenSSL
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies]
actix-codec = "0.5"
actix-http = "3.6"
actix-http = "3"
actix-http-test = "3"
actix-rt = "2.1"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4.5", default-features = false, features = ["cookies"] }
awc = { version = "3.4", default-features = false, features = ["cookies"] }
actix-web = { version = "4", default-features = false, features = ["cookies"] }
awc = { version = "3", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.17", default-features = false, features = ["std"] }
futures-util = { version = "0.3.17", default-features = false, features = [] }
@ -52,5 +50,4 @@ serde_urlencoded = "0.7"
tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true }
tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true }
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
tokio = { version = "1.24.2", features = ["sync"] }

View File

@ -143,8 +143,6 @@ where
StreamType::Rustls020(_) => true,
#[cfg(feature = "rustls-0_21")]
StreamType::Rustls021(_) => true,
#[cfg(feature = "rustls-0_22")]
StreamType::Rustls022(_) => true,
};
// run server in separate orphaned thread
@ -329,48 +327,6 @@ where
.rustls_021(config.clone())
}),
},
#[cfg(feature = "rustls-0_22")]
StreamType::Rustls022(config) => match cfg.tp {
HttpVer::Http1 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.h1(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_22(config.clone())
}),
HttpVer::Http2 => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.h2(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_22(config.clone())
}),
HttpVer::Both => builder.listen("test", tcp, move || {
let app_cfg =
AppConfig::__priv_test_new(false, local_addr.to_string(), local_addr);
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
HttpService::build()
.client_request_timeout(timeout)
.finish(map_config(fac, move |_| app_cfg.clone()))
.rustls_0_22(config.clone())
}),
},
}
.expect("test server could not be created");
@ -445,8 +401,6 @@ enum StreamType {
Rustls020(tls_rustls_0_20::ServerConfig),
#[cfg(feature = "rustls-0_21")]
Rustls021(tls_rustls_0_21::ServerConfig),
#[cfg(feature = "rustls-0_22")]
Rustls022(tls_rustls_0_22::ServerConfig),
}
/// Create default test server config.
@ -470,7 +424,7 @@ impl Default for TestServerConfig {
}
impl TestServerConfig {
/// Constructs default server configuration.
/// Create default server configuration
pub(crate) fn new() -> TestServerConfig {
TestServerConfig {
tp: HttpVer::Both,
@ -481,63 +435,40 @@ impl TestServerConfig {
}
}
/// Accepts HTTP/1.1 only.
/// Accept HTTP/1.1 only.
pub fn h1(mut self) -> Self {
self.tp = HttpVer::Http1;
self
}
/// Accepts HTTP/2 only.
/// Accept HTTP/2 only.
pub fn h2(mut self) -> Self {
self.tp = HttpVer::Http2;
self
}
/// Accepts secure connections via OpenSSL.
/// Accept secure connections via OpenSSL.
#[cfg(feature = "openssl")]
pub fn openssl(mut self, acceptor: openssl::ssl::SslAcceptor) -> Self {
self.stream = StreamType::Openssl(acceptor);
self
}
#[doc(hidden)]
#[deprecated(note = "Renamed to `rustls_0_20()`.")]
/// Accept secure connections via Rustls.
#[cfg(feature = "rustls-0_20")]
pub fn rustls(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
self.stream = StreamType::Rustls020(config);
self
}
/// Accepts secure connections via Rustls v0.20.
#[cfg(feature = "rustls-0_20")]
pub fn rustls_0_20(mut self, config: tls_rustls_0_20::ServerConfig) -> Self {
self.stream = StreamType::Rustls020(config);
self
}
#[doc(hidden)]
#[deprecated(note = "Renamed to `rustls_0_21()`.")]
/// Accept secure connections via Rustls.
#[cfg(feature = "rustls-0_21")]
pub fn rustls_021(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
self.stream = StreamType::Rustls021(config);
self
}
/// Accepts secure connections via Rustls v0.21.
#[cfg(feature = "rustls-0_21")]
pub fn rustls_0_21(mut self, config: tls_rustls_0_21::ServerConfig) -> Self {
self.stream = StreamType::Rustls021(config);
self
}
/// Accepts secure connections via Rustls v0.22.
#[cfg(feature = "rustls-0_22")]
pub fn rustls_0_22(mut self, config: tls_rustls_0_22::ServerConfig) -> Self {
self.stream = StreamType::Rustls022(config);
self
}
/// Sets client timeout for first request.
/// Set client timeout for first request.
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
self.client_request_timeout = dur;
self

View File

@ -2,10 +2,6 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.3.0
- Minimum supported Rust version (MSRV) is now 1.68 due to transitive `time` dependency.
## 4.2.0

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "4.3.0"
version = "4.2.0"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"]
@ -32,6 +32,6 @@ actix-test = "0.1"
awc = { version = "3", default-features = false }
actix-web = { version = "4", features = ["macros"] }
env_logger = "0.11"
env_logger = "0.10"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
mime = "0.3"

View File

@ -5,12 +5,17 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.3.0)](https://docs.rs/actix-web-actors/4.3.0)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.2.0)](https://docs.rs/actix-web-actors/4.2.0)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.3.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.3.0)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.2.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.2.0)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-actors)
- Minimum Supported Rust Version (MSRV): 1.68

View File

@ -248,11 +248,13 @@ where
mod tests {
use std::time::Duration;
use actix::Actor;
use actix_web::{
http::StatusCode,
test::{call_service, init_service, read_body, TestRequest},
web, App, HttpResponse,
};
use bytes::Bytes;
use super::*;

View File

@ -817,7 +817,10 @@ where
#[cfg(test)]
mod tests {
use actix_web::test::TestRequest;
use actix_web::{
http::{header, Method},
test::TestRequest,
};
use super::*;

View File

@ -2,7 +2,8 @@
## Unreleased
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.2.3
- Add a scope macro that takes a path
## 4.2.2

View File

@ -6,7 +6,7 @@
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.2.2)](https://docs.rs/actix-web-codegen/4.2.2)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![Version](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.2.2/status.svg)](https://deps.rs/crate/actix-web-codegen/4.2.2)
@ -15,6 +15,11 @@
<!-- prettier-ignore-end -->
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-web-codegen)
- Minimum Supported Rust Version (MSRV): 1.68
## Compile Testing
Uses the [`trybuild`] crate. All compile fail tests should include a stderr file generated by `trybuild`. See the [workflow section](https://github.com/dtolnay/trybuild#workflow) of the trybuild docs for info on how to do this.

View File

@ -254,10 +254,10 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
///
/// # Example
///
/// scope("/test")
/// ```rust
/// use actix_web_codegen::{scope};
///
/// #[scope("/test")]
/// const mod_inner: () = {
/// use actix_web::{HttpResponse, HttpRequest, Responder, put, head, connect, options, trace, patch, delete, web };
/// use actix_web::web::Json;
@ -320,7 +320,12 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
///};
/// ```
///
/// # Note
///
/// Internally the macro generates struct with name of scope (e.g. `mod_inner`).
/// And creates public module as `<name>_scope`.
///
#[proc_macro_attribute]
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
route::with_scope(args, input)
route::ScopeArgs::new(args, input).generate()
}

View File

@ -1,11 +1,10 @@
use std::collections::HashSet;
use std::{collections::HashSet, fmt};
use actix_router::ResourceDef;
use proc_macro::TokenStream;
use proc_macro2::{Span, TokenStream as TokenStream2};
use quote::{quote, ToTokens, TokenStreamExt};
use syn::Item::Fn;
use syn::{punctuated::Punctuated, Ident, ItemFn, LitStr, Path, Token}; // todo: cleanup
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
#[derive(Debug)]
pub struct RouteArgs {
@ -332,14 +331,14 @@ pub struct Route {
args: Vec<Args>,
/// AST of the handler function being annotated.
ast: ItemFn,
ast: syn::ItemFn,
/// The doc comment attributes to copy to generated struct, if any.
doc_attributes: Vec<syn::Attribute>,
}
impl Route {
pub fn new(args: RouteArgs, ast: ItemFn, method: Option<MethodType>) -> syn::Result<Self> {
pub fn new(args: RouteArgs, ast: syn::ItemFn, method: Option<MethodType>) -> syn::Result<Self> {
let name = ast.sig.ident.clone();
// Try and pull out the doc comments so that we can reapply them to the generated struct.
@ -375,7 +374,7 @@ impl Route {
})
}
fn multiple(args: Vec<Args>, ast: ItemFn) -> syn::Result<Self> {
fn multiple(args: Vec<Args>, ast: syn::ItemFn) -> syn::Result<Self> {
let name = ast.sig.ident.clone();
// Try and pull out the doc comments so that we can reapply them to the generated struct.
@ -484,7 +483,7 @@ pub(crate) fn with_method(
Err(err) => return input_and_compile_error(input, err),
};
let ast = match syn::parse::<ItemFn>(input.clone()) {
let ast = match syn::parse::<syn::ItemFn>(input.clone()) {
Ok(ast) => ast,
// on parse error, make IDEs happy; see fn docs
Err(err) => return input_and_compile_error(input, err),
@ -498,7 +497,7 @@ pub(crate) fn with_method(
}
pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
let mut ast = match syn::parse::<ItemFn>(input.clone()) {
let mut ast = match syn::parse::<syn::ItemFn>(input.clone()) {
Ok(ast) => ast,
// on parse error, make IDEs happy; see fn docs
Err(err) => return input_and_compile_error(input, err),
@ -556,83 +555,142 @@ fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStrea
item
}
// todo: regular new scoped path test...
// todo: test with enums in module...
// todo: test with different namespaces...
// todo: test with multiple get/post methods...
// todo: test with doc strings in attributes...
// todo: clean up the nested tree of death, tell it about the input_and_compile_error convenience function...
// todo: add in the group functions for convenient handling of group...
// todo: see if can cleanup the Attribute with a clone plus one field change...
// todo: #[actix_web::get("/test")] the namespace is messing matching up
pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream {
// Attempt to parse the scope path, returning on error
if args.is_empty() {
return input_and_compile_error(
args.clone(),
syn::Error::new(
Span::call_site(),
"Missing arguments for scope macro, expected: #[scope(\"some path\")]",
),
);
}
let scope_path = syn::parse::<LitStr>(args.clone().into());
if let Err(_err) = scope_path {
return input_and_compile_error(
args.clone(),
syn::Error::new(
Span::call_site(),
"Missing arguments for scope macro, expected: #[scope(\"some path\")]",
),
);
}
/// Implements scope proc macro
///
// Expect macro to be for a module
match syn::parse::<syn::ItemMod>(input) {
Ok(mut ast) => {
// Modify the attributes of functions within the module with method with scope, if any
if let Some((_, ref mut items)) = ast.content {
items.iter_mut().for_each(|item| {
if let Fn(fun) = item {
fun.attrs = fun
.attrs
.iter()
.map(|attr| {
modify_attribute_with_scope(
attr,
&scope_path.clone().unwrap().value(),
)
})
.collect();
struct ScopeItems {
handlers: Vec<String>,
}
impl ScopeItems {
pub fn from_items(items: &[syn::Item]) -> Self {
let mut handlers = Vec::new();
for item in items {
match item {
syn::Item::Fn(ref fun) => {
for attr in fun.attrs.iter() {
for bound in attr.path().segments.iter() {
if bound.ident == "get"
|| bound.ident == "post"
|| bound.ident == "put"
|| bound.ident == "head"
|| bound.ident == "connect"
|| bound.ident == "options"
|| bound.ident == "trace"
|| bound.ident == "patch"
|| bound.ident == "delete"
{
handlers.push(format!("{}", fun.sig.ident));
break;
}
}
}
})
}
_ => continue,
}
TokenStream::from(quote! { #ast })
}
Err(err) => {
input_and_compile_error(args, syn::Error::new(Span::call_site(), err.to_string()))
Self { handlers }
}
}
pub struct ScopeArgs {
ast: syn::ItemConst,
name: syn::Ident,
path: String,
scope_items: ScopeItems,
}
impl ScopeArgs {
pub fn new(args: TokenStream, input: TokenStream) -> Self {
if args.is_empty() {
panic!("invalid server definition, expected: #[scope(\"some path\")]");
}
let ast: syn::ItemConst = syn::parse(input).expect("Parse input as module");
//TODO: we should change it to mod once supported on stable
//let ast: syn::ItemMod = syn::parse(input).expect("Parse input as module");
let name = ast.ident.clone();
let mut items = Vec::new();
match ast.expr.as_ref() {
syn::Expr::Block(expr) => {
for item in expr.block.stmts.iter() {
match item {
syn::Stmt::Item(ref item) => items.push(item.clone()),
_ => continue,
}
}
}
_ => panic!("Scope should containt only code block"),
}
let scope_items = ScopeItems::from_items(&items);
let mut path = None;
if let Ok(parsed) = syn::parse::<syn::LitStr>(args) {
path = Some(parsed.value());
}
let path = path.expect("Scope's path is not specified!");
Self {
ast,
name,
path,
scope_items,
}
}
pub fn generate(&self) -> TokenStream {
let text = self.to_string();
match text.parse() {
Ok(res) => res,
Err(error) => panic!("Error: {:?}\nGenerated code: {}", error, text),
}
}
}
// Check if the attribute is a method type and has a route path, then modify it
fn modify_attribute_with_scope(attr: &syn::Attribute, scope_path: &str) -> syn::Attribute {
match (
MethodType::from_path(attr.path()),
attr.parse_args::<RouteArgs>(),
attr.clone().meta,
) {
(Ok(_method), Ok(route_args), syn::Meta::List(meta_list)) => {
let modified_path = format!("{}{}", scope_path, route_args.path.value());
let modified_tokens = quote! { #modified_path };
syn::Attribute {
meta: syn::Meta::List(syn::MetaList {
tokens: modified_tokens,
..meta_list.clone()
}),
..attr.clone()
}
impl fmt::Display for ScopeArgs {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let ast = &self.ast;
let module_name = format!("{}_scope", self.name);
let module_name = syn::Ident::new(&module_name, ast.ident.span());
let ast = match ast.expr.as_ref() {
syn::Expr::Block(expr) => quote!(pub mod #module_name #expr),
_ => panic!("Unexpect non-block ast in scope macro"),
};
writeln!(f, "{}\n", ast)?;
writeln!(f, "#[allow(non_camel_case_types)]")?;
writeln!(f, "struct {};\n", self.name)?;
writeln!(
f,
"impl actix_web::dev::HttpServiceFactory for {} {{",
self.name
)?;
writeln!(
f,
" fn register(self, __config: &mut actix_web::dev::AppService) {{"
)?;
write!(
f,
" let scope = actix_web::Scope::new(\"{}\")",
self.path
)?;
for handler in self.scope_items.handlers.iter() {
write!(f, ".service({}::{})", module_name, handler)?;
}
_ => attr.clone(),
writeln!(f, ";\n")?;
writeln!(
f,
" actix_web::dev::HttpServiceFactory::register(scope, __config)"
)?;
writeln!(f, " }}\n}}")
}
}

View File

@ -371,7 +371,7 @@ async fn test_auto_async() {
let response = request.send().await.unwrap();
assert!(response.status().is_success());
}
/*
#[actix_web::test]
async fn test_wrap() {
let srv = actix_test::start(|| App::new().service(get_wrap));
@ -384,27 +384,27 @@ async fn test_wrap() {
let body = String::from_utf8(body.to_vec()).unwrap();
assert!(body.contains("wrong number of parameters"));
}
*/
#[scope("/test")]
mod scope_module {
const mod_inner: () = {
use actix_web::{
get, post, connect, delete, head, options, patch, put, trace, web, HttpRequest,
connect, delete, head, options, patch, put, trace, web, web::Json, HttpRequest,
HttpResponse, Responder,
};
#[get("/test")]
#[actix_web::get("/test")]
pub async fn test() -> impl Responder {
HttpResponse::Ok().finish()
mod_test2()
}
#[get("/twicetest/{value}")]
#[actix_web::get("/twicetest/{value}")]
pub async fn test_twice(value: web::Path<String>) -> impl actix_web::Responder {
let int_value: i32 = value.parse().unwrap_or(0);
let doubled = int_value * 2;
HttpResponse::Ok().body(format!("Twice value: {}", doubled))
}
#[post("/test")]
#[actix_web::post("/test")]
pub async fn test_post() -> impl Responder {
HttpResponse::Ok().body(format!("post works"))
}
@ -451,31 +451,31 @@ mod scope_module {
pub fn mod_common(message: String) -> impl actix_web::Responder {
HttpResponse::Ok().body(message)
}
}
};
#[scope("/v1")]
mod mod_inner_v1 {
use actix_web::{get, Responder};
const mod_inner_v1: () = {
use actix_web::Responder;
#[get("/test")]
#[actix_web::get("/test")]
pub async fn test() -> impl Responder {
super::scope_module::mod_common("version1 works".to_string())
super::mod_inner_scope::mod_common("version1 works".to_string())
}
}
};
#[scope("/v2")]
mod mod_inner_v2 {
use actix_web::{get, Responder};
const mod_inner_v2: () = {
use actix_web::Responder;
#[get("/test")]
#[actix_web::get("/test")]
pub async fn test() -> impl Responder {
super::scope_module::mod_common("version2 works".to_string())
super::mod_inner_scope::mod_common("version2 works".to_string())
}
}
};
#[actix_rt::test]
async fn test_scope_get_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::GET, srv.url("/test/test"));
let response = request.send().await.unwrap();
@ -484,7 +484,7 @@ async fn test_scope_get_async() {
#[actix_rt::test]
async fn test_scope_get_param_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_twice));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::GET, srv.url("/test/twicetest/4"));
let mut response = request.send().await.unwrap();
@ -495,7 +495,7 @@ async fn test_scope_get_param_async() {
#[actix_rt::test]
async fn test_scope_post_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_post));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::POST, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -506,7 +506,7 @@ async fn test_scope_post_async() {
#[actix_rt::test]
async fn test_scope_put_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_put));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::PUT, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -517,7 +517,7 @@ async fn test_scope_put_async() {
#[actix_rt::test]
async fn test_scope_head_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_head));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::HEAD, srv.url("/test/test"));
let response = request.send().await.unwrap();
@ -526,7 +526,7 @@ async fn test_scope_head_async() {
#[actix_rt::test]
async fn test_scope_connect_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_connect));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::CONNECT, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -537,7 +537,7 @@ async fn test_scope_connect_async() {
#[actix_rt::test]
async fn test_scope_options_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_options));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::OPTIONS, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -548,7 +548,7 @@ async fn test_scope_options_async() {
#[actix_rt::test]
async fn test_scope_trace_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_trace));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::TRACE, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -559,7 +559,7 @@ async fn test_scope_trace_async() {
#[actix_rt::test]
async fn test_scope_patch_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_patch));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::PATCH, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -570,7 +570,7 @@ async fn test_scope_patch_async() {
#[actix_rt::test]
async fn test_scope_delete_async() {
let srv = actix_test::start(|| App::new().service(scope_module::test_delete));
let srv = actix_test::start(|| App::new().service(mod_inner));
let request = srv.request(http::Method::DELETE, srv.url("/test/test"));
let mut response = request.send().await.unwrap();
@ -581,7 +581,7 @@ async fn test_scope_delete_async() {
#[actix_rt::test]
async fn test_scope_v1_v2_async() {
let srv = actix_test::start(|| App::new().service(mod_inner_v1::test).service(mod_inner_v2::test));
let srv = actix_test::start(|| App::new().service(mod_inner_v1).service(mod_inner_v2));
let request = srv.request(http::Method::GET, srv.url("/v1/test"));
let mut response = request.send().await.unwrap();

View File

@ -1,4 +1,4 @@
#[rustversion::stable(1.72)] // MSRV
#[rustversion::stable(1.68)] // MSRV
#[test]
fn compile_macros() {
let t = trybuild::TestCases::new();

View File

@ -13,20 +13,17 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View File

@ -13,20 +13,17 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View File

@ -15,20 +15,17 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View File

@ -29,20 +29,17 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View File

@ -15,20 +15,17 @@ error[E0277]: the trait bound `fn() -> impl std::future::Future<Output = String>
| required by a bound introduced by this call
|
= help: the following other types implement trait `HttpServiceFactory`:
Resource<T>
actix_web::Scope<T>
Vec<T>
Redirect
(A,)
(A, B)
(A, B, C)
(A, B, C, D)
(A, B, C, D, E)
(A, B, C, D, E, F)
(A, B, C, D, E, F, G)
(A, B, C, D, E, F, G, H)
(A, B, C, D, E, F, G, H, I)
and $N others
note: required by a bound in `App::<T>::service`
--> $WORKSPACE/actix-web/src/app.rs
|
| pub fn service<F>(mut self, factory: F) -> Self
| ------- required by a bound in this associated function
| where
| F: HttpServiceFactory + 'static,
| ^^^^^^^^^^^^^^^^^^ required by this bound in `App::<T>::service`

View File

@ -2,27 +2,6 @@
## Unreleased
### Added
- Add `unicode` crate feature (on-by-default) to switch between `regex` and `regex-lite` as a trade-off between full unicode support and binary size.
### Changed
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.5.1
### Fixed
- Fix missing import when using enabling Rustls v0.22 support.
## 4.5.0
### Added
- Add `rustls-0_22` crate feature.
- Add `HttpServer::{bind_rustls_0_22, listen_rustls_0_22}()` builder methods.
## 4.4.1
### Changed

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.5.1"
version = "4.4.1"
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
@ -20,27 +20,16 @@ edition.workspace = true
rust-version.workspace = true
[package.metadata.docs.rs]
# features that docs.rs will build with
features = ["macros", "openssl", "rustls-0_20", "rustls-0_21", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
rustdoc-args = ["--cfg", "docsrs"]
features = [
"macros",
"openssl",
"rustls-0_20",
"rustls-0_21",
"rustls-0_22",
"compress-brotli",
"compress-gzip",
"compress-zstd",
"cookies",
"secure-cookies",
]
[lib]
name = "actix_web"
path = "src/lib.rs"
[features]
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "http2", "unicode"]
default = ["macros", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "http2"]
# Brotli algorithm content-encoding support
compress-brotli = ["actix-http/compress-brotli", "__compress"]
@ -69,11 +58,6 @@ rustls = ["rustls-0_20"]
rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21
rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_22"]
# Full unicode support
unicode = ["dep:regex", "actix-router/unicode"]
# Internal (PRIVATE!) features used to aid testing and checking feature status.
# Don't rely on these whatsoever. They may disappear at anytime.
@ -89,10 +73,10 @@ actix-rt = { version = "2.6", default-features = false }
actix-server = "2"
actix-service = "2"
actix-utils = "3"
actix-tls = { version = "3.3", default-features = false, optional = true }
actix-tls = { version = "3.1", default-features = false, optional = true }
actix-http = { version = "3.6", features = ["ws"] }
actix-router = { version = "0.5", default-features = false, features = ["http"] }
actix-http = { version = "3.5", features = ["ws"] }
actix-router = "0.5"
actix-web-codegen = { version = "4.2", optional = true }
ahash = "0.8"
@ -110,8 +94,7 @@ log = "0.4"
mime = "0.3"
once_cell = "1.5"
pin-project-lite = "0.2.7"
regex = { version = "1.5.5", optional = true }
regex-lite = "0.1"
regex = "1.5.5"
serde = "1.0"
serde_json = "1.0"
serde_urlencoded = "0.7"
@ -122,22 +105,22 @@ url = "2.1"
[dev-dependencies]
actix-files = "0.6"
actix-test = { version = "0.1", features = ["openssl", "rustls-0_22"] }
actix-test = { version = "0.1", features = ["openssl", "rustls-0_21"] }
awc = { version = "3", features = ["openssl"] }
brotli = "3.3.3"
const-str = "0.5"
criterion = { version = "0.5", features = ["html_reports"] }
env_logger = "0.11"
env_logger = "0.10"
flate2 = "1.0.13"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
rand = "0.8"
rcgen = "0.12"
rustls-pemfile = "2"
rcgen = "0.11"
rustls-pemfile = "1"
serde = { version = "1.0", features = ["derive"] }
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.22" }
tls-rustls = { package = "rustls", version = "0.21" }
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13"

View File

@ -8,10 +8,10 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.5.1)](https://docs.rs/actix-web/4.5.1)
![MSRV](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.4.1)](https://docs.rs/actix-web/4.4.1)
![MSRV](https://img.shields.io/badge/rustc-1.68+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.5.1/status.svg)](https://deps.rs/crate/actix-web/4.5.1)
[![Dependency Status](https://deps.rs/crate/actix-web/4.4.1/status.svg)](https://deps.rs/crate/actix-web/4.4.1)
<br />
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)
@ -37,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.72+
- Runs on stable Rust 1.68+
## Documentation

View File

@ -471,6 +471,7 @@ mod tests {
Method, StatusCode,
},
middleware::DefaultHeaders,
service::ServiceRequest,
test::{call_service, init_service, read_body, try_init_service, TestRequest},
web, HttpRequest, HttpResponse,
};

View File

@ -380,7 +380,7 @@ impl Guard for HeaderGuard {
#[cfg(test)]
mod tests {
use actix_http::Method;
use actix_http::{header, Method};
use super::*;
use crate::test::TestRequest;

View File

@ -13,10 +13,7 @@
use std::fmt::{self, Write};
use once_cell::sync::Lazy;
#[cfg(feature = "unicode")]
use regex::Regex;
#[cfg(not(feature = "unicode"))]
use regex_lite::Regex;
use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
use crate::http::header;

View File

@ -126,7 +126,7 @@ mod tests {
use std::fmt;
use super::*;
use crate::{test::TestRequest, HttpRequest};
use crate::{http::header::Header, test::TestRequest, HttpRequest};
fn req_from_raw_headers<H: Header, I: IntoIterator<Item = V>, V: AsRef<[u8]>>(
header_lines: I,

View File

@ -33,7 +33,7 @@ use crate::{
/// considered in this selection process.
///
/// # Pre-compressed Payload
/// If you are serving some data that is already using a compressed representation (e.g., a gzip
/// If you are serving some data is already using a compressed representation (e.g., a gzip
/// compressed HTML file from disk) you can signal this to `Compress` by setting an appropriate
/// `Content-Encoding` header. In addition to preventing double compressing the payload, this header
/// is required by the spec when using compressed representations and will inform the client that

View File

@ -135,7 +135,7 @@ mod tests {
use super::*;
use crate::{
body::BoxBody,
dev::ServiceRequest,
dev::{ServiceRequest, ServiceResponse},
error::Result,
http::{
header::{HeaderValue, CONTENT_TYPE},

View File

@ -190,6 +190,8 @@ mod tests {
use super::*;
use crate::{
dev::ServiceRequest,
http::header::CONTENT_TYPE,
test::{self, TestRequest},
HttpResponse,
};

View File

@ -407,7 +407,10 @@ mod tests {
use super::*;
use crate::{
body,
http::header::{HeaderValue, CONTENT_TYPE},
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::{self, TestRequest},
};

View File

@ -18,10 +18,7 @@ use bytes::Bytes;
use futures_core::ready;
use log::{debug, warn};
use pin_project_lite::pin_project;
#[cfg(feature = "unicode")]
use regex::Regex;
#[cfg(not(feature = "unicode"))]
use regex_lite::Regex;
use regex::{Regex, RegexSet};
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
use crate::{
@ -90,7 +87,7 @@ pub struct Logger(Rc<Inner>);
struct Inner {
format: Format,
exclude: HashSet<String>,
exclude_regex: Vec<Regex>,
exclude_regex: RegexSet,
log_target: Cow<'static, str>,
}
@ -100,7 +97,7 @@ impl Logger {
Logger(Rc::new(Inner {
format: Format::new(format),
exclude: HashSet::new(),
exclude_regex: Vec::new(),
exclude_regex: RegexSet::empty(),
log_target: Cow::Borrowed(module_path!()),
}))
}
@ -117,7 +114,10 @@ impl Logger {
/// Ignore and do not log access info for paths that match regex.
pub fn exclude_regex<T: Into<String>>(mut self, path: T) -> Self {
let inner = Rc::get_mut(&mut self.0).unwrap();
inner.exclude_regex.push(Regex::new(&path.into()).unwrap());
let mut patterns = inner.exclude_regex.patterns().to_vec();
patterns.push(path.into());
let regex_set = RegexSet::new(patterns).unwrap();
inner.exclude_regex = regex_set;
self
}
@ -240,7 +240,7 @@ impl Default for Logger {
Logger(Rc::new(Inner {
format: Format::default(),
exclude: HashSet::new(),
exclude_regex: Vec::new(),
exclude_regex: RegexSet::empty(),
log_target: Cow::Borrowed(module_path!()),
}))
}
@ -300,11 +300,7 @@ where
fn call(&self, req: ServiceRequest) -> Self::Future {
let excluded = self.inner.exclude.contains(req.path())
|| self
.inner
.exclude_regex
.iter()
.any(|r| r.is_match(req.path()));
|| self.inner.exclude_regex.is_match(req.path());
if excluded {
LoggerResponse {
@ -720,7 +716,7 @@ impl<'a> fmt::Display for FormatDisplay<'a> {
#[cfg(test)]
mod tests {
use actix_service::IntoService;
use actix_service::{IntoService, Service, Transform};
use actix_utils::future::ok;
use super::*;

View File

@ -4,10 +4,7 @@ use actix_http::uri::{PathAndQuery, Uri};
use actix_service::{Service, Transform};
use actix_utils::future::{ready, Ready};
use bytes::Bytes;
#[cfg(feature = "unicode")]
use regex::Regex;
#[cfg(not(feature = "unicode"))]
use regex_lite::Regex;
use crate::{
service::{ServiceRequest, ServiceResponse},
@ -208,6 +205,7 @@ mod tests {
use super::*;
use crate::{
dev::ServiceRequest,
guard::fn_guard,
test::{call_service, init_service, TestRequest},
web, App, HttpResponse,

View File

@ -182,7 +182,7 @@ impl Responder for Redirect {
#[cfg(test)]
mod tests {
use super::*;
use crate::{dev::Service, test, App};
use crate::{dev::Service, http::StatusCode, test, App};
#[actix_rt::test]
async fn absolute_redirects() {

View File

@ -523,7 +523,7 @@ mod tests {
use super::*;
use crate::{
dev::{ResourceDef, Service},
dev::{ResourceDef, ResourceMap, Service},
http::{header, StatusCode},
test::{self, call_service, init_service, read_body, TestRequest},
web, App, HttpResponse,

View File

@ -540,14 +540,20 @@ mod tests {
use std::time::Duration;
use actix_rt::time::sleep;
use actix_service::Service;
use actix_utils::future::ok;
use super::*;
use crate::{
http::{header::HeaderValue, Method, StatusCode},
guard,
http::{
header::{self, HeaderValue},
Method, StatusCode,
},
middleware::DefaultHeaders,
service::{ServiceRequest, ServiceResponse},
test::{call_service, init_service, TestRequest},
App, HttpMessage,
web, App, Error, HttpMessage, HttpResponse,
};
#[test]

View File

@ -408,7 +408,10 @@ mod tests {
use super::*;
use crate::{
body,
http::header::{HeaderValue, CONTENT_TYPE},
http::{
header::{self, HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::assert_body_eq,
};

View File

@ -175,7 +175,10 @@ mod tests {
use super::*;
use crate::{
http::header::{HeaderValue, CONTENT_TYPE},
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::TestRequest,
};

View File

@ -188,11 +188,15 @@ impl_into_string_responder!(Cow<'_, str>);
pub(crate) mod tests {
use actix_http::body::to_bytes;
use actix_service::Service;
use bytes::{Bytes, BytesMut};
use super::*;
use crate::{
error,
http::header::{HeaderValue, CONTENT_TYPE},
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::{assert_body_eq, init_service, TestRequest},
web, App,
};

View File

@ -399,7 +399,7 @@ mod tests {
use static_assertions::assert_impl_all;
use super::*;
use crate::http::header::COOKIE;
use crate::http::header::{HeaderValue, COOKIE};
assert_impl_all!(HttpResponse: Responder);
assert_impl_all!(HttpResponse<String>: Responder);

View File

@ -92,7 +92,6 @@ pub struct RouteService {
}
impl RouteService {
// TODO(breaking): remove pass by ref mut
#[allow(clippy::needless_pass_by_ref_mut)]
pub fn check(&self, req: &mut ServiceRequest) -> bool {
let guard_ctx = req.guard_ctx();

View File

@ -547,6 +547,7 @@ impl ServiceFactory<ServiceRequest> for ScopeEndpoint {
#[cfg(test)]
mod tests {
use actix_service::Service;
use actix_utils::future::ok;
use bytes::Bytes;
@ -558,6 +559,7 @@ mod tests {
Method, StatusCode,
},
middleware::DefaultHeaders,
service::{ServiceRequest, ServiceResponse},
test::{assert_body_eq, call_service, init_service, read_body, TestRequest},
web, App, HttpMessage, HttpRequest, HttpResponse,
};

View File

@ -7,12 +7,7 @@ use std::{
time::Duration,
};
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
))]
#[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};
@ -447,25 +442,6 @@ where
Ok(self)
}
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
/// using Rustls v0.22.
///
/// See [`bind()`](Self::bind()) for more details on `addrs` argument.
///
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
#[cfg(feature = "rustls-0_22")]
pub fn bind_rustls_0_22<A: net::ToSocketAddrs>(
mut self,
addrs: A,
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
let sockets = bind_addrs(addrs, self.backlog)?;
for lst in sockets {
self = self.listen_rustls_0_22_inner(lst, config.clone())?;
}
Ok(self)
}
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
/// using OpenSSL.
///
@ -709,72 +685,6 @@ where
Ok(self)
}
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
/// v0.22.
///
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
///
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
#[cfg(feature = "rustls-0_22")]
pub fn listen_rustls_0_22(
self,
lst: net::TcpListener,
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
self.listen_rustls_0_22_inner(lst, config)
}
#[cfg(feature = "rustls-0_22")]
fn listen_rustls_0_22_inner(
mut self,
lst: net::TcpListener,
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
scheme: "https",
});
let on_connect_fn = self.on_connect_fn.clone();
self.builder =
self.builder
.listen(format!("actix-web-service-{}", addr), lst, move || {
let c = cfg.lock().unwrap();
let host = c.host.clone().unwrap_or_else(|| format!("{}", addr));
let svc = HttpService::build()
.keep_alive(c.keep_alive)
.client_request_timeout(c.client_request_timeout)
.client_disconnect_timeout(c.client_disconnect_timeout);
let svc = if let Some(handler) = on_connect_fn.clone() {
svc.on_connect_ext(move |io: &_, ext: _| (handler)(io as &dyn Any, ext))
} else {
svc
};
let fac = factory()
.into_factory()
.map_err(|err| err.into().error_response());
let acceptor_config = match c.tls_handshake_timeout {
Some(dur) => TlsAcceptorConfig::default().handshake_timeout(dur),
None => TlsAcceptorConfig::default(),
};
svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr)
}))
.rustls_0_22_with_config(config.clone(), acceptor_config)
})?;
Ok(self)
}
/// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL.
///
/// See [`listen()`](Self::listen) for more details on the `lst` argument.

View File

@ -700,7 +700,7 @@ mod tests {
use crate::{
guard, http,
test::{self, init_service, TestRequest},
web, App,
web, App, HttpResponse,
};
#[actix_rt::test]

View File

@ -350,7 +350,7 @@ mod tests {
use std::time::SystemTime;
use super::*;
use crate::{http::header, test::init_service, web, App, Error, Responder};
use crate::{http::header, test::init_service, web, App, Error, HttpResponse, Responder};
#[actix_rt::test]
async fn test_basics() {

View File

@ -287,7 +287,10 @@ mod tests {
use serde::{Deserialize, Serialize};
use super::*;
use crate::test::TestRequest;
use crate::{
test::TestRequest,
web::{Form, Json},
};
#[derive(Debug, Clone, Serialize, Deserialize)]
struct TestForm {

Some files were not shown because too many files have changed in this diff Show More