mirror of
https://github.com/fafhrd91/actix-web
synced 2024-11-23 16:21:06 +01:00
Merge branch 'master' into prelim
This commit is contained in:
commit
afd1ce1d71
@ -6,9 +6,5 @@ lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
|
||||
ci-check-min = "hack --workspace check --no-default-features"
|
||||
ci-check-default = "hack --workspace check"
|
||||
ci-check-default-tests = "check --workspace --tests"
|
||||
ci-check-all-feature-powerset="hack --workspace --feature-powerset --skip=__compress,experimental-io-uring check"
|
||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --skip=__compress check"
|
||||
|
||||
# testing
|
||||
ci-doctest-default = "test --workspace --doc --no-fail-fast -- --nocapture"
|
||||
ci-doctest = "test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
ci-check-all-feature-powerset="hack --workspace --feature-powerset --depth=4 --skip=__compress,experimental-io-uring check"
|
||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --depth=4 --skip=__compress check"
|
||||
|
44
.github/workflows/ci-post-merge.yml
vendored
44
.github/workflows/ci-post-merge.yml
vendored
@ -30,6 +30,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.1
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
@ -44,10 +48,10 @@ jobs:
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install cargo-hack and cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.28.0
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
@ -57,19 +61,7 @@ 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-codegen --all-features
|
||||
cargo test --lib --tests -p=awc --all-features
|
||||
cargo test --lib --tests -p=actix-http-test --all-features
|
||||
cargo test --lib --tests -p=actix-test --all-features
|
||||
cargo test --lib --tests -p=actix-files
|
||||
cargo test --lib --tests -p=actix-multipart --all-features
|
||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
run: just test
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
@ -88,7 +80,7 @@ jobs:
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2.28.0
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
with:
|
||||
tool: cargo-hack
|
||||
|
||||
@ -97,21 +89,3 @@ jobs:
|
||||
|
||||
- name: check feature combinations
|
||||
run: cargo ci-check-all-feature-powerset-linux
|
||||
|
||||
nextest:
|
||||
name: nextest
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
|
||||
- name: Install nextest
|
||||
uses: taiki-e/install-action@v2.28.0
|
||||
with:
|
||||
tool: nextest
|
||||
|
||||
- name: Test with cargo-nextest
|
||||
run: cargo nextest run
|
||||
|
48
.github/workflows/ci.yml
vendored
48
.github/workflows/ci.yml
vendored
@ -16,7 +16,13 @@ concurrency:
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
read_msrv:
|
||||
name: Read MSRV
|
||||
uses: actions-rust-lang/msrv/.github/workflows/msrv.yml@v0.1.0
|
||||
|
||||
build_and_test:
|
||||
needs: read_msrv
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
@ -26,7 +32,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: "${{ needs.read_msrv.outputs.msrv }}" }
|
||||
- { name: stable, version: stable }
|
||||
|
||||
name: ${{ matrix.target.name }} / ${{ matrix.version.name }}
|
||||
@ -35,6 +41,10 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install nasm
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
uses: ilammy/setup-nasm@v1.5.1
|
||||
|
||||
- name: Install OpenSSL
|
||||
if: matrix.target.os == 'windows-latest'
|
||||
shell: bash
|
||||
@ -44,20 +54,23 @@ jobs:
|
||||
echo 'OPENSSL_DIR=C:\Program Files\OpenSSL' >> $GITHUB_ENV
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Setup mold linker
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: rui314/setup-mold@v1
|
||||
|
||||
- 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.28.0
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
with:
|
||||
tool: cargo-hack,cargo-ci-cache-clean
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
- name: workaround MSRV issues
|
||||
if: matrix.version.name == 'msrv'
|
||||
run: |
|
||||
cargo update -p=clap --precise=4.4.18
|
||||
run: just downgrade-for-msrv
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
@ -67,20 +80,7 @@ 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-codegen --all-features
|
||||
cargo test --lib --tests -p=awc --all-features
|
||||
cargo test --lib --tests -p=actix-http-test --all-features
|
||||
cargo test --lib --tests -p=actix-test --all-features
|
||||
cargo test --lib --tests -p=actix-files
|
||||
cargo test --lib --tests -p=actix-multipart --all-features
|
||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
run: just test
|
||||
|
||||
- name: CI cache clean
|
||||
run: cargo-ci-cache-clean
|
||||
@ -112,6 +112,10 @@ jobs:
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: doc tests
|
||||
run: cargo ci-doctest
|
||||
timeout-minutes: 60
|
||||
run: just test-docs
|
||||
|
8
.github/workflows/coverage.yml
vendored
8
.github/workflows/coverage.yml
vendored
@ -22,16 +22,16 @@ jobs:
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
|
||||
- name: Install cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.28.0
|
||||
- name: Install just,cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
with:
|
||||
tool: cargo-llvm-cov
|
||||
tool: just,cargo-llvm-cov
|
||||
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4.1.0
|
||||
uses: codecov/codecov-action@v4.4.1
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
|
6
.github/workflows/lint.yml
vendored
6
.github/workflows/lint.yml
vendored
@ -79,15 +79,15 @@ jobs:
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly-2023-08-25
|
||||
toolchain: nightly-2024-06-07
|
||||
|
||||
- name: Install cargo-public-api
|
||||
uses: taiki-e/install-action@v2.28.0
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
with:
|
||||
tool: cargo-public-api
|
||||
|
||||
- name: Generate API diff
|
||||
run: |
|
||||
for f in $(find -mindepth 2 -maxdepth 2 -name Cargo.toml); do
|
||||
cargo public-api --manifest-path "$f" diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||
cargo public-api --manifest-path "$f" --simplified diff ${{ github.event.pull_request.base.sha }}..${{ github.sha }}
|
||||
done
|
||||
|
41
.github/workflows/upload-doc.yml
vendored
41
.github/workflows/upload-doc.yml
vendored
@ -1,41 +0,0 @@
|
||||
name: Upload Documentation
|
||||
|
||||
on:
|
||||
push:
|
||||
branches: [master]
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
build:
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Build Docs
|
||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
||||
env:
|
||||
RUSTDOCFLAGS: --cfg=docsrs
|
||||
|
||||
- name: Tweak HTML
|
||||
run: echo '<meta http-equiv="refresh" content="0;url=actix_web/index.html">' > target/doc/index.html
|
||||
|
||||
- name: Deploy to GitHub Pages
|
||||
uses: JamesIves/github-pages-deploy-action@v4.5.0
|
||||
with:
|
||||
folder: target/doc
|
||||
single-commit: true
|
@ -15,6 +15,8 @@ members = [
|
||||
]
|
||||
|
||||
[workspace.package]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
rust-version = "1.72"
|
||||
|
@ -2,8 +2,16 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 3.7.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `rustls-0_23` crate feature
|
||||
- Add `{h1::H1Service, h2::H2Service, HttpService}::rustls_0_23()` and `HttpService::rustls_0_23_with_config()` service constructors.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update `brotli` dependency to `6`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 3.6.0
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.6.0"
|
||||
version = "3.7.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -28,6 +28,7 @@ features = [
|
||||
"rustls-0_20",
|
||||
"rustls-0_21",
|
||||
"rustls-0_22",
|
||||
"rustls-0_23",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
@ -66,6 +67,9 @@ 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"]
|
||||
|
||||
# TLS via Rustls v0.23
|
||||
rustls-0_23 = ["actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||
|
||||
# Compression codecs
|
||||
compress-brotli = ["__compress", "brotli"]
|
||||
compress-gzip = ["__compress", "flate2"]
|
||||
@ -111,17 +115,17 @@ 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.4", default-features = false, optional = true }
|
||||
|
||||
# compress-*
|
||||
brotli = { version = "3.3.3", optional = true }
|
||||
brotli = { version = "6", optional = true }
|
||||
flate2 = { version = "1.0.13", optional = true }
|
||||
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.4", features = ["openssl", "rustls-0_23-webpki-roots"] }
|
||||
actix-web = "4"
|
||||
|
||||
async-stream = "0.3"
|
||||
@ -131,7 +135,7 @@ env_logger = "0.11"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
memchr = "2.4"
|
||||
once_cell = "1.9"
|
||||
rcgen = "0.12"
|
||||
rcgen = "0.13"
|
||||
regex = "1.3"
|
||||
rustversion = "1"
|
||||
rustls-pemfile = "2"
|
||||
@ -139,16 +143,16 @@ 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_023 = { package = "rustls", version = "0.23" }
|
||||
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
|
||||
|
||||
[[example]]
|
||||
name = "ws"
|
||||
required-features = ["ws", "rustls-0_22"]
|
||||
required-features = ["ws", "rustls-0_23"]
|
||||
|
||||
[[example]]
|
||||
name = "tls_rustls"
|
||||
required-features = ["http2", "rustls-0_22"]
|
||||
required-features = ["http2", "rustls-0_23"]
|
||||
|
||||
[[bench]]
|
||||
name = "response-body-compression"
|
||||
|
@ -5,11 +5,11 @@
|
||||
<!-- 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)
|
||||
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.7.0)](https://docs.rs/actix-http/3.7.0)
|
||||
![Version](https://img.shields.io/badge/rustc-1.72+-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.7.0/status.svg)](https://deps.rs/crate/actix-http/3.7.0)
|
||||
[![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)
|
||||
|
||||
|
@ -12,7 +12,7 @@
|
||||
//! Protocol: HTTP/1.1
|
||||
//! ```
|
||||
|
||||
extern crate tls_rustls_022 as rustls;
|
||||
extern crate tls_rustls_023 as rustls;
|
||||
|
||||
use std::io;
|
||||
|
||||
@ -36,16 +36,17 @@ async fn main() -> io::Result<()> {
|
||||
);
|
||||
ok::<_, Error>(Response::ok().set_body(body))
|
||||
})
|
||||
.rustls_0_22(rustls_config())
|
||||
.rustls_0_23(rustls_config())
|
||||
})?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
|
||||
fn rustls_config() -> rustls::ServerConfig {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut io::BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut io::BufReader::new(key_file.as_bytes());
|
||||
|
@ -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_023 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_0_23(tls_config())
|
||||
})?
|
||||
.run()
|
||||
.await
|
||||
@ -87,9 +87,10 @@ fn tls_config() -> rustls::ServerConfig {
|
||||
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
|
@ -706,7 +706,7 @@ where
|
||||
|
||||
req.head_mut().peer_addr = *this.peer_addr;
|
||||
|
||||
req.conn_data = this.conn_data.clone();
|
||||
req.conn_data.clone_from(this.conn_data);
|
||||
|
||||
match this.codec.message_type() {
|
||||
// request has no payload
|
||||
|
@ -335,6 +335,67 @@ mod rustls_0_22 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
mod rustls_0_23 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<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.23 based service.
|
||||
pub fn rustls_0_23(
|
||||
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 = ()>,
|
||||
|
@ -126,7 +126,7 @@ where
|
||||
head.headers = parts.headers.into();
|
||||
head.peer_addr = this.peer_addr;
|
||||
|
||||
req.conn_data = this.conn_data.clone();
|
||||
req.conn_data.clone_from(&this.conn_data);
|
||||
|
||||
let fut = this.flow.service.call(req);
|
||||
let config = this.config.clone();
|
||||
|
@ -293,6 +293,57 @@ mod rustls_0_22 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
mod rustls_0_23 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<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.23 based service.
|
||||
pub fn rustls_0_23(
|
||||
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,
|
||||
|
@ -6,7 +6,10 @@
|
||||
//! | ------------------- | ------------------------------------------- |
|
||||
//! | `http2` | HTTP/2 support via [h2]. |
|
||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||
//! | `rustls` | TLS support via [rustls]. |
|
||||
//! | `rustls` | TLS support via [rustls] 0.20. |
|
||||
//! | `rustls-0_21` | TLS support via [rustls] 0.21. |
|
||||
//! | `rustls-0_22` | TLS support via [rustls] 0.22. |
|
||||
//! | `rustls-0_23` | TLS support via [rustls] 0.23. |
|
||||
//! | `compress-brotli` | Payload compression support: Brotli. |
|
||||
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
|
||||
//! | `compress-zstd` | Payload compression support: Zstd. |
|
||||
@ -28,7 +31,7 @@
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
|
||||
|
||||
pub use ::http::{uri, uri::Uri, Method, StatusCode, Version};
|
||||
pub use http::{uri, uri::Uri, Method, StatusCode, Version};
|
||||
|
||||
pub mod body;
|
||||
mod builder;
|
||||
@ -63,6 +66,7 @@ pub use self::payload::PayloadStream;
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
pub use self::service::TlsAcceptorConfig;
|
||||
pub use self::{
|
||||
|
@ -246,6 +246,7 @@ where
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TlsAcceptorConfig {
|
||||
@ -257,6 +258,7 @@ pub struct TlsAcceptorConfig {
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
impl TlsAcceptorConfig {
|
||||
/// Set TLS handshake timeout duration.
|
||||
@ -650,6 +652,102 @@ mod rustls_0_22 {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
mod rustls_0_23 {
|
||||
use std::io;
|
||||
|
||||
use actix_service::ServiceFactoryExt as _;
|
||||
use actix_tls::accept::{
|
||||
rustls_0_23::{reexports::ServerConfig, Acceptor, TlsStream},
|
||||
TlsError,
|
||||
};
|
||||
|
||||
use super::*;
|
||||
|
||||
impl<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.23 based service.
|
||||
pub fn rustls_0_23(
|
||||
self,
|
||||
config: ServerConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<io::Error, DispatchError>,
|
||||
InitError = (),
|
||||
> {
|
||||
self.rustls_0_23_with_config(config, TlsAcceptorConfig::default())
|
||||
}
|
||||
|
||||
/// Create Rustls v0.23 based service with custom TLS acceptor configuration.
|
||||
pub fn rustls_0_23_with_config(
|
||||
self,
|
||||
mut config: ServerConfig,
|
||||
tls_acceptor_config: TlsAcceptorConfig,
|
||||
) -> impl ServiceFactory<
|
||||
TcpStream,
|
||||
Config = (),
|
||||
Response = (),
|
||||
Error = TlsError<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
|
||||
|
@ -178,14 +178,14 @@ impl Parser {
|
||||
};
|
||||
|
||||
if payload_len < 126 {
|
||||
dst.reserve(p_len + 2 + if mask { 4 } else { 0 });
|
||||
dst.reserve(p_len + 2);
|
||||
dst.put_slice(&[one, two | payload_len as u8]);
|
||||
} else if payload_len <= 65_535 {
|
||||
dst.reserve(p_len + 4 + if mask { 4 } else { 0 });
|
||||
dst.reserve(p_len + 4);
|
||||
dst.put_slice(&[one, two | 126]);
|
||||
dst.put_u16(payload_len as u16);
|
||||
} else {
|
||||
dst.reserve(p_len + 10 + if mask { 4 } else { 0 });
|
||||
dst.reserve(p_len + 10);
|
||||
dst.put_slice(&[one, two | 127]);
|
||||
dst.put_u64(payload_len as u64);
|
||||
};
|
||||
|
@ -42,9 +42,11 @@ where
|
||||
}
|
||||
|
||||
fn tls_config() -> SslAcceptor {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![cfg(feature = "rustls-0_22")]
|
||||
#![cfg(feature = "rustls-0_23")]
|
||||
|
||||
extern crate tls_rustls_022 as rustls;
|
||||
extern crate tls_rustls_023 as rustls;
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
@ -20,7 +20,7 @@ use actix_http::{
|
||||
use actix_http_test::test_server;
|
||||
use actix_rt::pin;
|
||||
use actix_service::{fn_factory_with_config, fn_service};
|
||||
use actix_tls::connect::rustls_0_22::webpki_roots_cert_store;
|
||||
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
|
||||
use actix_utils::future::{err, ok, poll_fn};
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::{Display, Error};
|
||||
@ -52,9 +52,10 @@ where
|
||||
}
|
||||
|
||||
fn tls_config() -> RustlsServerConfig {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
@ -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_0_23(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_0_23(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_0_23(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_0_23_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_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@ -205,7 +206,7 @@ async fn h2_content_length() {
|
||||
];
|
||||
ok::<_, Infallible>(Response::new(statuses[indx]))
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@ -277,7 +278,7 @@ async fn h2_headers() {
|
||||
}
|
||||
ok::<_, Infallible>(config.body(data.clone()))
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls_0_23(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_0_23(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_0_23(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_0_23(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_0_23(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_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@ -434,7 +435,7 @@ async fn h2_body_chunked_explicit() {
|
||||
.body(BodyStream::new(body)),
|
||||
)
|
||||
})
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
})
|
||||
.await;
|
||||
|
||||
@ -463,7 +464,7 @@ async fn h2_response_http_error_handling() {
|
||||
)
|
||||
}))
|
||||
}))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls_0_23(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_0_23(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_0_23(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_0_23(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_0_23(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_0_23(config)
|
||||
})
|
||||
.await;
|
||||
|
||||
|
@ -4,10 +4,11 @@ version = "0.6.1"
|
||||
authors = ["Jacob Halsey <jacob@jhalsey.com>"]
|
||||
description = "Multipart form derive macro for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.6.2
|
||||
|
||||
- Add testing utilities under new module `test`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.6.1"
|
||||
version = "0.6.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Jacob Halsey <jacob@jhalsey.com>",
|
||||
|
@ -5,12 +5,73 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[![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)
|
||||
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.6.2)](https://docs.rs/actix-multipart/0.6.2)
|
||||
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
|
||||
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
|
||||
<br />
|
||||
[![dependency status](https://deps.rs/crate/actix-multipart/0.6.1/status.svg)](https://deps.rs/crate/actix-multipart/0.6.1)
|
||||
[![dependency status](https://deps.rs/crate/actix-multipart/0.6.2/status.svg)](https://deps.rs/crate/actix-multipart/0.6.2)
|
||||
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
|
||||
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Example
|
||||
|
||||
Dependencies:
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
actix-multipart = "0.6"
|
||||
actix-web = "4.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
```
|
||||
|
||||
Code:
|
||||
|
||||
```rust
|
||||
use actix_web::{post, App, HttpServer, Responder};
|
||||
|
||||
use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
|
||||
use serde::Deserialize;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct Metadata {
|
||||
name: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, MultipartForm)]
|
||||
struct UploadForm {
|
||||
#[multipart(limit = "100MB")]
|
||||
file: TempFile,
|
||||
json: MPJson<Metadata>,
|
||||
}
|
||||
|
||||
#[post("/videos")]
|
||||
pub async fn post_video(MultipartForm(form): MultipartForm<UploadForm>) -> impl Responder {
|
||||
format!(
|
||||
"Uploaded file {}, with size: {}",
|
||||
form.json.name, form.file.size
|
||||
)
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> std::io::Result<()> {
|
||||
HttpServer::new(move || App::new().service(post_video))
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.run()
|
||||
.await
|
||||
}
|
||||
```
|
||||
|
||||
Curl request :
|
||||
|
||||
```bash
|
||||
curl -v --request POST \
|
||||
--url http://localhost:8080/videos \
|
||||
-F 'json={"name": "Cargo.lock"};type=application/json' \
|
||||
-F file=@./Cargo.lock
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
https://github.com/actix/examples/tree/master/forms/multipart
|
||||
|
@ -313,7 +313,8 @@ where
|
||||
let entry = field_limits
|
||||
.entry(field.name().to_owned())
|
||||
.or_insert_with(|| T::limit(field.name()));
|
||||
limits.field_limit_remaining = entry.to_owned();
|
||||
|
||||
limits.field_limit_remaining.clone_from(entry);
|
||||
|
||||
T::handle_field(&req, field, &mut limits, &mut state).await?;
|
||||
|
||||
|
@ -1,4 +1,39 @@
|
||||
//! Multipart form support for Actix Web.
|
||||
//! # Examples
|
||||
//! ```no_run
|
||||
//! use actix_web::{post, App, HttpServer, Responder};
|
||||
//!
|
||||
//! use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
|
||||
//! use serde::Deserialize;
|
||||
//!
|
||||
//! #[derive(Debug, Deserialize)]
|
||||
//! struct Metadata {
|
||||
//! name: String,
|
||||
//! }
|
||||
//!
|
||||
//! #[derive(Debug, MultipartForm)]
|
||||
//! struct UploadForm {
|
||||
//! #[multipart(limit = "100MB")]
|
||||
//! file: TempFile,
|
||||
//! json: MPJson<Metadata>,
|
||||
//! }
|
||||
//!
|
||||
//! #[post("/videos")]
|
||||
//! pub async fn post_video(MultipartForm(form): MultipartForm<UploadForm>) -> impl Responder {
|
||||
//! format!(
|
||||
//! "Uploaded file {}, with size: {}",
|
||||
//! form.json.name, form.file.size
|
||||
//! )
|
||||
//! }
|
||||
//!
|
||||
//! #[actix_web::main]
|
||||
//! async fn main() -> std::io::Result<()> {
|
||||
//! HttpServer::new(move || App::new().service(post_video))
|
||||
//! .bind(("127.0.0.1", 8080))?
|
||||
//! .run()
|
||||
//! .await
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
|
@ -2,6 +2,8 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.5.3
|
||||
|
||||
- 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.
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-router"
|
||||
version = "0.5.2"
|
||||
version = "0.5.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||
|
@ -3,11 +3,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[![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)
|
||||
[![Documentation](https://docs.rs/actix-router/badge.svg?version=0.5.3)](https://docs.rs/actix-router/0.5.3)
|
||||
![Version](https://img.shields.io/badge/rustc-1.72+-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)
|
||||
[![dependency status](https://deps.rs/crate/actix-router/0.5.3/status.svg)](https://deps.rs/crate/actix-router/0.5.3)
|
||||
[![Download](https://img.shields.io/crates/d/actix-router.svg)](https://crates.io/crates/actix-router)
|
||||
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -154,15 +154,11 @@ impl<T: ResourcePath> Path<T> {
|
||||
None
|
||||
}
|
||||
|
||||
/// Get matched parameter by name.
|
||||
/// Returns matched parameter by name.
|
||||
///
|
||||
/// If keyed parameter is not available empty string is used as default value.
|
||||
pub fn query(&self, key: &str) -> &str {
|
||||
if let Some(s) = self.get(key) {
|
||||
s
|
||||
} else {
|
||||
""
|
||||
}
|
||||
self.get(key).unwrap_or_default()
|
||||
}
|
||||
|
||||
/// Return iterator to items in parameter container.
|
||||
|
@ -2,6 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
||||
- Add `TestServerConfig::disable_redirects()` method.
|
||||
- Various types from `awc`, such as `ClientRequest` and `ClientResponse`, are now re-exported.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 0.1.3
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.3"
|
||||
version = "0.1.4"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -29,19 +29,21 @@ rustls-0_20 = ["tls-rustls-0_20", "actix-http/rustls-0_20", "awc/rustls-0_20"]
|
||||
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 Rustls v0.23
|
||||
rustls-0_23 = ["tls-rustls-0_23", "actix-http/rustls-0_23", "awc/rustls-0_23-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.7"
|
||||
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.6", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.5", 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 = [] }
|
||||
@ -53,4 +55,5 @@ 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 }
|
||||
tls-rustls-0_23 = { package = "rustls", version = "0.23", default-features = false, optional = true }
|
||||
tokio = { version = "1.24.2", features = ["sync"] }
|
||||
|
@ -52,7 +52,7 @@ use actix_web::{
|
||||
rt::{self, System},
|
||||
web, Error,
|
||||
};
|
||||
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
pub use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
use futures_core::Stream;
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
@ -145,8 +145,12 @@ where
|
||||
StreamType::Rustls021(_) => true,
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
StreamType::Rustls022(_) => true,
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
StreamType::Rustls023(_) => true,
|
||||
};
|
||||
|
||||
let client_cfg = cfg.clone();
|
||||
|
||||
// run server in separate orphaned thread
|
||||
thread::spawn(move || {
|
||||
rt::System::new().block_on(async move {
|
||||
@ -371,6 +375,48 @@ where
|
||||
.rustls_0_22(config.clone())
|
||||
}),
|
||||
},
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
StreamType::Rustls023(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_23(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_23(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_23(config.clone())
|
||||
}),
|
||||
},
|
||||
}
|
||||
.expect("test server could not be created");
|
||||
|
||||
@ -416,7 +462,13 @@ where
|
||||
}
|
||||
};
|
||||
|
||||
Client::builder().connector(connector).finish()
|
||||
let mut client_builder = Client::builder().connector(connector);
|
||||
|
||||
if client_cfg.disable_redirects {
|
||||
client_builder = client_builder.disable_redirects();
|
||||
}
|
||||
|
||||
client_builder.finish()
|
||||
};
|
||||
|
||||
TestServer {
|
||||
@ -436,6 +488,7 @@ enum HttpVer {
|
||||
Both,
|
||||
}
|
||||
|
||||
#[allow(clippy::large_enum_variant)]
|
||||
#[derive(Clone)]
|
||||
enum StreamType {
|
||||
Tcp,
|
||||
@ -447,6 +500,8 @@ enum StreamType {
|
||||
Rustls021(tls_rustls_0_21::ServerConfig),
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
Rustls022(tls_rustls_0_22::ServerConfig),
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
Rustls023(tls_rustls_0_23::ServerConfig),
|
||||
}
|
||||
|
||||
/// Create default test server config.
|
||||
@ -461,6 +516,7 @@ pub struct TestServerConfig {
|
||||
client_request_timeout: Duration,
|
||||
port: u16,
|
||||
workers: usize,
|
||||
disable_redirects: bool,
|
||||
}
|
||||
|
||||
impl Default for TestServerConfig {
|
||||
@ -478,6 +534,7 @@ impl TestServerConfig {
|
||||
client_request_timeout: Duration::from_secs(5),
|
||||
port: 0,
|
||||
workers: 1,
|
||||
disable_redirects: false,
|
||||
}
|
||||
}
|
||||
|
||||
@ -537,6 +594,13 @@ impl TestServerConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Accepts secure connections via Rustls v0.23.
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
pub fn rustls_0_23(mut self, config: tls_rustls_0_23::ServerConfig) -> Self {
|
||||
self.stream = StreamType::Rustls023(config);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets client timeout for first request.
|
||||
pub fn client_request_timeout(mut self, dur: Duration) -> Self {
|
||||
self.client_request_timeout = dur;
|
||||
@ -558,6 +622,15 @@ impl TestServerConfig {
|
||||
self.workers = workers;
|
||||
self
|
||||
}
|
||||
|
||||
/// Instruct the client to not follow redirects.
|
||||
///
|
||||
/// By default, the client will follow up to 10 consecutive redirects
|
||||
/// before giving up.
|
||||
pub fn disable_redirects(mut self) -> Self {
|
||||
self.disable_redirects = true;
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// A basic HTTP server controller that simplifies the process of writing integration tests for
|
||||
|
@ -2,6 +2,11 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 4.3.0
|
||||
|
||||
- Add `#[scope]` macro.
|
||||
- Add `compat-routing-macros-force-pub` crate feature which, on-by-default, which when disabled causes handlers to inherit their attached function's visibility.
|
||||
- Prevent inclusion of default `actix-router` features.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 4.2.2
|
||||
|
@ -1,21 +1,26 @@
|
||||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "4.2.2"
|
||||
version = "4.3.0"
|
||||
description = "Routing and runtime macros for Actix Web"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
homepage.workspace = true
|
||||
repository.workspace = true
|
||||
license.workspace = true
|
||||
edition.workspace = true
|
||||
rust-version.workspace = true
|
||||
|
||||
[lib]
|
||||
proc-macro = true
|
||||
|
||||
[features]
|
||||
default = ["compat-routing-macros-force-pub"]
|
||||
compat-routing-macros-force-pub = []
|
||||
|
||||
[dependencies]
|
||||
actix-router = "0.5"
|
||||
actix-router = { version = "0.5", default-features = false }
|
||||
proc-macro2 = "1"
|
||||
quote = "1"
|
||||
syn = { version = "2", features = ["full", "extra-traits"] }
|
||||
|
@ -5,11 +5,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[![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)
|
||||
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=4.3.0)](https://docs.rs/actix-web-codegen/4.3.0)
|
||||
![Version](https://img.shields.io/badge/rustc-1.72+-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)
|
||||
[![dependency status](https://deps.rs/crate/actix-web-codegen/4.3.0/status.svg)](https://deps.rs/crate/actix-web-codegen/4.3.0)
|
||||
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
|
||||
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -83,6 +83,7 @@ use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
||||
mod route;
|
||||
mod scope;
|
||||
|
||||
/// Creates resource handler , allowing multiple HTTP method guards.
|
||||
///
|
||||
@ -198,6 +199,43 @@ method_macro!(Trace, trace);
|
||||
method_macro!(Patch, patch);
|
||||
method_macro!(All, all);
|
||||
|
||||
/// Prepends a path prefix to all handlers using routing macros inside the attached module.
|
||||
///
|
||||
/// # Syntax
|
||||
///
|
||||
/// ```
|
||||
/// # use actix_web_codegen::scope;
|
||||
/// #[scope("/prefix")]
|
||||
/// mod api {
|
||||
/// // ...
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Arguments
|
||||
///
|
||||
/// - `"/prefix"` - Raw literal string to be prefixed onto contained handlers' paths.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use actix_web_codegen::{scope, get};
|
||||
/// # use actix_web::Responder;
|
||||
/// #[scope("/api")]
|
||||
/// mod api {
|
||||
/// # use super::*;
|
||||
/// #[get("/hello")]
|
||||
/// pub async fn hello() -> impl Responder {
|
||||
/// // this has path /api/hello
|
||||
/// "Hello, world!"
|
||||
/// }
|
||||
/// }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
#[proc_macro_attribute]
|
||||
pub fn scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
scope::with_scope(args, input)
|
||||
}
|
||||
|
||||
/// Marks async main function as the Actix Web system entry-point.
|
||||
///
|
||||
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
|
||||
@ -241,3 +279,15 @@ pub fn test(_: TokenStream, item: TokenStream) -> TokenStream {
|
||||
output.extend(item);
|
||||
output
|
||||
}
|
||||
|
||||
/// Converts the error to a token stream and appends it to the original input.
|
||||
///
|
||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||
/// recover and show more precise errors within the macro body.
|
||||
///
|
||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||
let compile_err = TokenStream::from(err.to_compile_error());
|
||||
item.extend(compile_err);
|
||||
item
|
||||
}
|
||||
|
@ -6,10 +6,12 @@ use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{punctuated::Punctuated, Ident, LitStr, Path, Token};
|
||||
|
||||
use crate::input_and_compile_error;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct RouteArgs {
|
||||
path: syn::LitStr,
|
||||
options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||
pub(crate) path: syn::LitStr,
|
||||
pub(crate) options: Punctuated<syn::MetaNameValue, Token![,]>,
|
||||
}
|
||||
|
||||
impl syn::parse::Parse for RouteArgs {
|
||||
@ -79,7 +81,7 @@ macro_rules! standard_method_type {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_path(method: &Path) -> Result<Self, ()> {
|
||||
pub(crate) fn from_path(method: &Path) -> Result<Self, ()> {
|
||||
match () {
|
||||
$(_ if method.is_ident(stringify!($lower)) => Ok(Self::$variant),)+
|
||||
_ => Err(()),
|
||||
@ -412,6 +414,13 @@ impl ToTokens for Route {
|
||||
doc_attributes,
|
||||
} = self;
|
||||
|
||||
#[allow(unused_variables)] // used when force-pub feature is disabled
|
||||
let vis = &ast.vis;
|
||||
|
||||
// TODO(breaking): remove this force-pub forwards-compatibility feature
|
||||
#[cfg(feature = "compat-routing-macros-force-pub")]
|
||||
let vis = syn::Visibility::Public(<Token![pub]>::default());
|
||||
|
||||
let registrations: TokenStream2 = args
|
||||
.iter()
|
||||
.map(|args| {
|
||||
@ -459,7 +468,7 @@ impl ToTokens for Route {
|
||||
let stream = quote! {
|
||||
#(#doc_attributes)*
|
||||
#[allow(non_camel_case_types, missing_docs)]
|
||||
pub struct #name;
|
||||
#vis struct #name;
|
||||
|
||||
impl ::actix_web::dev::HttpServiceFactory for #name {
|
||||
fn register(self, __config: &mut actix_web::dev::AppService) {
|
||||
@ -543,15 +552,3 @@ pub(crate) fn with_methods(input: TokenStream) -> TokenStream {
|
||||
Err(err) => input_and_compile_error(input, err),
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts the error to a token stream and appends it to the original input.
|
||||
///
|
||||
/// Returning the original input in addition to the error is good for IDEs which can gracefully
|
||||
/// recover and show more precise errors within the macro body.
|
||||
///
|
||||
/// See <https://github.com/rust-analyzer/rust-analyzer/issues/10468> for more info.
|
||||
fn input_and_compile_error(mut item: TokenStream, err: syn::Error) -> TokenStream {
|
||||
let compile_err = TokenStream::from(err.to_compile_error());
|
||||
item.extend(compile_err);
|
||||
item
|
||||
}
|
||||
|
103
actix-web-codegen/src/scope.rs
Normal file
103
actix-web-codegen/src/scope.rs
Normal file
@ -0,0 +1,103 @@
|
||||
use proc_macro::TokenStream;
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
use quote::{quote, ToTokens as _};
|
||||
|
||||
use crate::{
|
||||
input_and_compile_error,
|
||||
route::{MethodType, RouteArgs},
|
||||
};
|
||||
|
||||
pub fn with_scope(args: TokenStream, input: TokenStream) -> TokenStream {
|
||||
match with_scope_inner(args, input.clone()) {
|
||||
Ok(stream) => stream,
|
||||
Err(err) => input_and_compile_error(input, err),
|
||||
}
|
||||
}
|
||||
|
||||
fn with_scope_inner(args: TokenStream, input: TokenStream) -> syn::Result<TokenStream> {
|
||||
if args.is_empty() {
|
||||
return Err(syn::Error::new(
|
||||
Span::call_site(),
|
||||
"missing arguments for scope macro, expected: #[scope(\"/prefix\")]",
|
||||
));
|
||||
}
|
||||
|
||||
let scope_prefix = syn::parse::<syn::LitStr>(args.clone()).map_err(|err| {
|
||||
syn::Error::new(
|
||||
err.span(),
|
||||
"argument to scope macro is not a string literal, expected: #[scope(\"/prefix\")]",
|
||||
)
|
||||
})?;
|
||||
|
||||
let scope_prefix_value = scope_prefix.value();
|
||||
|
||||
if scope_prefix_value.ends_with('/') {
|
||||
// trailing slashes cause non-obvious problems
|
||||
// it's better to point them out to developers rather than
|
||||
|
||||
return Err(syn::Error::new(
|
||||
scope_prefix.span(),
|
||||
"scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes",
|
||||
));
|
||||
}
|
||||
|
||||
let mut module = syn::parse::<syn::ItemMod>(input).map_err(|err| {
|
||||
syn::Error::new(err.span(), "#[scope] macro must be attached to a module")
|
||||
})?;
|
||||
|
||||
// modify any routing macros (method or route[s]) attached to
|
||||
// functions by prefixing them with this scope macro's argument
|
||||
if let Some((_, items)) = &mut module.content {
|
||||
for item in items {
|
||||
if let syn::Item::Fn(fun) = item {
|
||||
fun.attrs = fun
|
||||
.attrs
|
||||
.iter()
|
||||
.map(|attr| modify_attribute_with_scope(attr, &scope_prefix_value))
|
||||
.collect();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(module.to_token_stream().into())
|
||||
}
|
||||
|
||||
/// Checks if the attribute is a method type and has a route path, then modifies it.
|
||||
fn modify_attribute_with_scope(attr: &syn::Attribute, scope_path: &str) -> syn::Attribute {
|
||||
match (attr.parse_args::<RouteArgs>(), attr.clone().meta) {
|
||||
(Ok(route_args), syn::Meta::List(meta_list)) if has_allowed_methods_in_scope(attr) => {
|
||||
let modified_path = format!("{}{}", scope_path, route_args.path.value());
|
||||
|
||||
let options_tokens: Vec<TokenStream2> = route_args
|
||||
.options
|
||||
.iter()
|
||||
.map(|option| {
|
||||
quote! { ,#option }
|
||||
})
|
||||
.collect();
|
||||
|
||||
let combined_options_tokens: TokenStream2 =
|
||||
options_tokens
|
||||
.into_iter()
|
||||
.fold(TokenStream2::new(), |mut acc, ts| {
|
||||
acc.extend(std::iter::once(ts));
|
||||
acc
|
||||
});
|
||||
|
||||
syn::Attribute {
|
||||
meta: syn::Meta::List(syn::MetaList {
|
||||
tokens: quote! { #modified_path #combined_options_tokens },
|
||||
..meta_list.clone()
|
||||
}),
|
||||
..attr.clone()
|
||||
}
|
||||
}
|
||||
_ => attr.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
fn has_allowed_methods_in_scope(attr: &syn::Attribute) -> bool {
|
||||
MethodType::from_path(attr.path()).is_ok()
|
||||
|| attr.path().is_ident("route")
|
||||
|| attr.path().is_ident("ROUTE")
|
||||
}
|
200
actix-web-codegen/tests/scopes.rs
Normal file
200
actix-web-codegen/tests/scopes.rs
Normal file
@ -0,0 +1,200 @@
|
||||
use actix_web::{guard::GuardContext, http, http::header, web, App, HttpResponse, Responder};
|
||||
use actix_web_codegen::{delete, get, post, route, routes, scope};
|
||||
|
||||
pub fn image_guard(ctx: &GuardContext) -> bool {
|
||||
ctx.header::<header::Accept>()
|
||||
.map(|h| h.preference() == "image/*")
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
#[scope("/test")]
|
||||
mod scope_module {
|
||||
// ensure that imports can be brought into the scope
|
||||
use super::*;
|
||||
|
||||
#[get("/test/guard", guard = "image_guard")]
|
||||
pub async fn guard() -> impl Responder {
|
||||
HttpResponse::Ok()
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
pub async fn test() -> impl Responder {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[get("/twice-test/{value}")]
|
||||
pub async fn 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")]
|
||||
pub async fn post() -> impl Responder {
|
||||
HttpResponse::Ok().body("post works")
|
||||
}
|
||||
|
||||
#[delete("/test")]
|
||||
pub async fn delete() -> impl Responder {
|
||||
"delete works"
|
||||
}
|
||||
|
||||
#[route("/test", method = "PUT", method = "PATCH", method = "CUSTOM")]
|
||||
pub async fn multiple_shared_path() -> impl Responder {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
#[routes]
|
||||
#[head("/test1")]
|
||||
#[connect("/test2")]
|
||||
#[options("/test3")]
|
||||
#[trace("/test4")]
|
||||
pub async fn multiple_separate_paths() -> impl Responder {
|
||||
HttpResponse::Ok().finish()
|
||||
}
|
||||
|
||||
// test calling this from other mod scope with scope attribute...
|
||||
pub fn mod_common(message: String) -> impl actix_web::Responder {
|
||||
HttpResponse::Ok().body(message)
|
||||
}
|
||||
}
|
||||
|
||||
/// Scope doc string to check in cargo expand.
|
||||
#[scope("/v1")]
|
||||
mod mod_scope_v1 {
|
||||
use super::*;
|
||||
|
||||
/// Route doc string to check in cargo expand.
|
||||
#[get("/test")]
|
||||
pub async fn test() -> impl Responder {
|
||||
scope_module::mod_common("version1 works".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
#[scope("/v2")]
|
||||
mod mod_scope_v2 {
|
||||
use super::*;
|
||||
|
||||
// check to make sure non-function tokens in the scope block are preserved...
|
||||
enum TestEnum {
|
||||
Works,
|
||||
}
|
||||
|
||||
#[get("/test")]
|
||||
pub async fn test() -> impl Responder {
|
||||
// make sure this type still exists...
|
||||
let test_enum = TestEnum::Works;
|
||||
|
||||
match test_enum {
|
||||
TestEnum::Works => scope_module::mod_common("version2 works".to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_get_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::test));
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/test/test"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_get_param_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::twice));
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/test/twice-test/4"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "Twice value: 8");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_post_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::post));
|
||||
|
||||
let request = srv.request(http::Method::POST, srv.url("/test/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "post works");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn multiple_shared_path_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::multiple_shared_path));
|
||||
|
||||
let request = srv.request(http::Method::PUT, srv.url("/test/test"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::PATCH, srv.url("/test/test"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn multiple_multi_path_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::multiple_separate_paths));
|
||||
|
||||
let request = srv.request(http::Method::HEAD, srv.url("/test/test1"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::CONNECT, srv.url("/test/test2"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::OPTIONS, srv.url("/test/test3"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
||||
let request = srv.request(http::Method::TRACE, srv.url("/test/test4"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_delete_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::delete));
|
||||
|
||||
let request = srv.request(http::Method::DELETE, srv.url("/test/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "delete works");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_get_with_guard_async() {
|
||||
let srv = actix_test::start(|| App::new().service(scope_module::guard));
|
||||
|
||||
let request = srv
|
||||
.request(http::Method::GET, srv.url("/test/test/guard"))
|
||||
.insert_header(("Accept", "image/*"));
|
||||
let response = request.send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn scope_v1_v2_async() {
|
||||
let srv = actix_test::start(|| {
|
||||
App::new()
|
||||
.service(mod_scope_v1::test)
|
||||
.service(mod_scope_v2::test)
|
||||
});
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/v1/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "version1 works");
|
||||
|
||||
let request = srv.request(http::Method::GET, srv.url("/v2/test"));
|
||||
let mut response = request.send().await.unwrap();
|
||||
let body = response.body().await.unwrap();
|
||||
let body_str = String::from_utf8(body.to_vec()).unwrap();
|
||||
assert_eq!(body_str, "version2 works");
|
||||
}
|
@ -18,6 +18,11 @@ fn compile_macros() {
|
||||
t.compile_fail("tests/trybuild/routes-missing-method-fail.rs");
|
||||
t.compile_fail("tests/trybuild/routes-missing-args-fail.rs");
|
||||
|
||||
t.compile_fail("tests/trybuild/scope-on-handler.rs");
|
||||
t.compile_fail("tests/trybuild/scope-missing-args.rs");
|
||||
t.compile_fail("tests/trybuild/scope-invalid-args.rs");
|
||||
t.compile_fail("tests/trybuild/scope-trailing-slash.rs");
|
||||
|
||||
t.pass("tests/trybuild/docstring-ok.rs");
|
||||
|
||||
t.pass("tests/trybuild/test-runtime.rs");
|
||||
|
@ -20,10 +20,7 @@ error: custom attribute panicked
|
||||
13 | #[get("/{}")]
|
||||
| ^^^^^^^^^^^^^
|
||||
|
|
||||
= help: message: Wrong path pattern: "/{}" regex parse error:
|
||||
((?s-m)^/(?P<>[^/]+))$
|
||||
^
|
||||
error: empty capture group name
|
||||
= help: message: Wrong path pattern: "/{}" empty capture group names are not allowed
|
||||
|
||||
error: custom attribute panicked
|
||||
--> $DIR/route-malformed-path-fail.rs:23:1
|
||||
|
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
14
actix-web-codegen/tests/trybuild/scope-invalid-args.rs
Normal file
@ -0,0 +1,14 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
const PATH: &str = "/api";
|
||||
|
||||
#[scope(PATH)]
|
||||
mod api_const {}
|
||||
|
||||
#[scope(true)]
|
||||
mod api_bool {}
|
||||
|
||||
#[scope(123)]
|
||||
mod api_num {}
|
||||
|
||||
fn main() {}
|
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
17
actix-web-codegen/tests/trybuild/scope-invalid-args.stderr
Normal file
@ -0,0 +1,17 @@
|
||||
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-invalid-args.rs:5:9
|
||||
|
|
||||
5 | #[scope(PATH)]
|
||||
| ^^^^
|
||||
|
||||
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-invalid-args.rs:8:9
|
||||
|
|
||||
8 | #[scope(true)]
|
||||
| ^^^^
|
||||
|
||||
error: argument to scope macro is not a string literal, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-invalid-args.rs:11:9
|
||||
|
|
||||
11 | #[scope(123)]
|
||||
| ^^^
|
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-missing-args.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
#[scope]
|
||||
mod api {}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,7 @@
|
||||
error: missing arguments for scope macro, expected: #[scope("/prefix")]
|
||||
--> tests/trybuild/scope-missing-args.rs:3:1
|
||||
|
|
||||
3 | #[scope]
|
||||
| ^^^^^^^^
|
||||
|
|
||||
= note: this error originates in the attribute macro `scope` (in Nightly builds, run with -Z macro-backtrace for more info)
|
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
8
actix-web-codegen/tests/trybuild/scope-on-handler.rs
Normal file
@ -0,0 +1,8 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
#[scope("/api")]
|
||||
async fn index() -> &'static str {
|
||||
"Hello World!"
|
||||
}
|
||||
|
||||
fn main() {}
|
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
5
actix-web-codegen/tests/trybuild/scope-on-handler.stderr
Normal file
@ -0,0 +1,5 @@
|
||||
error: #[scope] macro must be attached to a module
|
||||
--> tests/trybuild/scope-on-handler.rs:4:1
|
||||
|
|
||||
4 | async fn index() -> &'static str {
|
||||
| ^^^^^
|
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
6
actix-web-codegen/tests/trybuild/scope-trailing-slash.rs
Normal file
@ -0,0 +1,6 @@
|
||||
use actix_web_codegen::scope;
|
||||
|
||||
#[scope("/api/")]
|
||||
mod api {}
|
||||
|
||||
fn main() {}
|
@ -0,0 +1,5 @@
|
||||
error: scopes should not have trailing slashes; see https://docs.rs/actix-web/4/actix_web/struct.Scope.html#avoid-trailing-slashes
|
||||
--> tests/trybuild/scope-trailing-slash.rs:3:9
|
||||
|
|
||||
3 | #[scope("/api/")]
|
||||
| ^^^^^^^
|
@ -2,14 +2,35 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 4.7.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `middleware::Identity` type.
|
||||
- Add `CustomizeResponder::add_cookie()` method.
|
||||
- Add `guard::GuardContext::app_data()` method.
|
||||
- Add `compat-routing-macros-force-pub` crate feature which (on-by-default) which, when disabled, causes handlers to inherit their attached function's visibility.
|
||||
- Add `compat` crate feature group (on-by-default) which, when disabled, helps with transitioning to some planned v5.0 breaking changes, starting only with `compat-routing-macros-force-pub`.
|
||||
- Implement `From<Box<dyn ResponseError>>` for `Error`.
|
||||
|
||||
## 4.6.0
|
||||
|
||||
### 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.
|
||||
- Add `rustls-0_23` crate feature.
|
||||
- Add `HttpServer::{bind_rustls_0_23, listen_rustls_0_23}()` builder methods.
|
||||
- Add `HttpServer::tls_handshake_timeout()` builder method for `rustls-0_22` and `rustls-0_23`.
|
||||
|
||||
### Changed
|
||||
|
||||
- Update `brotli` dependency to `6`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Avoid type confusion with `rustls` in some circumstances.
|
||||
|
||||
## 4.5.1
|
||||
|
||||
### Fixed
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.5.1"
|
||||
version = "4.7.0"
|
||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
@ -27,6 +27,7 @@ features = [
|
||||
"rustls-0_20",
|
||||
"rustls-0_21",
|
||||
"rustls-0_22",
|
||||
"rustls-0_23",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
@ -34,13 +35,21 @@ features = [
|
||||
"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",
|
||||
"unicode",
|
||||
"compat",
|
||||
]
|
||||
|
||||
# Brotli algorithm content-encoding support
|
||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||
@ -50,14 +59,15 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||
|
||||
# Routing and runtime proc macros
|
||||
macros = ["actix-macros", "actix-web-codegen"]
|
||||
macros = ["dep:actix-macros", "dep:actix-web-codegen"]
|
||||
|
||||
# Cookies support
|
||||
cookies = ["cookie"]
|
||||
cookies = ["dep:cookie"]
|
||||
|
||||
# Secure & signed cookies
|
||||
secure-cookies = ["cookies", "cookie/secure"]
|
||||
|
||||
# HTTP/2 support (including h2c).
|
||||
http2 = ["actix-http/http2"]
|
||||
|
||||
# TLS via OpenSSL
|
||||
@ -71,6 +81,8 @@ rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls
|
||||
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"]
|
||||
# TLS via Rustls v0.23
|
||||
rustls-0_23 = ["http2", "actix-http/rustls-0_23", "actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||
|
||||
# Full unicode support
|
||||
unicode = ["dep:regex", "actix-router/unicode"]
|
||||
@ -82,6 +94,14 @@ __compress = []
|
||||
# io-uring feature only available for Linux OSes.
|
||||
experimental-io-uring = ["actix-server/io-uring"]
|
||||
|
||||
# Feature group which, when disabled, helps migrate code to v5.0.
|
||||
compat = [
|
||||
"compat-routing-macros-force-pub",
|
||||
]
|
||||
|
||||
# Opt-out forwards-compatibility for handler visibility inheritance fix.
|
||||
compat-routing-macros-force-pub = ["actix-web-codegen?/compat-routing-macros-force-pub"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-macros = { version = "0.2.3", optional = true }
|
||||
@ -89,11 +109,11 @@ 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.4", default-features = false, optional = true }
|
||||
|
||||
actix-http = { version = "3.6", features = ["ws"] }
|
||||
actix-router = { version = "0.5", default-features = false, features = ["http"] }
|
||||
actix-web-codegen = { version = "4.2", optional = true }
|
||||
actix-http = { version = "3.7", features = ["ws"] }
|
||||
actix-router = { version = "0.5.3", default-features = false, features = ["http"] }
|
||||
actix-web-codegen = { version = "4.3", optional = true, default-features = false }
|
||||
|
||||
ahash = "0.8"
|
||||
bytes = "1"
|
||||
@ -122,22 +142,23 @@ 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_23"] }
|
||||
awc = { version = "3", features = ["openssl"] }
|
||||
|
||||
brotli = "3.3.3"
|
||||
brotli = "6"
|
||||
const-str = "0.5"
|
||||
core_affinity = "0.8"
|
||||
criterion = { version = "0.5", features = ["html_reports"] }
|
||||
env_logger = "0.11"
|
||||
flate2 = "1.0.13"
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
|
||||
rand = "0.8"
|
||||
rcgen = "0.12"
|
||||
rcgen = "0.13"
|
||||
rustls-pemfile = "2"
|
||||
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.23" }
|
||||
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
|
||||
zstd = "0.13"
|
||||
|
||||
|
@ -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)
|
||||
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.7.0)](https://docs.rs/actix-web/4.7.0)
|
||||
![MSRV](https://img.shields.io/badge/rustc-1.72+-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.7.0/status.svg)](https://deps.rs/crate/actix-web/4.7.0)
|
||||
<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)
|
||||
|
41
actix-web/examples/worker-cpu-pin.rs
Normal file
41
actix-web/examples/worker-cpu-pin.rs
Normal file
@ -0,0 +1,41 @@
|
||||
use std::{
|
||||
io,
|
||||
sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
},
|
||||
thread,
|
||||
};
|
||||
|
||||
use actix_web::{middleware, web, App, HttpServer};
|
||||
|
||||
async fn hello() -> &'static str {
|
||||
"Hello world!"
|
||||
}
|
||||
|
||||
#[actix_web::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
let core_ids = core_affinity::get_core_ids().unwrap();
|
||||
let n_core_ids = core_ids.len();
|
||||
let next_core_id = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
HttpServer::new(move || {
|
||||
let pin = Arc::clone(&next_core_id).fetch_add(1, Ordering::AcqRel);
|
||||
log::info!(
|
||||
"setting CPU affinity for worker {}: pinning to core {}",
|
||||
thread::current().name().unwrap(),
|
||||
pin,
|
||||
);
|
||||
core_affinity::set_for_current(core_ids[pin]);
|
||||
|
||||
App::new()
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(web::resource("/").get(hello))
|
||||
})
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.workers(n_core_ids)
|
||||
.run()
|
||||
.await
|
||||
}
|
@ -112,8 +112,8 @@ where
|
||||
/// })
|
||||
/// ```
|
||||
#[doc(alias = "manage")]
|
||||
pub fn app_data<U: 'static>(mut self, ext: U) -> Self {
|
||||
self.extensions.insert(ext);
|
||||
pub fn app_data<U: 'static>(mut self, data: U) -> Self {
|
||||
self.extensions.insert(data);
|
||||
self
|
||||
}
|
||||
|
||||
|
@ -263,8 +263,9 @@ impl ServiceFactory<ServiceRequest> for AppRoutingFactory {
|
||||
let guards = guards.borrow_mut().take().unwrap_or_default();
|
||||
let factory_fut = factory.new_service(());
|
||||
async move {
|
||||
let service = factory_fut.await?;
|
||||
Ok((path, guards, service))
|
||||
factory_fut
|
||||
.await
|
||||
.map(move |service| (path, guards, service))
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -148,7 +148,7 @@ impl AppConfig {
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn set_host(&mut self, host: &str) {
|
||||
self.host = host.to_owned();
|
||||
host.clone_into(&mut self.host);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -60,6 +60,12 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Box<dyn ResponseError>> for Error {
|
||||
fn from(value: Box<dyn ResponseError>) -> Self {
|
||||
Error { cause: value }
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Response<BoxBody> {
|
||||
fn from(err: Error) -> Response<BoxBody> {
|
||||
err.error_response().into()
|
||||
|
@ -110,6 +110,12 @@ impl<'a> GuardContext<'a> {
|
||||
pub fn header<H: Header>(&self) -> Option<H> {
|
||||
H::parse(self.req).ok()
|
||||
}
|
||||
|
||||
/// Counterpart to [HttpRequest::app_data](crate::HttpRequest::app_data).
|
||||
#[inline]
|
||||
pub fn app_data<T: 'static>(&self) -> Option<&T> {
|
||||
self.req.app_data()
|
||||
}
|
||||
}
|
||||
|
||||
/// Interface for routing guards.
|
||||
@ -512,4 +518,18 @@ mod tests {
|
||||
.to_srv_request();
|
||||
assert!(guard.check(&req.guard_ctx()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn app_data() {
|
||||
const TEST_VALUE: u32 = 42;
|
||||
let guard = fn_guard(|ctx| dbg!(ctx.app_data::<u32>()) == Some(&TEST_VALUE));
|
||||
|
||||
let req = TestRequest::default().app_data(TEST_VALUE).to_srv_request();
|
||||
assert!(guard.check(&req.guard_ctx()));
|
||||
|
||||
let req = TestRequest::default()
|
||||
.app_data(TEST_VALUE * 2)
|
||||
.to_srv_request();
|
||||
assert!(!guard.check(&req.guard_ctx()));
|
||||
}
|
||||
}
|
||||
|
@ -64,7 +64,10 @@
|
||||
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
|
||||
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
|
||||
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
|
||||
//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
|
||||
//! - `rustls` - HTTPS support via `rustls` 0.20 crate, supports `HTTP/2`
|
||||
//! - `rustls-0_21` - HTTPS support via `rustls` 0.21 crate, supports `HTTP/2`
|
||||
//! - `rustls-0_22` - HTTPS support via `rustls` 0.22 crate, supports `HTTP/2`
|
||||
//! - `rustls-0_23` - HTTPS support via `rustls` 0.23 crate, supports `HTTP/2`
|
||||
//! - `secure-cookies` - secure cookies support
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
@ -142,5 +145,6 @@ codegen_reexport!(delete);
|
||||
codegen_reexport!(trace);
|
||||
codegen_reexport!(connect);
|
||||
codegen_reexport!(options);
|
||||
codegen_reexport!(scope);
|
||||
|
||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||
|
@ -38,15 +38,6 @@ pub struct Compat<T> {
|
||||
transform: T,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
impl Compat<super::Noop> {
|
||||
pub(crate) fn noop() -> Self {
|
||||
Self {
|
||||
transform: super::Noop,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Compat<T> {
|
||||
/// Wrap a middleware to give it broader compatibility.
|
||||
pub fn new(middleware: T) -> Self {
|
||||
@ -152,7 +143,7 @@ mod tests {
|
||||
use crate::{
|
||||
dev::ServiceRequest,
|
||||
http::StatusCode,
|
||||
middleware::{self, Condition, Logger},
|
||||
middleware::{self, Condition, Identity, Logger},
|
||||
test::{self, call_service, init_service, TestRequest},
|
||||
web, App, HttpResponse,
|
||||
};
|
||||
@ -225,7 +216,7 @@ mod tests {
|
||||
async fn compat_noop_is_noop() {
|
||||
let srv = test::ok_service();
|
||||
|
||||
let mw = Compat::noop()
|
||||
let mw = Compat::new(Identity)
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
@ -141,7 +141,7 @@ mod tests {
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
|
||||
middleware::{self, ErrorHandlerResponse, ErrorHandlers, Identity},
|
||||
test::{self, TestRequest},
|
||||
web::Bytes,
|
||||
HttpResponse,
|
||||
@ -158,7 +158,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn compat_with_builtin_middleware() {
|
||||
let _ = Condition::new(true, middleware::Compat::noop());
|
||||
let _ = Condition::new(true, middleware::Compat::new(Identity));
|
||||
let _ = Condition::new(true, middleware::Logger::default());
|
||||
let _ = Condition::new(true, middleware::Compress::default());
|
||||
let _ = Condition::new(true, middleware::NormalizePath::trim());
|
||||
|
@ -2,35 +2,39 @@
|
||||
|
||||
use actix_utils::future::{ready, Ready};
|
||||
|
||||
use crate::dev::{Service, Transform};
|
||||
use crate::dev::{forward_ready, Service, Transform};
|
||||
|
||||
/// A no-op middleware that passes through request and response untouched.
|
||||
pub(crate) struct Noop;
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[non_exhaustive]
|
||||
pub struct Identity;
|
||||
|
||||
impl<S: Service<Req>, Req> Transform<S, Req> for Noop {
|
||||
impl<S: Service<Req>, Req> Transform<S, Req> for Identity {
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Transform = NoopService<S>;
|
||||
type Transform = IdentityMiddleware<S>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
#[inline]
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(NoopService { service }))
|
||||
ready(Ok(IdentityMiddleware { service }))
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub(crate) struct NoopService<S> {
|
||||
pub struct IdentityMiddleware<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S: Service<Req>, Req> Service<Req> for NoopService<S> {
|
||||
impl<S: Service<Req>, Req> Service<Req> for IdentityMiddleware<S> {
|
||||
type Response = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
crate::dev::forward_ready!(service);
|
||||
forward_ready!(service);
|
||||
|
||||
#[inline]
|
||||
fn call(&self, req: Req) -> Self::Future {
|
||||
self.service.call(req)
|
||||
}
|
@ -33,13 +33,13 @@
|
||||
//!
|
||||
//! # fn main() {
|
||||
//! # // These aren't snake_case, because they are supposed to be unit structs.
|
||||
//! # let MiddlewareA = middleware::Compress::default();
|
||||
//! # let MiddlewareB = middleware::Compress::default();
|
||||
//! # let MiddlewareC = middleware::Compress::default();
|
||||
//! # type MiddlewareA = middleware::Compress;
|
||||
//! # type MiddlewareB = middleware::Compress;
|
||||
//! # type MiddlewareC = middleware::Compress;
|
||||
//! let app = App::new()
|
||||
//! .wrap(MiddlewareA)
|
||||
//! .wrap(MiddlewareB)
|
||||
//! .wrap(MiddlewareC)
|
||||
//! .wrap(MiddlewareA::default())
|
||||
//! .wrap(MiddlewareB::default())
|
||||
//! .wrap(MiddlewareC::default())
|
||||
//! .service(service);
|
||||
//! # }
|
||||
//! ```
|
||||
@ -218,31 +218,27 @@
|
||||
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
|
||||
|
||||
mod compat;
|
||||
#[cfg(feature = "__compress")]
|
||||
mod compress;
|
||||
mod condition;
|
||||
mod default_headers;
|
||||
mod err_handlers;
|
||||
mod identity;
|
||||
mod logger;
|
||||
#[cfg(test)]
|
||||
mod noop;
|
||||
mod normalize;
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) use self::noop::Noop;
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use self::compress::Compress;
|
||||
pub use self::{
|
||||
compat::Compat,
|
||||
condition::Condition,
|
||||
default_headers::DefaultHeaders,
|
||||
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
|
||||
identity::Identity,
|
||||
logger::Logger,
|
||||
normalize::{NormalizePath, TrailingSlash},
|
||||
};
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
mod compress;
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use self::compress::Compress;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -771,7 +771,7 @@ mod tests {
|
||||
data3: web::Data<f64>| {
|
||||
assert_eq!(**data1, 10);
|
||||
assert_eq!(**data2, '*');
|
||||
let error = std::f64::EPSILON;
|
||||
let error = f64::EPSILON;
|
||||
assert!((**data3 - 1.0).abs() < error);
|
||||
HttpResponse::Ok()
|
||||
},
|
||||
|
@ -7,7 +7,7 @@ use actix_http::{
|
||||
|
||||
use crate::{HttpRequest, HttpResponse, Responder};
|
||||
|
||||
/// Allows overriding status code and headers for a [`Responder`].
|
||||
/// Allows overriding status code and headers (including cookies) for a [`Responder`].
|
||||
///
|
||||
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
|
||||
pub struct CustomizeResponder<R> {
|
||||
@ -137,6 +137,29 @@ impl<R: Responder> CustomizeResponder<R> {
|
||||
Some(&mut self.inner)
|
||||
}
|
||||
}
|
||||
|
||||
/// Appends a `cookie` to the final response.
|
||||
///
|
||||
/// # Errors
|
||||
///
|
||||
/// Final response will be an error if `cookie` cannot be converted into a valid header value.
|
||||
#[cfg(feature = "cookies")]
|
||||
pub fn add_cookie(mut self, cookie: &crate::cookie::Cookie<'_>) -> Self {
|
||||
use actix_http::header::{TryIntoHeaderValue as _, SET_COOKIE};
|
||||
|
||||
if let Some(inner) = self.inner() {
|
||||
match cookie.to_string().try_into_value() {
|
||||
Ok(val) => {
|
||||
inner.append_headers.append(SET_COOKIE, val);
|
||||
}
|
||||
Err(err) => {
|
||||
self.error = Some(err.into());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Responder for CustomizeResponder<T>
|
||||
@ -175,6 +198,7 @@ mod tests {
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
cookie::Cookie,
|
||||
http::header::{HeaderValue, CONTENT_TYPE},
|
||||
test::TestRequest,
|
||||
};
|
||||
@ -209,6 +233,22 @@ mod tests {
|
||||
to_bytes(res.into_body()).await.unwrap(),
|
||||
Bytes::from_static(b"test"),
|
||||
);
|
||||
|
||||
let res = "test"
|
||||
.to_string()
|
||||
.customize()
|
||||
.add_cookie(&Cookie::new("name", "value"))
|
||||
.respond_to(&req);
|
||||
|
||||
assert!(res.status().is_success());
|
||||
assert_eq!(
|
||||
res.cookies().collect::<Vec<Cookie<'_>>>(),
|
||||
vec![Cookie::new("name", "value")],
|
||||
);
|
||||
assert_eq!(
|
||||
to_bytes(res.into_body()).await.unwrap(),
|
||||
Bytes::from_static(b"test"),
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
|
@ -470,8 +470,9 @@ impl ServiceFactory<ServiceRequest> for ScopeFactory {
|
||||
let guards = guards.borrow_mut().take().unwrap_or_default();
|
||||
let factory_fut = factory.new_service(());
|
||||
async move {
|
||||
let service = factory_fut.await?;
|
||||
Ok((path, guards, service))
|
||||
factory_fut
|
||||
.await
|
||||
.map(move |service| (path, guards, service))
|
||||
}
|
||||
}));
|
||||
|
||||
|
@ -12,6 +12,7 @@ use std::{
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
use actix_http::TlsAcceptorConfig;
|
||||
use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response};
|
||||
@ -242,7 +243,13 @@ where
|
||||
/// time, the connection is closed.
|
||||
///
|
||||
/// By default, the handshake timeout is 3 seconds.
|
||||
#[cfg(any(feature = "openssl", feature = "rustls-0_20", feature = "rustls-0_21"))]
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
|
||||
self.config
|
||||
.lock()
|
||||
@ -270,6 +277,10 @@ where
|
||||
/// Rustls v0.20.
|
||||
/// - `actix_tls::accept::rustls_0_21::TlsStream<actix_web::rt::net::TcpStream>` when using
|
||||
/// Rustls v0.21.
|
||||
/// - `actix_tls::accept::rustls_0_22::TlsStream<actix_web::rt::net::TcpStream>` when using
|
||||
/// Rustls v0.22.
|
||||
/// - `actix_tls::accept::rustls_0_23::TlsStream<actix_web::rt::net::TcpStream>` when using
|
||||
/// Rustls v0.23.
|
||||
/// - `actix_web::rt::net::TcpStream` when no encryption is used.
|
||||
///
|
||||
/// See the `on_connect` example for additional details.
|
||||
@ -466,6 +477,25 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
|
||||
/// using Rustls v0.23.
|
||||
///
|
||||
/// See [`bind()`](Self::bind()) for more details on `addrs` argument.
|
||||
///
|
||||
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
pub fn bind_rustls_0_23<A: net::ToSocketAddrs>(
|
||||
mut self,
|
||||
addrs: A,
|
||||
config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
|
||||
) -> io::Result<Self> {
|
||||
let sockets = bind_addrs(addrs, self.backlog)?;
|
||||
for lst in sockets {
|
||||
self = self.listen_rustls_0_23_inner(lst, config.clone())?;
|
||||
}
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Resolves socket address(es) and binds server to created listener(s) for TLS connections
|
||||
/// using OpenSSL.
|
||||
///
|
||||
@ -595,7 +625,7 @@ where
|
||||
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
||||
/// v0.21.
|
||||
///
|
||||
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
|
||||
/// See [`listen()`](Self::listen()) for more details on the `lst` argument.
|
||||
///
|
||||
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
||||
#[cfg(feature = "rustls-0_21")]
|
||||
@ -712,7 +742,7 @@ where
|
||||
/// 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.
|
||||
/// 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")]
|
||||
@ -775,6 +805,72 @@ where
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Binds to existing listener for accepting incoming TLS connection requests using Rustls
|
||||
/// v0.23.
|
||||
///
|
||||
/// See [`listen()`](Self::listen()) for more details on the `lst` argument.
|
||||
///
|
||||
/// ALPN protocols "h2" and "http/1.1" are added to any configured ones.
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
pub fn listen_rustls_0_23(
|
||||
self,
|
||||
lst: net::TcpListener,
|
||||
config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
|
||||
) -> io::Result<Self> {
|
||||
self.listen_rustls_0_23_inner(lst, config)
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
fn listen_rustls_0_23_inner(
|
||||
mut self,
|
||||
lst: net::TcpListener,
|
||||
config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
|
||||
) -> io::Result<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_23_with_config(config.clone(), acceptor_config)
|
||||
})?;
|
||||
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
/// Binds to existing listener for accepting incoming TLS connection requests using OpenSSL.
|
||||
///
|
||||
/// See [`listen()`](Self::listen) for more details on the `lst` argument.
|
||||
|
@ -64,9 +64,11 @@ fn ssl_acceptor() -> openssl::ssl::SslAcceptorBuilder {
|
||||
x509::X509,
|
||||
};
|
||||
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#[cfg(feature = "openssl")]
|
||||
extern crate tls_openssl as openssl;
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
extern crate tls_rustls as rustls;
|
||||
|
||||
use std::{
|
||||
@ -34,9 +34,11 @@ const STR: &str = const_str::repeat!(S, 100);
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
fn openssl_config() -> SslAcceptor {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||
|
||||
@ -704,7 +706,7 @@ async fn test_brotli_encoding_large_openssl() {
|
||||
srv.stop().await;
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_22")]
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
mod plus_rustls {
|
||||
use std::io::BufReader;
|
||||
|
||||
@ -714,9 +716,10 @@ mod plus_rustls {
|
||||
use super::*;
|
||||
|
||||
fn tls_config() -> RustlsServerConfig {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
@ -740,7 +743,7 @@ mod plus_rustls {
|
||||
.map(char::from)
|
||||
.collect::<String>();
|
||||
|
||||
let srv = actix_test::start_with(actix_test::config().rustls_0_22(tls_config()), || {
|
||||
let srv = actix_test::start_with(actix_test::config().rustls_0_23(tls_config()), || {
|
||||
App::new().service(web::resource("/").route(web::to(|bytes: Bytes| async {
|
||||
// echo decompressed request body back in response
|
||||
HttpResponse::Ok()
|
||||
|
@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 3.5.0
|
||||
|
||||
- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features.
|
||||
- Add `awc::Connector::rustls_0_23()` constructor.
|
||||
- Fix `rustls-0_22-native-roots` root store lookup
|
||||
- Update `brotli` dependency to `6`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 3.4.0
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "3.4.0"
|
||||
version = "3.5.0"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Async HTTP and WebSocket client library"
|
||||
keywords = ["actix", "http", "framework", "async", "web"]
|
||||
@ -27,6 +27,7 @@ features = [
|
||||
"rustls-0_20",
|
||||
"rustls-0_21",
|
||||
"rustls-0_22-webpki-roots",
|
||||
"rustls-0_23-webpki-roots",
|
||||
"compress-brotli",
|
||||
"compress-gzip",
|
||||
"compress-zstd",
|
||||
@ -48,6 +49,12 @@ rustls-0_21 = ["tls-rustls-0_21", "actix-tls/rustls-0_21"]
|
||||
rustls-0_22-webpki-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-webpki-roots"]
|
||||
# TLS via Rustls v0.22 (Native roots)
|
||||
rustls-0_22-native-roots = ["tls-rustls-0_22", "actix-tls/rustls-0_22-native-roots"]
|
||||
# TLS via Rustls v0.23
|
||||
rustls-0_23 = ["tls-rustls-0_23", "actix-tls/rustls-0_23"]
|
||||
# TLS via Rustls v0.23 (WebPKI roots)
|
||||
rustls-0_23-webpki-roots = ["rustls-0_23", "actix-tls/rustls-0_23-webpki-roots"]
|
||||
# TLS via Rustls v0.23 (Native roots)
|
||||
rustls-0_23-native-roots = ["rustls-0_23", "actix-tls/rustls-0_23-native-roots"]
|
||||
|
||||
# Brotli algorithm content-encoding support
|
||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||
@ -57,7 +64,7 @@ compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||
|
||||
# Cookie parsing and cookie jar
|
||||
cookies = ["cookie"]
|
||||
cookies = ["dep:cookie"]
|
||||
|
||||
# Use `trust-dns-resolver` crate as DNS resolver
|
||||
trust-dns = ["trust-dns-resolver"]
|
||||
@ -74,9 +81,9 @@ dangerous-h2c = []
|
||||
[dependencies]
|
||||
actix-codec = "0.5"
|
||||
actix-service = "2"
|
||||
actix-http = { version = "3.6", features = ["http2", "ws"] }
|
||||
actix-http = { version = "3.7", features = ["http2", "ws"] }
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3.3", features = ["connect", "uri"] }
|
||||
actix-tls = { version = "3.4", features = ["connect", "uri"] }
|
||||
actix-utils = "3"
|
||||
|
||||
base64 = "0.22"
|
||||
@ -104,29 +111,31 @@ tls-openssl = { package = "openssl", version = "0.10.55", optional = true }
|
||||
tls-rustls-0_20 = { package = "rustls", version = "0.20", optional = true, features = ["dangerous_configuration"] }
|
||||
tls-rustls-0_21 = { package = "rustls", version = "0.21", optional = true, features = ["dangerous_configuration"] }
|
||||
tls-rustls-0_22 = { package = "rustls", version = "0.22", optional = true }
|
||||
tls-rustls-0_23 = { package = "rustls", version = "0.23", optional = true, default-features = false }
|
||||
|
||||
trust-dns-resolver = { version = "0.23", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "3.6", features = ["openssl"] }
|
||||
actix-http = { version = "3.7", features = ["openssl"] }
|
||||
actix-http-test = { version = "3", features = ["openssl"] }
|
||||
actix-server = "2"
|
||||
actix-test = { version = "0.1", features = ["openssl", "rustls-0_22"] }
|
||||
actix-tls = { version = "3.3", features = ["openssl", "rustls-0_22"] }
|
||||
actix-test = { version = "0.1", features = ["openssl", "rustls-0_23"] }
|
||||
actix-tls = { version = "3.4", features = ["openssl", "rustls-0_23"] }
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4", features = ["openssl"] }
|
||||
|
||||
brotli = "3.3.3"
|
||||
brotli = "6"
|
||||
const-str = "0.5"
|
||||
env_logger = "0.11"
|
||||
flate2 = "1.0.13"
|
||||
futures-util = { version = "0.3.17", default-features = false }
|
||||
static_assertions = "1.1"
|
||||
rcgen = "0.12"
|
||||
rcgen = "0.13"
|
||||
rustls-pemfile = "2"
|
||||
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
|
||||
zstd = "0.13"
|
||||
tls-rustls-0_23 = { package = "rustls", version = "0.23" } # add rustls 0.23 with default features to make aws_lc_rs work in tests
|
||||
|
||||
[[example]]
|
||||
name = "client"
|
||||
required-features = ["rustls-0_22-webpki-roots"]
|
||||
required-features = ["rustls-0_23-webpki-roots"]
|
||||
|
@ -5,9 +5,9 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
|
||||
[![Documentation](https://docs.rs/awc/badge.svg?version=3.4.0)](https://docs.rs/awc/3.4.0)
|
||||
[![Documentation](https://docs.rs/awc/badge.svg?version=3.5.0)](https://docs.rs/awc/3.5.0)
|
||||
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
|
||||
[![Dependency Status](https://deps.rs/crate/awc/3.4.0/status.svg)](https://deps.rs/crate/awc/3.4.0)
|
||||
[![Dependency Status](https://deps.rs/crate/awc/3.5.0/status.svg)](https://deps.rs/crate/awc/3.5.0)
|
||||
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
@ -37,6 +37,12 @@ pub struct ClientBuilder<S = (), M = ()> {
|
||||
}
|
||||
|
||||
impl ClientBuilder {
|
||||
/// Create a new ClientBuilder with default settings
|
||||
///
|
||||
/// Note: If the `rustls-0_23` feature is enabled and neither `rustls-0_23-native-roots` nor
|
||||
/// `rustls-0_23-webpki-roots` are enabled, this ClientBuilder will build without TLS. In order
|
||||
/// to enable TLS in this scenario, a custom `Connector` _must_ be added to the builder before
|
||||
/// finishing construction.
|
||||
#[allow(clippy::new_ret_no_self)]
|
||||
pub fn new() -> ClientBuilder<
|
||||
impl Service<
|
||||
|
@ -57,6 +57,10 @@ enum OurTlsConnector {
|
||||
))]
|
||||
#[allow(dead_code)] // false positive; used in build_tls
|
||||
Rustls022(std::sync::Arc<actix_tls::connect::rustls_0_22::reexports::ClientConfig>),
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
#[allow(dead_code)] // false positive; used in build_tls
|
||||
Rustls023(std::sync::Arc<actix_tls::connect::rustls_0_23::reexports::ClientConfig>),
|
||||
}
|
||||
|
||||
/// Manages HTTP client network connectivity.
|
||||
@ -80,6 +84,14 @@ pub struct Connector<T> {
|
||||
}
|
||||
|
||||
impl Connector<()> {
|
||||
/// Create a new connector with default TLS settings
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// - When the `rustls-0_23-webpki-roots` or `rustls-0_23-native-roots` features are enabled
|
||||
/// and no default crypto provider has been loaded, this method will panic.
|
||||
/// - When the `rustls-0_23-native-roots` or `rustls-0_22-native-roots` features are enabled
|
||||
/// and the runtime system has no native root certificates, this method will panic.
|
||||
#[allow(clippy::new_ret_no_self, clippy::let_unit_value)]
|
||||
pub fn new() -> Connector<
|
||||
impl Service<
|
||||
@ -96,10 +108,31 @@ impl Connector<()> {
|
||||
}
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-webpki-roots"))] {
|
||||
/// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols.
|
||||
if #[cfg(any(feature = "rustls-0_23-webpki-roots", feature = "rustls-0_23-native-roots"))] {
|
||||
/// Build TLS connector with Rustls v0.23, based on supplied ALPN protocols.
|
||||
///
|
||||
/// Note that if other TLS crate features are enabled, Rustls v0.22 will be used.
|
||||
/// Note that if other TLS crate features are enabled, Rustls v0.23 will be used.
|
||||
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
|
||||
use actix_tls::connect::rustls_0_23::{self, reexports::ClientConfig};
|
||||
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "rustls-0_23-webpki-roots")] {
|
||||
let certs = rustls_0_23::webpki_roots_cert_store();
|
||||
} else if #[cfg(feature = "rustls-0_23-native-roots")] {
|
||||
let certs = rustls_0_23::native_roots_cert_store().expect("Failed to find native root certificates");
|
||||
}
|
||||
}
|
||||
|
||||
let mut config = ClientConfig::builder()
|
||||
.with_root_certificates(certs)
|
||||
.with_no_client_auth();
|
||||
|
||||
config.alpn_protocols = protocols;
|
||||
|
||||
OurTlsConnector::Rustls023(std::sync::Arc::new(config))
|
||||
}
|
||||
} else if #[cfg(any(feature = "rustls-0_22-webpki-roots", feature = "rustls-0_22-native-roots"))] {
|
||||
/// Build TLS connector with Rustls v0.22, based on supplied ALPN protocols.
|
||||
fn build_tls(protocols: Vec<Vec<u8>>) -> OurTlsConnector {
|
||||
use actix_tls::connect::rustls_0_22::{self, reexports::ClientConfig};
|
||||
|
||||
@ -107,7 +140,7 @@ impl Connector<()> {
|
||||
if #[cfg(feature = "rustls-0_22-webpki-roots")] {
|
||||
let certs = rustls_0_22::webpki_roots_cert_store();
|
||||
} else if #[cfg(feature = "rustls-0_22-native-roots")] {
|
||||
let certs = rustls_0_22::native_roots_cert_store();
|
||||
let certs = rustls_0_22::native_roots_cert_store().expect("Failed to find native root certificates");
|
||||
}
|
||||
}
|
||||
|
||||
@ -167,7 +200,8 @@ impl Connector<()> {
|
||||
OurTlsConnector::OpensslBuilder(ssl)
|
||||
}
|
||||
} else {
|
||||
/// Provides an empty TLS connector when no TLS feature is enabled.
|
||||
/// Provides an empty TLS connector when no TLS feature is enabled, or when only the
|
||||
/// `rustls-0_23` crate feature is enabled.
|
||||
fn build_tls(_: Vec<Vec<u8>>) -> OurTlsConnector {
|
||||
OurTlsConnector::None
|
||||
}
|
||||
@ -278,6 +312,24 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets custom Rustls v0.23 `ClientConfig` instance.
|
||||
///
|
||||
/// In order to enable ALPN, set the `.alpn_protocols` field on the ClientConfig to the
|
||||
/// following:
|
||||
///
|
||||
/// ```no_run
|
||||
/// vec![b"h2".to_vec(), b"http/1.1".to_vec()]
|
||||
/// # ;
|
||||
/// ```
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
pub fn rustls_0_23(
|
||||
mut self,
|
||||
connector: std::sync::Arc<actix_tls::connect::rustls_0_23::reexports::ClientConfig>,
|
||||
) -> Self {
|
||||
self.tls = OurTlsConnector::Rustls023(connector);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets maximum supported HTTP major version.
|
||||
///
|
||||
/// Supported versions are HTTP/1.1 and HTTP/2.
|
||||
@ -588,6 +640,40 @@ where
|
||||
|
||||
Some(actix_service::boxed::rc_service(tls_service))
|
||||
}
|
||||
|
||||
#[cfg(feature = "rustls-0_23")]
|
||||
OurTlsConnector::Rustls023(tls) => {
|
||||
const H2: &[u8] = b"h2";
|
||||
|
||||
use actix_tls::connect::rustls_0_23::{reexports::AsyncTlsStream, TlsConnector};
|
||||
|
||||
#[allow(non_local_definitions)]
|
||||
impl<Io: ConnectionIo> IntoConnectionIo for TcpConnection<Uri, AsyncTlsStream<Io>> {
|
||||
fn into_connection_io(self) -> (Box<dyn ConnectionIo>, Protocol) {
|
||||
let sock = self.into_parts().0;
|
||||
let h2 = sock
|
||||
.get_ref()
|
||||
.1
|
||||
.alpn_protocol()
|
||||
.map_or(false, |protos| protos.windows(2).any(|w| w == H2));
|
||||
if h2 {
|
||||
(Box::new(sock), Protocol::Http2)
|
||||
} else {
|
||||
(Box::new(sock), Protocol::Http1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let handshake_timeout = self.config.handshake_timeout;
|
||||
|
||||
let tls_service = TlsConnectorService {
|
||||
tcp_service: tcp_service_inner,
|
||||
tls_service: TlsConnector::service(tls),
|
||||
timeout: handshake_timeout,
|
||||
};
|
||||
|
||||
Some(actix_service::boxed::rc_service(tls_service))
|
||||
}
|
||||
};
|
||||
|
||||
let tcp_config = self.config.no_disconnect_timeout();
|
||||
@ -649,6 +735,17 @@ where
|
||||
|
||||
/// service for establish tcp connection and do client tls handshake.
|
||||
/// operation is canceled when timeout limit reached.
|
||||
#[cfg(any(
|
||||
feature = "dangerous-h2c",
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22-webpki-roots",
|
||||
feature = "rustls-0_22-native-roots",
|
||||
feature = "rustls-0_23",
|
||||
feature = "rustls-0_23-webpki-roots",
|
||||
feature = "rustls-0_23-native-roots"
|
||||
))]
|
||||
struct TlsConnectorService<Tcp, Tls> {
|
||||
/// TCP connection is canceled on `TcpConnectorInnerService`'s timeout setting.
|
||||
tcp_service: Tcp,
|
||||
@ -659,6 +756,15 @@ struct TlsConnectorService<Tcp, Tls> {
|
||||
timeout: Duration,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "dangerous-h2c",
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22-webpki-roots",
|
||||
feature = "rustls-0_22-native-roots",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
impl<Tcp, Tls, IO> Service<Connect> for TlsConnectorService<Tcp, Tls>
|
||||
where
|
||||
Tcp:
|
||||
@ -974,7 +1080,7 @@ mod resolver {
|
||||
|
||||
// resolver struct is cached in thread local so new clients can reuse the existing instance
|
||||
thread_local! {
|
||||
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = RefCell::new(None);
|
||||
static TRUST_DNS_RESOLVER: RefCell<Option<Resolver>> = const { RefCell::new(None) };
|
||||
}
|
||||
|
||||
// get from thread local or construct a new trust-dns resolver.
|
||||
|
@ -13,9 +13,11 @@ use openssl::{
|
||||
};
|
||||
|
||||
fn tls_config() -> SslAcceptor {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||
|
||||
|
@ -1,6 +1,6 @@
|
||||
#![cfg(feature = "rustls-0_22-webpki-roots")]
|
||||
#![cfg(feature = "rustls-0_23-webpki-roots")]
|
||||
|
||||
extern crate tls_rustls_0_22 as rustls;
|
||||
extern crate tls_rustls_0_23 as rustls;
|
||||
|
||||
use std::{
|
||||
io::BufReader,
|
||||
@ -13,7 +13,7 @@ use std::{
|
||||
use actix_http::HttpService;
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::{fn_service, map_config, ServiceFactoryExt};
|
||||
use actix_tls::connect::rustls_0_22::webpki_roots_cert_store;
|
||||
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
|
||||
use actix_utils::future::ok;
|
||||
use actix_web::{dev::AppConfig, http::Version, web, App, HttpResponse};
|
||||
use rustls::{
|
||||
@ -23,9 +23,10 @@ use rustls::{
|
||||
use rustls_pemfile::{certs, pkcs8_private_keys};
|
||||
|
||||
fn tls_config() -> ServerConfig {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert_file = &mut BufReader::new(cert_file.as_bytes());
|
||||
let key_file = &mut BufReader::new(key_file.as_bytes());
|
||||
@ -83,7 +84,7 @@ mod danger {
|
||||
}
|
||||
|
||||
fn supported_verify_schemes(&self) -> Vec<rustls::SignatureScheme> {
|
||||
rustls::crypto::ring::default_provider()
|
||||
rustls::crypto::aws_lc_rs::default_provider()
|
||||
.signature_verification_algorithms
|
||||
.supported_schemes()
|
||||
}
|
||||
@ -107,7 +108,7 @@ async fn test_connection_reuse_h2() {
|
||||
App::new().service(web::resource("/").route(web::to(HttpResponse::Ok))),
|
||||
|_| AppConfig::default(),
|
||||
))
|
||||
.rustls_0_22(tls_config())
|
||||
.rustls_0_23(tls_config())
|
||||
.map_err(|_| ()),
|
||||
)
|
||||
})
|
||||
@ -126,7 +127,7 @@ async fn test_connection_reuse_h2() {
|
||||
.set_certificate_verifier(Arc::new(danger::NoCertificateVerification));
|
||||
|
||||
let client = awc::Client::builder()
|
||||
.connector(awc::Connector::new().rustls_0_22(Arc::new(config)))
|
||||
.connector(awc::Connector::new().rustls_0_23(Arc::new(config)))
|
||||
.finish();
|
||||
|
||||
// req 1
|
||||
|
@ -19,9 +19,11 @@ use openssl::{
|
||||
};
|
||||
|
||||
fn tls_config() -> SslAcceptor {
|
||||
let cert = rcgen::generate_simple_self_signed(vec!["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.serialize_pem().unwrap();
|
||||
let key_file = cert.serialize_private_key_pem();
|
||||
let rcgen::CertifiedKey { cert, key_pair } =
|
||||
rcgen::generate_simple_self_signed(["localhost".to_owned()]).unwrap();
|
||||
let cert_file = cert.pem();
|
||||
let key_file = key_pair.serialize_pem();
|
||||
|
||||
let cert = X509::from_pem(cert_file.as_bytes()).unwrap();
|
||||
let key = PKey::private_key_from_pem(key_file.as_bytes()).unwrap();
|
||||
|
||||
|
57
justfile
57
justfile
@ -4,16 +4,63 @@ _list:
|
||||
# Format workspace.
|
||||
fmt:
|
||||
cargo +nightly fmt
|
||||
npx -y prettier --write $(fd --type=file --hidden --extension=md --extension=yml)
|
||||
fd --hidden --type=file --extension=md --extension=yml --exec-batch npx -y prettier --write
|
||||
|
||||
# Downgrade dev-dependencies necessary to run MSRV checks/tests.
|
||||
[private]
|
||||
downgrade-for-msrv:
|
||||
cargo update -p=clap --precise=4.4.18
|
||||
|
||||
msrv := ```
|
||||
cargo metadata --format-version=1 \
|
||||
| jq -r 'first(.packages[] | select(.source == null and .rust_version)) | .rust_version' \
|
||||
| sed -E 's/^1\.([0-9]{2})$/1\.\1\.0/'
|
||||
```
|
||||
msrv_rustup := "+" + msrv
|
||||
|
||||
non_linux_all_features_list := ```
|
||||
cargo metadata --format-version=1 \
|
||||
| jq '.packages[] | select(.source == null) | .features | keys' \
|
||||
| jq -r --slurp \
|
||||
--arg exclusions "tokio-uring,io-uring,experimental-io-uring" \
|
||||
'add | unique | . - ($exclusions | split(",")) | join(",")'
|
||||
```
|
||||
|
||||
all_crate_features := if os() == "linux" {
|
||||
"--all-features"
|
||||
} else {
|
||||
"--features='" + non_linux_all_features_list + "'"
|
||||
}
|
||||
|
||||
# Run Clippy over workspace.
|
||||
clippy toolchain="":
|
||||
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
|
||||
|
||||
# Test workspace using MSRV.
|
||||
test-msrv: downgrade-for-msrv (test msrv_rustup)
|
||||
|
||||
# Test workspace code.
|
||||
test toolchain="":
|
||||
cargo {{ toolchain }} test --lib --tests -p=actix-web-codegen --all-features
|
||||
cargo {{ toolchain }} test --lib --tests -p=actix-multipart-derive --all-features
|
||||
cargo {{ toolchain }} nextest run -p=actix-router --no-default-features
|
||||
cargo {{ toolchain }} nextest run --workspace --exclude=actix-web-codegen --exclude=actix-multipart-derive {{ all_crate_features }} --filter-expr="not test(test_reading_deflate_encoding_large_random_rustls)"
|
||||
|
||||
# Test workspace docs.
|
||||
test-docs toolchain="": && doc
|
||||
cargo {{ toolchain }} test --doc --workspace {{ all_crate_features }} --no-fail-fast -- --nocapture
|
||||
|
||||
# Test workspace.
|
||||
test-all toolchain="": (test toolchain) (test-docs toolchain)
|
||||
|
||||
# Document crates in workspace.
|
||||
doc:
|
||||
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl
|
||||
doc *args:
|
||||
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --no-deps --workspace {{ all_crate_features }} {{ args }}
|
||||
|
||||
# Document crates in workspace and watch for changes.
|
||||
doc-watch:
|
||||
RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl --open
|
||||
cargo watch -- RUSTDOCFLAGS="--cfg=docsrs" cargo +nightly doc --no-deps --workspace --features=rustls,openssl
|
||||
@just doc --open
|
||||
cargo watch -- just doc
|
||||
|
||||
# Update READMEs from crate root documentation.
|
||||
update-readmes: && fmt
|
||||
|
Loading…
Reference in New Issue
Block a user