mirror of
https://github.com/fafhrd91/actix-web
synced 2025-07-04 18:06:23 +02:00
Compare commits
35 Commits
codegen-v4
...
http-v3.8.
Author | SHA1 | Date | |
---|---|---|---|
4222f92bd3 | |||
d92a73eacd | |||
c612b5ce94 | |||
cbb55ba27d | |||
643d64581a | |||
66905efd7b | |||
c076e34b5d | |||
3ecaff5f5b | |||
fa74ab3dfb | |||
188206a903 | |||
0ce488e57a | |||
132b84d3b1 | |||
cc5030c542 | |||
cd301a6932 | |||
4c4c279938 | |||
0fd85bae2a | |||
9b3de1f1fe | |||
9553e7afff | |||
d9579cf58a | |||
7a2313cc4b | |||
2ee92d778e | |||
59e42c1446 | |||
53086a90a6 | |||
7f529e35b2 | |||
4908fd7dea | |||
a2b9823d9d | |||
da56de4556 | |||
758ae1dac1 | |||
37577dcb89 | |||
8b8eb4eae1 | |||
22593a1532 | |||
f7646bcc48 | |||
8018983a68 | |||
266834cf7c | |||
40e1034566 |
@ -1,10 +0,0 @@
|
||||
[alias]
|
||||
lint = "clippy --workspace --all-targets -- -Dclippy::todo"
|
||||
lint-all = "clippy --workspace --all-features --all-targets -- -Dclippy::todo"
|
||||
|
||||
# lib checking
|
||||
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 --depth=4 --skip=__compress,experimental-io-uring check"
|
||||
ci-check-all-feature-powerset-linux="hack --workspace --feature-powerset --depth=4 --skip=__compress check"
|
26
.github/workflows/ci-post-merge.yml
vendored
26
.github/workflows/ci-post-merge.yml
vendored
@ -44,20 +44,20 @@ jobs:
|
||||
echo "RUSTFLAGS=-C target-feature=+crt-static" >> $GITHUB_ENV
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
run: just check-min
|
||||
|
||||
- name: check default
|
||||
run: cargo ci-check-default
|
||||
run: just check-default
|
||||
|
||||
- name: tests
|
||||
timeout-minutes: 60
|
||||
@ -76,16 +76,16 @@ jobs:
|
||||
- name: Free Disk Space
|
||||
run: ./scripts/free-disk-space.sh
|
||||
|
||||
- name: Setup mold linker
|
||||
uses: rui314/setup-mold@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
|
||||
- name: Install cargo-hack
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
- name: Install just, cargo-hack
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: cargo-hack
|
||||
tool: just,cargo-hack
|
||||
|
||||
- name: check feature combinations
|
||||
run: cargo ci-check-all-feature-powerset
|
||||
|
||||
- name: check feature combinations
|
||||
run: cargo ci-check-all-feature-powerset-linux
|
||||
- name: Check feature combinations
|
||||
run: just check-feature-combinations
|
||||
|
14
.github/workflows/ci.yml
vendored
14
.github/workflows/ci.yml
vendored
@ -59,12 +59,12 @@ jobs:
|
||||
uses: rui314/setup-mold@v1
|
||||
|
||||
- name: Install Rust (${{ matrix.version.name }})
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: ${{ matrix.version.version }}
|
||||
|
||||
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
|
||||
|
||||
@ -73,10 +73,10 @@ jobs:
|
||||
run: just downgrade-for-msrv
|
||||
|
||||
- name: check minimal
|
||||
run: cargo ci-check-min
|
||||
run: just check-min
|
||||
|
||||
- name: check default
|
||||
run: cargo ci-check-default
|
||||
run: just check-default
|
||||
|
||||
- name: tests
|
||||
timeout-minutes: 60
|
||||
@ -92,7 +92,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
@ -108,12 +108,12 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: just
|
||||
|
||||
|
17
.github/workflows/coverage.yml
vendored
17
.github/workflows/coverage.yml
vendored
@ -17,21 +17,22 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
components: llvm-tools-preview
|
||||
toolchain: nightly
|
||||
components: llvm-tools
|
||||
|
||||
- name: Install just,cargo-llvm-cov
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
- name: Install just, cargo-llvm-cov, cargo-nextest
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: just,cargo-llvm-cov
|
||||
tool: just,cargo-llvm-cov,cargo-nextest
|
||||
|
||||
- name: Generate code coverage
|
||||
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
|
||||
run: just test-coverage-codecov
|
||||
|
||||
- name: Upload coverage to Codecov
|
||||
uses: codecov/codecov-action@v4.4.1
|
||||
uses: codecov/codecov-action@v4.5.0
|
||||
with:
|
||||
files: codecov.json
|
||||
fail_ci_if_error: true
|
||||
|
35
.github/workflows/lint.yml
vendored
35
.github/workflows/lint.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rustfmt
|
||||
@ -36,7 +36,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
components: clippy
|
||||
|
||||
@ -55,7 +55,7 @@ jobs:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly
|
||||
components: rust-docs
|
||||
@ -65,6 +65,29 @@ jobs:
|
||||
RUSTDOCFLAGS: -D warnings
|
||||
run: cargo +nightly doc --no-deps --workspace --all-features
|
||||
|
||||
check-external-types:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust (nightly-2024-05-01)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly-2024-05-01
|
||||
|
||||
- name: Install just
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: just
|
||||
|
||||
- name: Install cargo-check-external-types
|
||||
uses: taiki-e/cache-cargo-install-action@v2.0.1
|
||||
with:
|
||||
tool: cargo-check-external-types
|
||||
|
||||
- name: check external types
|
||||
run: just check-external-types-all +nightly-2024-05-01
|
||||
|
||||
public-api-diff:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
@ -76,13 +99,13 @@ jobs:
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
|
||||
- name: Install Rust (nightly-2024-06-07)
|
||||
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
|
||||
with:
|
||||
toolchain: nightly-2024-06-07
|
||||
|
||||
- name: Install cargo-public-api
|
||||
uses: taiki-e/install-action@v2.34.0
|
||||
uses: taiki-e/install-action@v2.39.1
|
||||
with:
|
||||
tool: cargo-public-api
|
||||
|
||||
|
@ -2,6 +2,9 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.6.6
|
||||
|
||||
- Update `tokio-uring` dependency to `0.4`.
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 0.6.5
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.5"
|
||||
version = "0.6.6"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -13,9 +13,14 @@ categories = ["asynchronous", "web-programming::http-server"]
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "actix_files"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_http::*",
|
||||
"actix_service::*",
|
||||
"actix_web::*",
|
||||
"http::*",
|
||||
"mime::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||
@ -40,8 +45,8 @@ v_htmlescape = "0.15.5"
|
||||
|
||||
# experimental-io-uring
|
||||
[target.'cfg(target_os = "linux")'.dependencies]
|
||||
tokio-uring = { version = "0.4", optional = true, features = ["bytes"] }
|
||||
actix-server = { version = "2.2", optional = true } # ensure matching tokio-uring versions
|
||||
tokio-uring = { version = "0.5", optional = true, features = ["bytes"] }
|
||||
actix-server = { version = "2.4", optional = true } # ensure matching tokio-uring versions
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.7"
|
||||
|
@ -3,11 +3,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.5)
|
||||
[](https://docs.rs/actix-files/0.6.6)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.5)
|
||||
[](https://deps.rs/crate/actix-files/0.6.6)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -18,9 +18,17 @@ edition = "2021"
|
||||
[package.metadata.docs.rs]
|
||||
features = []
|
||||
|
||||
[lib]
|
||||
name = "actix_http_test"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_codec::*",
|
||||
"actix_http::*",
|
||||
"actix_server::*",
|
||||
"awc::*",
|
||||
"bytes::*",
|
||||
"futures_core::*",
|
||||
"http::*",
|
||||
"tokio::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
@ -1,7 +1,5 @@
|
||||
# `actix-http-test`
|
||||
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
@ -14,3 +12,9 @@
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Various helpers for Actix applications to use during testing.
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 3.8.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `error::InvalidStatusCode` re-export.
|
||||
|
||||
## 3.7.0
|
||||
|
||||
### Added
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.7.0"
|
||||
version = "3.8.0"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -34,51 +34,72 @@ features = [
|
||||
"compress-zstd",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "actix_http"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_codec::*",
|
||||
"actix_service::*",
|
||||
"actix_tls::*",
|
||||
"actix_utils::*",
|
||||
"bytes::*",
|
||||
"bytestring::*",
|
||||
"encoding_rs::*",
|
||||
"futures_core::*",
|
||||
"h2::*",
|
||||
"http::*",
|
||||
"httparse::*",
|
||||
"language_tags::*",
|
||||
"mime::*",
|
||||
"openssl::*",
|
||||
"rustls::*",
|
||||
"tokio_util::*",
|
||||
"tokio::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
# HTTP/2 protocol support
|
||||
http2 = ["h2"]
|
||||
http2 = ["dep:h2"]
|
||||
|
||||
# WebSocket protocol implementation
|
||||
ws = [
|
||||
"local-channel",
|
||||
"base64",
|
||||
"rand",
|
||||
"sha1",
|
||||
"dep:local-channel",
|
||||
"dep:base64",
|
||||
"dep:rand",
|
||||
"dep:sha1",
|
||||
]
|
||||
|
||||
# TLS via OpenSSL
|
||||
openssl = ["actix-tls/accept", "actix-tls/openssl"]
|
||||
openssl = ["__tls", "actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# TLS via Rustls v0.20
|
||||
rustls = ["rustls-0_20"]
|
||||
rustls = ["__tls", "rustls-0_20"]
|
||||
|
||||
# TLS via Rustls v0.20
|
||||
rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
|
||||
rustls-0_20 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_20"]
|
||||
|
||||
# TLS via Rustls v0.21
|
||||
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
|
||||
rustls-0_21 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_21"]
|
||||
|
||||
# TLS via Rustls v0.22
|
||||
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
|
||||
rustls-0_22 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_22"]
|
||||
|
||||
# TLS via Rustls v0.23
|
||||
rustls-0_23 = ["actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||
rustls-0_23 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||
|
||||
# Compression codecs
|
||||
compress-brotli = ["__compress", "brotli"]
|
||||
compress-gzip = ["__compress", "flate2"]
|
||||
compress-zstd = ["__compress", "zstd"]
|
||||
compress-brotli = ["__compress", "dep:brotli"]
|
||||
compress-gzip = ["__compress", "dep:flate2"]
|
||||
compress-zstd = ["__compress", "dep:zstd"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and checking feature status.
|
||||
# Don't rely on these whatsoever. They are semver-exempt and may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
# Internal (PRIVATE!) features used to aid checking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__tls = []
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2"
|
||||
actix-codec = "0.5"
|
||||
@ -106,7 +127,7 @@ tokio-util = { version = "0.7", features = ["io", "codec"] }
|
||||
tracing = { version = "0.1.30", default-features = false, features = ["log"] }
|
||||
|
||||
# http2
|
||||
h2 = { version = "0.3.24", optional = true }
|
||||
h2 = { version = "0.3.26", optional = true }
|
||||
|
||||
# websockets
|
||||
local-channel = { version = "0.1", optional = true }
|
||||
|
@ -5,11 +5,11 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.7.0)
|
||||
[](https://docs.rs/actix-http/3.8.0)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.7.0)
|
||||
[](https://deps.rs/crate/actix-http/3.8.0)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
|
||||
|
||||
use derive_more::{Display, Error, From};
|
||||
pub use http::Error as HttpError;
|
||||
pub use http::{status::InvalidStatusCode, Error as HttpError};
|
||||
use http::{uri::InvalidUri, StatusCode};
|
||||
|
||||
use crate::{body::BoxBody, Response};
|
||||
|
@ -6,10 +6,10 @@
|
||||
//! | ------------------- | ------------------------------------------- |
|
||||
//! | `http2` | HTTP/2 support via [h2]. |
|
||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||
//! | `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. |
|
||||
//! | `rustls-0_20` | 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. |
|
||||
@ -61,13 +61,7 @@ pub mod ws;
|
||||
|
||||
#[allow(deprecated)]
|
||||
pub use self::payload::PayloadStream;
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[cfg(feature = "__tls")]
|
||||
pub use self::service::TlsAcceptorConfig;
|
||||
pub use self::{
|
||||
builder::HttpServiceBuilder,
|
||||
|
@ -241,25 +241,13 @@ where
|
||||
}
|
||||
|
||||
/// Configuration options used when accepting TLS connection.
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[cfg(feature = "__tls")]
|
||||
#[derive(Debug, Default)]
|
||||
pub struct TlsAcceptorConfig {
|
||||
pub(crate) handshake_timeout: Option<std::time::Duration>,
|
||||
}
|
||||
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[cfg(feature = "__tls")]
|
||||
impl TlsAcceptorConfig {
|
||||
/// Set TLS handshake timeout duration.
|
||||
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {
|
||||
|
@ -16,6 +16,21 @@ edition = "2021"
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
all-features = true
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_http::*",
|
||||
"actix_multipart_derive::*",
|
||||
"actix_utils::*",
|
||||
"actix_web::*",
|
||||
"bytes::*",
|
||||
"futures_core::*",
|
||||
"mime::*",
|
||||
"serde_json::*",
|
||||
"serde_plain::*",
|
||||
"serde::*",
|
||||
"tempfile::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["tempfile", "derive"]
|
||||
derive = ["actix-multipart-derive"]
|
||||
|
@ -1,7 +1,5 @@
|
||||
# `actix-multipart`
|
||||
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
@ -15,18 +13,11 @@
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
## Example
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Dependencies:
|
||||
Multipart form support for Actix Web.
|
||||
|
||||
```toml
|
||||
[dependencies]
|
||||
actix-multipart = "0.6"
|
||||
actix-web = "4.5"
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
```
|
||||
|
||||
Code:
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
use actix_web::{post, App, HttpServer, Responder};
|
||||
@ -63,6 +54,10 @@ async fn main() -> std::io::Result<()> {
|
||||
}
|
||||
```
|
||||
|
||||
<!-- cargo-rdme end -->
|
||||
|
||||
[More available in the examples repo →](https://github.com/actix/examples/tree/master/forms/multipart)
|
||||
|
||||
Curl request :
|
||||
|
||||
```bash
|
||||
@ -71,7 +66,3 @@ curl -v --request POST \
|
||||
-F 'json={"name": "Cargo.lock"};type=application/json' \
|
||||
-F file=@./Cargo.lock
|
||||
```
|
||||
|
||||
### Examples
|
||||
|
||||
https://github.com/actix/examples/tree/master/forms/multipart
|
||||
|
@ -33,6 +33,14 @@ pub trait FieldReader<'t>: Sized + Any {
|
||||
type Future: Future<Output = Result<Self, MultipartError>>;
|
||||
|
||||
/// The form will call this function to handle the field.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// When reading the `field` payload using its `Stream` implementation, polling (manually or via
|
||||
/// `next()`/`try_next()`) may panic after the payload is exhausted. If this is a problem for
|
||||
/// your implementation of this method, you should [`fuse()`] the `Field` first.
|
||||
///
|
||||
/// [`fuse()`]: futures_util::stream::StreamExt::fuse()
|
||||
fn read_field(req: &'t HttpRequest, field: Field, limits: &'t mut Limits) -> Self::Future;
|
||||
}
|
||||
|
||||
@ -396,11 +404,20 @@ mod tests {
|
||||
use actix_http::encoding::Decoder;
|
||||
use actix_multipart_rfc7578::client::multipart;
|
||||
use actix_test::TestServer;
|
||||
use actix_web::{dev::Payload, http::StatusCode, web, App, HttpResponse, Responder};
|
||||
use actix_web::{
|
||||
dev::Payload, http::StatusCode, web, App, HttpRequest, HttpResponse, Resource, Responder,
|
||||
};
|
||||
use awc::{Client, ClientResponse};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use futures_util::TryStreamExt as _;
|
||||
|
||||
use super::MultipartForm;
|
||||
use crate::form::{bytes::Bytes, tempfile::TempFile, text::Text, MultipartFormConfig};
|
||||
use crate::{
|
||||
form::{
|
||||
bytes::Bytes, tempfile::TempFile, text::Text, FieldReader, Limits, MultipartFormConfig,
|
||||
},
|
||||
Field, MultipartError,
|
||||
};
|
||||
|
||||
pub async fn send_form(
|
||||
srv: &TestServer,
|
||||
@ -734,4 +751,49 @@ mod tests {
|
||||
let response = send_form(&srv, form, "/").await;
|
||||
assert_eq!(response.status(), StatusCode::BAD_REQUEST);
|
||||
}
|
||||
|
||||
#[should_panic(expected = "called `Result::unwrap()` on an `Err` value: Connect(Disconnected)")]
|
||||
#[actix_web::test]
|
||||
async fn field_try_next_panic() {
|
||||
#[derive(Debug)]
|
||||
struct NullSink;
|
||||
|
||||
impl<'t> FieldReader<'t> for NullSink {
|
||||
type Future = LocalBoxFuture<'t, Result<Self, MultipartError>>;
|
||||
|
||||
fn read_field(
|
||||
_: &'t HttpRequest,
|
||||
mut field: Field,
|
||||
_limits: &'t mut Limits,
|
||||
) -> Self::Future {
|
||||
Box::pin(async move {
|
||||
// exhaust field stream
|
||||
while let Some(_chunk) = field.try_next().await? {}
|
||||
|
||||
// poll again, crash
|
||||
let _post = field.try_next().await;
|
||||
|
||||
Ok(Self)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(MultipartForm)]
|
||||
struct NullSinkForm {
|
||||
foo: NullSink,
|
||||
}
|
||||
|
||||
async fn null_sink(_form: MultipartForm<NullSinkForm>) -> impl Responder {
|
||||
"unreachable"
|
||||
}
|
||||
|
||||
let srv = actix_test::start(|| App::new().service(Resource::new("/").post(null_sink)));
|
||||
|
||||
let mut form = multipart::Form::default();
|
||||
form.add_text("foo", "data is not important to this test");
|
||||
|
||||
// panics with Err(Connect(Disconnected)) due to form NullSink panic
|
||||
let _res = send_form(&srv, form, "/").await;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
//! Multipart form support for Actix Web.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use actix_web::{post, App, HttpServer, Responder};
|
||||
//!
|
||||
|
@ -465,7 +465,12 @@ impl Stream for Field {
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
let this = self.get_mut();
|
||||
let mut inner = this.inner.borrow_mut();
|
||||
if let Some(mut buffer) = inner.payload.as_ref().unwrap().get_mut(&this.safety) {
|
||||
if let Some(mut buffer) = inner
|
||||
.payload
|
||||
.as_ref()
|
||||
.expect("Field should not be polled after completion")
|
||||
.get_mut(&this.safety)
|
||||
{
|
||||
// check safety and poll read payload to buffer.
|
||||
buffer.poll_stream(cx)?;
|
||||
} else if !this.safety.is_clean() {
|
||||
@ -496,6 +501,7 @@ impl fmt::Debug for Field {
|
||||
}
|
||||
|
||||
struct InnerField {
|
||||
/// Payload is initialized as Some and is `take`n when the field stream finishes.
|
||||
payload: Option<PayloadRef>,
|
||||
boundary: String,
|
||||
eof: bool,
|
||||
@ -643,7 +649,12 @@ impl InnerField {
|
||||
return Poll::Ready(None);
|
||||
}
|
||||
|
||||
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) {
|
||||
let result = if let Some(mut payload) = self
|
||||
.payload
|
||||
.as_ref()
|
||||
.expect("Field should not be polled after completion")
|
||||
.get_mut(s)
|
||||
{
|
||||
if !self.eof {
|
||||
let res = if let Some(ref mut len) = self.length {
|
||||
InnerField::read_len(&mut payload, len)
|
||||
@ -674,8 +685,10 @@ impl InnerField {
|
||||
};
|
||||
|
||||
if let Poll::Ready(None) = result {
|
||||
self.payload.take();
|
||||
// drop payload buffer and make future un-poll-able
|
||||
let _ = self.payload.take();
|
||||
}
|
||||
|
||||
result
|
||||
}
|
||||
}
|
||||
|
@ -12,9 +12,11 @@ repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "actix_router"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"http::*",
|
||||
"serde::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["http", "unicode"]
|
||||
|
@ -2,6 +2,10 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 0.1.5
|
||||
|
||||
- Add `TestServerConfig::listen_address()` method.
|
||||
|
||||
## 0.1.4
|
||||
|
||||
- Add `TestServerConfig::rustls_0_23()` method for Rustls v0.23 support behind new `rustls-0_23` crate feature.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.4"
|
||||
version = "0.1.5"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@ -18,6 +18,22 @@ categories = [
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_codec::*",
|
||||
"actix_http_test::*",
|
||||
"actix_http::*",
|
||||
"actix_service::*",
|
||||
"actix_web::*",
|
||||
"awc::*",
|
||||
"bytes::*",
|
||||
"futures_core::*",
|
||||
"http::*",
|
||||
"openssl::*",
|
||||
"rustls::*",
|
||||
"tokio::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = []
|
||||
|
||||
|
45
actix-test/README.md
Normal file
45
actix-test/README.md
Normal file
@ -0,0 +1,45 @@
|
||||
# `actix-test`
|
||||
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-test)
|
||||
[](https://docs.rs/actix-test/0.1.5)
|
||||

|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-test/0.1.5)
|
||||
[](https://crates.io/crates/actix-test)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
<!-- prettier-ignore-end -->
|
||||
|
||||
<!-- cargo-rdme start -->
|
||||
|
||||
Integration testing tools for Actix Web applications.
|
||||
|
||||
The main integration testing tool is [`TestServer`]. It spawns a real HTTP server on an unused port and provides methods that use a real HTTP client. Therefore, it is much closer to real-world cases than using `init_service`, which skips HTTP encoding and decoding.
|
||||
|
||||
## Examples
|
||||
|
||||
```rust
|
||||
use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
|
||||
|
||||
#[get("/")]
|
||||
async fn my_handler() -> Result<impl Responder, Error> {
|
||||
Ok(HttpResponse::Ok())
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_example() {
|
||||
let srv = actix_test::start(||
|
||||
App::new().service(my_handler)
|
||||
);
|
||||
|
||||
let req = srv.get("/");
|
||||
let res = req.send().await.unwrap();
|
||||
|
||||
assert!(res.status().is_success());
|
||||
}
|
||||
```
|
||||
|
||||
<!-- cargo-rdme end -->
|
@ -5,6 +5,7 @@
|
||||
//! real-world cases than using `init_service`, which skips HTTP encoding and decoding.
|
||||
//!
|
||||
//! # Examples
|
||||
//!
|
||||
//! ```
|
||||
//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
|
||||
//!
|
||||
@ -154,7 +155,7 @@ where
|
||||
// run server in separate orphaned thread
|
||||
thread::spawn(move || {
|
||||
rt::System::new().block_on(async move {
|
||||
let tcp = net::TcpListener::bind(("127.0.0.1", cfg.port)).unwrap();
|
||||
let tcp = net::TcpListener::bind((cfg.listen_address.clone(), cfg.port)).unwrap();
|
||||
let local_addr = tcp.local_addr().unwrap();
|
||||
let factory = factory.clone();
|
||||
let srv_cfg = cfg.clone();
|
||||
@ -514,6 +515,7 @@ pub struct TestServerConfig {
|
||||
tp: HttpVer,
|
||||
stream: StreamType,
|
||||
client_request_timeout: Duration,
|
||||
listen_address: String,
|
||||
port: u16,
|
||||
workers: usize,
|
||||
disable_redirects: bool,
|
||||
@ -532,6 +534,7 @@ impl TestServerConfig {
|
||||
tp: HttpVer::Both,
|
||||
stream: StreamType::Tcp,
|
||||
client_request_timeout: Duration::from_secs(5),
|
||||
listen_address: "127.0.0.1".to_string(),
|
||||
port: 0,
|
||||
workers: 1,
|
||||
disable_redirects: false,
|
||||
@ -607,6 +610,14 @@ impl TestServerConfig {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the address the server will listen on.
|
||||
///
|
||||
/// By default, only listens on `127.0.0.1`.
|
||||
pub fn listen_address(mut self, addr: impl Into<String>) -> Self {
|
||||
self.listen_address = addr.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets test server port.
|
||||
///
|
||||
/// By default, a random free port is determined by the OS.
|
||||
@ -657,9 +668,9 @@ impl TestServer {
|
||||
let scheme = if self.tls { "https" } else { "http" };
|
||||
|
||||
if uri.starts_with('/') {
|
||||
format!("{}://localhost:{}{}", scheme, self.addr.port(), uri)
|
||||
format!("{}://{}{}", scheme, self.addr, uri)
|
||||
} else {
|
||||
format!("{}://localhost:{}/{}", scheme, self.addr.port(), uri)
|
||||
format!("{}://{}/{}", scheme, self.addr, uri)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2,6 +2,7 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
- Take the encoded buffer when yielding bytes in the response stream rather than splitting the buffer, reducing memory use
|
||||
- Minimum supported Rust version (MSRV) is now 1.72.
|
||||
|
||||
## 4.3.0
|
||||
|
@ -9,9 +9,15 @@ repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "actix_web_actors"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix::*",
|
||||
"actix_http::*",
|
||||
"actix_web::*",
|
||||
"bytes::*",
|
||||
"bytestring::*",
|
||||
"futures_core::*",
|
||||
]
|
||||
|
||||
[dependencies]
|
||||
actix = { version = ">=0.12, <0.14", default-features = false }
|
||||
|
@ -710,7 +710,7 @@ where
|
||||
}
|
||||
|
||||
if !this.buf.is_empty() {
|
||||
Poll::Ready(Some(Ok(this.buf.split().freeze())))
|
||||
Poll::Ready(Some(Ok(std::mem::take(&mut this.buf).freeze())))
|
||||
} else if this.fut.alive() && !this.closed {
|
||||
Poll::Pending
|
||||
} else {
|
||||
|
@ -2,10 +2,24 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 4.8.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `web::Html` responder.
|
||||
- Add `HttpRequest::full_url()` method to get the complete URL of the request.
|
||||
|
||||
### Fixed
|
||||
|
||||
- Always remove port from return value of `ConnectionInfo::realip_remote_addr()` when handling IPv6 addresses. from the `Forwarded` header.
|
||||
- The `UrlencodedError::ContentType` variant (relevant to the `Form` extractor) now uses the 415 (Media Type Unsupported) status code in it's `ResponseError` implementation.
|
||||
- Apply `HttpServer::max_connection_rate()` setting when using rustls v0.22 or v0.23.
|
||||
|
||||
## 4.7.0
|
||||
|
||||
### Added
|
||||
|
||||
- Add `#[scope]` macro.
|
||||
- Add `middleware::Identity` type.
|
||||
- Add `CustomizeResponder::add_cookie()` method.
|
||||
- Add `guard::GuardContext::app_data()` method.
|
||||
|
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.7.0"
|
||||
version = "4.8.0"
|
||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
@ -35,9 +35,31 @@ features = [
|
||||
"secure-cookies",
|
||||
]
|
||||
|
||||
[lib]
|
||||
name = "actix_web"
|
||||
path = "src/lib.rs"
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_http::*",
|
||||
"actix_router::*",
|
||||
"actix_rt::*",
|
||||
"actix_server::*",
|
||||
"actix_service::*",
|
||||
"actix_utils::*",
|
||||
"actix_web_codegen::*",
|
||||
"bytes::*",
|
||||
"cookie::*",
|
||||
"cookie",
|
||||
"futures_core::*",
|
||||
"http::*",
|
||||
"language_tags::*",
|
||||
"mime::*",
|
||||
"openssl::*",
|
||||
"rustls::*",
|
||||
"serde_json::*",
|
||||
"serde_urlencoded::*",
|
||||
"serde::*",
|
||||
"serde::*",
|
||||
"tokio::*",
|
||||
"url::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = [
|
||||
@ -71,18 +93,18 @@ secure-cookies = ["cookies", "cookie/secure"]
|
||||
http2 = ["actix-http/http2"]
|
||||
|
||||
# TLS via OpenSSL
|
||||
openssl = ["http2", "actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
|
||||
openssl = ["__tls", "http2", "actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
|
||||
|
||||
# TLS via Rustls v0.20
|
||||
rustls = ["rustls-0_20"]
|
||||
# TLS via Rustls v0.20
|
||||
rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"]
|
||||
rustls-0_20 = ["__tls", "http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"]
|
||||
# TLS via Rustls v0.21
|
||||
rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"]
|
||||
rustls-0_21 = ["__tls", "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"]
|
||||
rustls-0_22 = ["__tls", "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"]
|
||||
rustls-0_23 = ["__tls", "http2", "actix-http/rustls-0_23", "actix-tls/accept", "actix-tls/rustls-0_23"]
|
||||
|
||||
# Full unicode support
|
||||
unicode = ["dep:regex", "actix-router/unicode"]
|
||||
@ -91,6 +113,10 @@ unicode = ["dep:regex", "actix-router/unicode"]
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
# Internal (PRIVATE!) features used to aid checking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__tls = []
|
||||
|
||||
# io-uring feature only available for Linux OSes.
|
||||
experimental-io-uring = ["actix-server/io-uring"]
|
||||
|
||||
|
@ -8,10 +8,10 @@
|
||||
<!-- prettier-ignore-start -->
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.7.0)
|
||||
[](https://docs.rs/actix-web/4.8.0)
|
||||

|
||||

|
||||
[](https://deps.rs/crate/actix-web/4.7.0)
|
||||
[](https://deps.rs/crate/actix-web/4.8.0)
|
||||
<br />
|
||||
[](https://github.com/actix/actix-web/actions/workflows/ci.yml)
|
||||
[](https://codecov.io/gh/actix/actix-web)
|
||||
@ -109,4 +109,4 @@ This project is licensed under either of the following licenses, at your option:
|
||||
|
||||
## Code of Conduct
|
||||
|
||||
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant. The Actix team promises to intervene to uphold that code of conduct.
|
||||
Contribution to the `actix/actix-web` repo is organized under the terms of the Contributor Covenant. The Actix team promises to intervene to uphold that code of conduct.
|
||||
|
@ -234,7 +234,6 @@ where
|
||||
///
|
||||
/// * *Resource* is an entry in resource table which corresponds to requested URL.
|
||||
/// * *Scope* is a set of resources with common root path.
|
||||
/// * "StaticFiles" is a service for static files support
|
||||
pub fn service<F>(mut self, factory: F) -> Self
|
||||
where
|
||||
F: HttpServiceFactory + 'static,
|
||||
|
@ -100,6 +100,7 @@ impl ResponseError for UrlencodedError {
|
||||
match self {
|
||||
Self::Overflow { .. } => StatusCode::PAYLOAD_TOO_LARGE,
|
||||
Self::UnknownLength => StatusCode::LENGTH_REQUIRED,
|
||||
Self::ContentType => StatusCode::UNSUPPORTED_MEDIA_TYPE,
|
||||
Self::Payload(err) => err.status_code(),
|
||||
_ => StatusCode::BAD_REQUEST,
|
||||
}
|
||||
@ -232,7 +233,7 @@ mod tests {
|
||||
let resp = UrlencodedError::UnknownLength.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::LENGTH_REQUIRED);
|
||||
let resp = UrlencodedError::ContentType.error_response();
|
||||
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
|
||||
assert_eq!(resp.status(), StatusCode::UNSUPPORTED_MEDIA_TYPE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -21,6 +21,20 @@ fn unquote(val: &str) -> &str {
|
||||
val.trim().trim_start_matches('"').trim_end_matches('"')
|
||||
}
|
||||
|
||||
/// Remove port and IPv6 square brackets from a peer specification.
|
||||
fn bare_address(val: &str) -> &str {
|
||||
if val.starts_with('[') {
|
||||
val.split("]:")
|
||||
.next()
|
||||
.map(|s| s.trim_start_matches('[').trim_end_matches(']'))
|
||||
// this indicates that the IPv6 address is malformed so shouldn't
|
||||
// usually happen, but if it does, just return the original input
|
||||
.unwrap_or(val)
|
||||
} else {
|
||||
val.split(':').next().unwrap_or(val)
|
||||
}
|
||||
}
|
||||
|
||||
/// Extracts and trims first value for given header name.
|
||||
fn first_header_value<'a>(req: &'a RequestHead, name: &'_ HeaderName) -> Option<&'a str> {
|
||||
let hdr = req.headers.get(name)?.to_str().ok()?;
|
||||
@ -100,7 +114,7 @@ impl ConnectionInfo {
|
||||
// --- https://datatracker.ietf.org/doc/html/rfc7239#section-5.2
|
||||
|
||||
match name.trim().to_lowercase().as_str() {
|
||||
"for" => realip_remote_addr.get_or_insert_with(|| unquote(val)),
|
||||
"for" => realip_remote_addr.get_or_insert_with(|| bare_address(unquote(val))),
|
||||
"proto" => scheme.get_or_insert_with(|| unquote(val)),
|
||||
"host" => host.get_or_insert_with(|| unquote(val)),
|
||||
"by" => {
|
||||
@ -368,16 +382,25 @@ mod tests {
|
||||
.insert_header((header::FORWARDED, r#"for="192.0.2.60:8080""#))
|
||||
.to_http_request();
|
||||
let info = req.connection_info();
|
||||
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60:8080"));
|
||||
assert_eq!(info.realip_remote_addr(), Some("192.0.2.60"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forwarded_for_ipv6() {
|
||||
let req = TestRequest::default()
|
||||
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]""#))
|
||||
.to_http_request();
|
||||
let info = req.connection_info();
|
||||
assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn forwarded_for_ipv6_with_port() {
|
||||
let req = TestRequest::default()
|
||||
.insert_header((header::FORWARDED, r#"for="[2001:db8:cafe::17]:4711""#))
|
||||
.to_http_request();
|
||||
let info = req.connection_info();
|
||||
assert_eq!(info.realip_remote_addr(), Some("[2001:db8:cafe::17]:4711"));
|
||||
assert_eq!(info.realip_remote_addr(), Some("2001:db8:cafe::17"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
@ -91,6 +91,35 @@ impl HttpRequest {
|
||||
&self.head().uri
|
||||
}
|
||||
|
||||
/// Returns request's original full URL.
|
||||
///
|
||||
/// Reconstructed URL is best-effort, using [`connection_info`](HttpRequest::connection_info())
|
||||
/// to get forwarded scheme & host.
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::test::TestRequest;
|
||||
/// let req = TestRequest::with_uri("http://10.1.2.3:8443/api?id=4&name=foo")
|
||||
/// .insert_header(("host", "example.com"))
|
||||
/// .to_http_request();
|
||||
///
|
||||
/// assert_eq!(
|
||||
/// req.full_url().as_str(),
|
||||
/// "http://example.com/api?id=4&name=foo",
|
||||
/// );
|
||||
/// ```
|
||||
pub fn full_url(&self) -> url::Url {
|
||||
let info = self.connection_info();
|
||||
let scheme = info.scheme();
|
||||
let host = info.host();
|
||||
let path_and_query = self
|
||||
.uri()
|
||||
.path_and_query()
|
||||
.map(|paq| paq.as_str())
|
||||
.unwrap_or("/");
|
||||
|
||||
url::Url::parse(&format!("{scheme}://{host}{path_and_query}")).unwrap()
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
#[inline]
|
||||
pub fn method(&self) -> &Method {
|
||||
@ -963,4 +992,27 @@ mod tests {
|
||||
|
||||
assert!(format!("{:?}", req).contains(location_header));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn check_full_url() {
|
||||
let req = TestRequest::with_uri("/api?id=4&name=foo").to_http_request();
|
||||
assert_eq!(
|
||||
req.full_url().as_str(),
|
||||
"http://localhost:8080/api?id=4&name=foo",
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("https://example.com/api?id=4&name=foo").to_http_request();
|
||||
assert_eq!(
|
||||
req.full_url().as_str(),
|
||||
"https://example.com/api?id=4&name=foo",
|
||||
);
|
||||
|
||||
let req = TestRequest::with_uri("http://10.1.2.3:8443/api?id=4&name=foo")
|
||||
.insert_header(("host", "example.com"))
|
||||
.to_http_request();
|
||||
assert_eq!(
|
||||
req.full_url().as_str(),
|
||||
"http://example.com/api?id=4&name=foo",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -213,7 +213,6 @@ where
|
||||
///
|
||||
/// * *Resource* is an entry in resource table which corresponds to requested URL.
|
||||
/// * *Scope* is a set of resources with common root path.
|
||||
/// * "StaticFiles" is a service for static files support
|
||||
///
|
||||
/// ```
|
||||
/// use actix_web::{web, App, HttpRequest};
|
||||
|
@ -7,13 +7,7 @@ use std::{
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
#[cfg(any(
|
||||
feature = "openssl",
|
||||
feature = "rustls-0_20",
|
||||
feature = "rustls-0_21",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[cfg(feature = "__tls")]
|
||||
use actix_http::TlsAcceptorConfig;
|
||||
use actix_http::{body::MessageBody, Extensions, HttpService, KeepAlive, Request, Response};
|
||||
use actix_server::{Server, ServerBuilder};
|
||||
@ -190,7 +184,7 @@ where
|
||||
/// By default max connections is set to a 256.
|
||||
#[allow(unused_variables)]
|
||||
pub fn max_connection_rate(self, num: usize) -> Self {
|
||||
#[cfg(any(feature = "rustls-0_20", feature = "rustls-0_21", feature = "openssl"))]
|
||||
#[cfg(feature = "__tls")]
|
||||
actix_tls::accept::max_concurrent_tls_connect(num);
|
||||
self
|
||||
}
|
||||
@ -243,13 +237,7 @@ 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",
|
||||
feature = "rustls-0_22",
|
||||
feature = "rustls-0_23",
|
||||
))]
|
||||
#[cfg(feature = "__tls")]
|
||||
pub fn tls_handshake_timeout(self, dur: Duration) -> Self {
|
||||
self.config
|
||||
.lock()
|
||||
|
66
actix-web/src/types/html.rs
Normal file
66
actix-web/src/types/html.rs
Normal file
@ -0,0 +1,66 @@
|
||||
//! Semantic HTML responder. See [`Html`].
|
||||
|
||||
use crate::{
|
||||
http::{
|
||||
header::{self, ContentType, TryIntoHeaderValue},
|
||||
StatusCode,
|
||||
},
|
||||
HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
|
||||
/// Semantic HTML responder.
|
||||
///
|
||||
/// When used as a responder, creates a 200 OK response, sets the correct HTML content type, and
|
||||
/// uses the string passed to [`Html::new()`] as the body.
|
||||
///
|
||||
/// ```
|
||||
/// # use actix_web::web::Html;
|
||||
/// Html::new("<p>Hello, World!</p>")
|
||||
/// # ;
|
||||
/// ```
|
||||
#[derive(Debug, Clone, PartialEq, Hash)]
|
||||
pub struct Html(String);
|
||||
|
||||
impl Html {
|
||||
/// Constructs a new `Html` responder.
|
||||
pub fn new(html: impl Into<String>) -> Self {
|
||||
Self(html.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for Html {
|
||||
type Body = String;
|
||||
|
||||
fn respond_to(self, _req: &HttpRequest) -> HttpResponse<Self::Body> {
|
||||
let mut res = HttpResponse::with_body(StatusCode::OK, self.0);
|
||||
res.headers_mut().insert(
|
||||
header::CONTENT_TYPE,
|
||||
ContentType::html().try_into_value().unwrap(),
|
||||
);
|
||||
res
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::test::TestRequest;
|
||||
|
||||
#[test]
|
||||
fn responder() {
|
||||
let req = TestRequest::default().to_http_request();
|
||||
|
||||
let res = Html::new("<p>Hello, World!</p>");
|
||||
let res = res.respond_to(&req);
|
||||
|
||||
assert!(res.status().is_success());
|
||||
assert!(res
|
||||
.headers()
|
||||
.get(header::CONTENT_TYPE)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
.unwrap()
|
||||
.starts_with("text/html"));
|
||||
assert!(res.body().starts_with("<p>"));
|
||||
}
|
||||
}
|
@ -3,6 +3,7 @@
|
||||
mod either;
|
||||
mod form;
|
||||
mod header;
|
||||
mod html;
|
||||
mod json;
|
||||
mod path;
|
||||
mod payload;
|
||||
@ -13,6 +14,7 @@ pub use self::{
|
||||
either::Either,
|
||||
form::{Form, FormConfig, UrlEncoded},
|
||||
header::Header,
|
||||
html::Html,
|
||||
json::{Json, JsonBody, JsonConfig},
|
||||
path::{Path, PathConfig},
|
||||
payload::{Payload, PayloadConfig},
|
||||
|
@ -15,10 +15,6 @@ repository = "https://github.com/actix/actix-web"
|
||||
license = "MIT OR Apache-2.0"
|
||||
edition = "2021"
|
||||
|
||||
[lib]
|
||||
name = "awc"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[package.metadata.docs.rs]
|
||||
rustdoc-args = ["--cfg", "docsrs"]
|
||||
features = [
|
||||
@ -33,6 +29,27 @@ features = [
|
||||
"compress-zstd",
|
||||
]
|
||||
|
||||
[package.metadata.cargo_check_external_types]
|
||||
allowed_external_types = [
|
||||
"actix_codec::*",
|
||||
"actix_http::*",
|
||||
"actix_rt::*",
|
||||
"actix_service::*",
|
||||
"actix_tls::*",
|
||||
"bytes::*",
|
||||
"cookie::*",
|
||||
"cookie",
|
||||
"futures_core::*",
|
||||
"h2::*",
|
||||
"http::*",
|
||||
"openssl::*",
|
||||
"rustls::*",
|
||||
"serde_json::*",
|
||||
"serde_urlencoded::*",
|
||||
"serde::*",
|
||||
"tokio::*",
|
||||
]
|
||||
|
||||
[features]
|
||||
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
|
||||
@ -92,7 +109,7 @@ cfg-if = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] }
|
||||
h2 = "0.3.24"
|
||||
h2 = "0.3.26"
|
||||
http = "0.2.7"
|
||||
itoa = "1"
|
||||
log =" 0.4"
|
||||
@ -134,7 +151,7 @@ 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
|
||||
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"
|
||||
|
79
justfile
79
justfile
@ -22,7 +22,7 @@ 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" \
|
||||
--arg exclusions "__tls,__compress,tokio-uring,io-uring,experimental-io-uring" \
|
||||
'add | unique | . - ($exclusions | split(",")) | join(",")'
|
||||
```
|
||||
|
||||
@ -32,6 +32,14 @@ all_crate_features := if os() == "linux" {
|
||||
"--features='" + non_linux_all_features_list + "'"
|
||||
}
|
||||
|
||||
[private]
|
||||
check-min:
|
||||
cargo hack --workspace check --no-default-features
|
||||
|
||||
[private]
|
||||
check-default:
|
||||
cargo hack --workspace check
|
||||
|
||||
# Run Clippy over workspace.
|
||||
clippy toolchain="":
|
||||
cargo {{ toolchain }} clippy --workspace --all-targets {{ all_crate_features }}
|
||||
@ -53,9 +61,32 @@ test-docs toolchain="": && doc
|
||||
# Test workspace.
|
||||
test-all toolchain="": (test toolchain) (test-docs toolchain)
|
||||
|
||||
# Test workspace and collect coverage info.
|
||||
[private]
|
||||
test-coverage toolchain="":
|
||||
cargo {{ toolchain }} llvm-cov nextest --no-report {{ all_crate_features }}
|
||||
cargo {{ toolchain }} llvm-cov --doc --no-report {{ all_crate_features }}
|
||||
|
||||
# Test workspace and generate Codecov report.
|
||||
test-coverage-codecov toolchain="": (test-coverage toolchain)
|
||||
cargo {{ toolchain }} llvm-cov report --doctests --codecov --output-path=codecov.json
|
||||
|
||||
# Test workspace and generate LCOV report.
|
||||
test-coverage-lcov toolchain="": (test-coverage toolchain)
|
||||
cargo {{ toolchain }} llvm-cov report --doctests --lcov --output-path=lcov.info
|
||||
|
||||
# Document crates in workspace.
|
||||
doc *args:
|
||||
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --no-deps --workspace {{ all_crate_features }} {{ args }}
|
||||
doc *args: && doc-set-workspace-crates
|
||||
RUSTDOCFLAGS="--cfg=docsrs -Dwarnings" cargo +nightly doc --workspace {{ all_crate_features }} {{ args }}
|
||||
|
||||
[private]
|
||||
doc-set-workspace-crates:
|
||||
#!/usr/bin/env bash
|
||||
(
|
||||
echo "window.ALL_CRATES ="
|
||||
cargo metadata --format-version=1 | jq '[.packages[] | select(.source == null) | .name]'
|
||||
echo ";"
|
||||
) > "$(cargo metadata --format-version=1 | jq -r '.target_directory')/doc/crates.js"
|
||||
|
||||
# Document crates in workspace and watch for changes.
|
||||
doc-watch:
|
||||
@ -65,4 +96,46 @@ doc-watch:
|
||||
# Update READMEs from crate root documentation.
|
||||
update-readmes: && fmt
|
||||
cd ./actix-files && cargo rdme --force
|
||||
cd ./actix-http-test && cargo rdme --force
|
||||
cd ./actix-router && cargo rdme --force
|
||||
cd ./actix-multipart && cargo rdme --force
|
||||
cd ./actix-test && cargo rdme --force
|
||||
|
||||
feature_combo_skip_list := if os() == "linux" {
|
||||
"__tls,__compress"
|
||||
} else {
|
||||
"__tls,__compress,experimental-io-uring"
|
||||
}
|
||||
|
||||
# Checks compatibility of feature combinations.
|
||||
check-feature-combinations:
|
||||
cargo hack --workspace \
|
||||
--feature-powerset --depth=4 \
|
||||
--skip={{ feature_combo_skip_list }} \
|
||||
check
|
||||
|
||||
# Check for unintentional external type exposure on all crates in workspace.
|
||||
check-external-types-all toolchain="+nightly":
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
exit=0
|
||||
for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
|
||||
if ! just check-external-types-manifest "$f" {{toolchain}}; then exit=1; fi
|
||||
echo
|
||||
echo
|
||||
done
|
||||
exit $exit
|
||||
|
||||
# Check for unintentional external type exposure on all crates in workspace.
|
||||
check-external-types-all-table toolchain="+nightly":
|
||||
#!/usr/bin/env bash
|
||||
set -euo pipefail
|
||||
for f in $(find . -mindepth 2 -maxdepth 2 -name Cargo.toml | grep -vE "\-codegen/|\-derive/|\-macros/"); do
|
||||
echo
|
||||
echo "Checking for $f"
|
||||
just check-external-types-manifest "$f" {{toolchain}} --output-format=markdown-table
|
||||
done
|
||||
|
||||
# Check for unintentional external type exposure on a crate.
|
||||
check-external-types-manifest manifest_path toolchain="+nightly" *extra_args="":
|
||||
cargo {{toolchain}} check-external-types --manifest-path "{{manifest_path}}" {{extra_args}}
|
||||
|
Reference in New Issue
Block a user