1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-06 19:00:18 +02:00

Compare commits

..

2 Commits

Author SHA1 Message Date
eb10b74751 Merge branch 'master' into error-response-mapping 2024-06-10 01:31:51 +01:00
98c99f3bc2 add methods for mapping error responses 2023-02-13 23:40:23 +00:00
37 changed files with 181 additions and 588 deletions

10
.cargo/config.toml Normal file
View File

@ -0,0 +1,10 @@
[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"

View File

@ -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.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.39.1
uses: taiki-e/install-action@v2.34.0
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
- name: check minimal
run: just check-min
run: cargo ci-check-min
- name: check default
run: just check-default
run: cargo ci-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.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
- name: Install just, cargo-hack
uses: taiki-e/install-action@v2.39.1
- name: Install cargo-hack
uses: taiki-e/install-action@v2.34.0
with:
tool: just,cargo-hack
tool: cargo-hack
- name: Check feature combinations
run: just check-feature-combinations
- name: check feature combinations
run: cargo ci-check-all-feature-powerset
- name: check feature combinations
run: cargo ci-check-all-feature-powerset-linux

View File

@ -59,12 +59,12 @@ jobs:
uses: rui314/setup-mold@v1
- name: Install Rust (${{ matrix.version.name }})
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.39.1
uses: taiki-e/install-action@v2.34.0
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: just check-min
run: cargo ci-check-min
- name: check default
run: just check-default
run: cargo ci-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.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.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.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly
- name: Install just
uses: taiki-e/install-action@v2.39.1
uses: taiki-e/install-action@v2.34.0
with:
tool: just

View File

@ -17,22 +17,21 @@ jobs:
steps:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly
components: llvm-tools
components: llvm-tools-preview
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@v2.39.1
- name: Install just,cargo-llvm-cov
uses: taiki-e/install-action@v2.34.0
with:
tool: just,cargo-llvm-cov,cargo-nextest
tool: just,cargo-llvm-cov
- name: Generate code coverage
run: just test-coverage-codecov
run: cargo llvm-cov --workspace --all-features --codecov --output-path codecov.json
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v4.5.0
uses: codecov/codecov-action@v4.4.1
with:
files: codecov.json
fail_ci_if_error: true

View File

@ -18,7 +18,7 @@ jobs:
- uses: actions/checkout@v4
- name: Install Rust (nightly)
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.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.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.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.9.0
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly
components: rust-docs
@ -65,29 +65,6 @@ 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:
@ -99,13 +76,13 @@ jobs:
- name: Checkout PR branch
uses: actions/checkout@v4
- name: Install Rust (nightly-2024-06-07)
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Install Rust
uses: actions-rust-lang/setup-rust-toolchain@v1.8.0
with:
toolchain: nightly-2024-06-07
- name: Install cargo-public-api
uses: taiki-e/install-action@v2.39.1
uses: taiki-e/install-action@v2.34.0
with:
tool: cargo-public-api

View File

@ -13,14 +13,9 @@ categories = ["asynchronous", "web-programming::http-server"]
license = "MIT OR Apache-2.0"
edition = "2021"
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"actix_http::*",
"actix_service::*",
"actix_web::*",
"http::*",
"mime::*",
]
[lib]
name = "actix_files"
path = "src/lib.rs"
[features]
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]

View File

@ -18,17 +18,9 @@ edition = "2021"
[package.metadata.docs.rs]
features = []
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"actix_codec::*",
"actix_http::*",
"actix_server::*",
"awc::*",
"bytes::*",
"futures_core::*",
"http::*",
"tokio::*",
]
[lib]
name = "actix_http_test"
path = "src/lib.rs"
[features]
default = []

View File

@ -1,5 +1,7 @@
# `actix-http-test`
> Various helpers for Actix applications to use during testing.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
@ -12,9 +14,3 @@
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end -->
<!-- cargo-rdme start -->
Various helpers for Actix applications to use during testing.
<!-- cargo-rdme end -->

View File

@ -2,8 +2,6 @@
## Unreleased
## 3.8.0
### Added
- Add `error::InvalidStatusCode` re-export.

View File

@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.8.0"
version = "3.7.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@ -34,72 +34,51 @@ features = [
"compress-zstd",
]
[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::*",
]
[lib]
name = "actix_http"
path = "src/lib.rs"
[features]
default = []
# HTTP/2 protocol support
http2 = ["dep:h2"]
http2 = ["h2"]
# WebSocket protocol implementation
ws = [
"dep:local-channel",
"dep:base64",
"dep:rand",
"dep:sha1",
"local-channel",
"base64",
"rand",
"sha1",
]
# TLS via OpenSSL
openssl = ["__tls", "actix-tls/accept", "actix-tls/openssl"]
openssl = ["actix-tls/accept", "actix-tls/openssl"]
# TLS via Rustls v0.20
rustls = ["__tls", "rustls-0_20"]
rustls = ["rustls-0_20"]
# TLS via Rustls v0.20
rustls-0_20 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_20"]
rustls-0_20 = ["actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21
rustls-0_21 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_21"]
rustls-0_21 = ["actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_22"]
rustls-0_22 = ["actix-tls/accept", "actix-tls/rustls-0_22"]
# TLS via Rustls v0.23
rustls-0_23 = ["__tls", "actix-tls/accept", "actix-tls/rustls-0_23"]
rustls-0_23 = ["actix-tls/accept", "actix-tls/rustls-0_23"]
# Compression codecs
compress-brotli = ["__compress", "dep:brotli"]
compress-gzip = ["__compress", "dep:flate2"]
compress-zstd = ["__compress", "dep:zstd"]
compress-brotli = ["__compress", "brotli"]
compress-gzip = ["__compress", "flate2"]
compress-zstd = ["__compress", "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"

View File

@ -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.8.0)](https://docs.rs/actix-http/3.8.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.8.0/status.svg)](https://deps.rs/crate/actix-http/3.8.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)

View File

@ -6,10 +6,10 @@
//! | ------------------- | ------------------------------------------- |
//! | `http2` | HTTP/2 support via [h2]. |
//! | `openssl` | TLS support via [OpenSSL]. |
//! | `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. |
//! | `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. |
@ -61,7 +61,13 @@ pub mod ws;
#[allow(deprecated)]
pub use self::payload::PayloadStream;
#[cfg(feature = "__tls")]
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
feature = "rustls-0_23",
))]
pub use self::service::TlsAcceptorConfig;
pub use self::{
builder::HttpServiceBuilder,

View File

@ -241,13 +241,25 @@ where
}
/// Configuration options used when accepting TLS connection.
#[cfg(feature = "__tls")]
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
feature = "rustls-0_23",
))]
#[derive(Debug, Default)]
pub struct TlsAcceptorConfig {
pub(crate) handshake_timeout: Option<std::time::Duration>,
}
#[cfg(feature = "__tls")]
#[cfg(any(
feature = "openssl",
feature = "rustls-0_20",
feature = "rustls-0_21",
feature = "rustls-0_22",
feature = "rustls-0_23",
))]
impl TlsAcceptorConfig {
/// Set TLS handshake timeout duration.
pub fn handshake_timeout(self, dur: std::time::Duration) -> Self {

View File

@ -16,21 +16,6 @@ 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"]

View File

@ -1,5 +1,7 @@
# `actix-multipart`
> Multipart form support for Actix Web.
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
@ -13,11 +15,18 @@
<!-- prettier-ignore-end -->
<!-- cargo-rdme start -->
## Example
Multipart form support for Actix Web.
Dependencies:
## Examples
```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};
@ -54,10 +63,6 @@ async fn main() -> std::io::Result<()> {
}
```
<!-- cargo-rdme end -->
[More available in the examples repo &rarr;](https://github.com/actix/examples/tree/master/forms/multipart)
Curl request :
```bash
@ -66,3 +71,7 @@ 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

View File

@ -33,14 +33,6 @@ 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;
}
@ -404,20 +396,11 @@ 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, HttpRequest, HttpResponse, Resource, Responder,
};
use actix_web::{dev::Payload, http::StatusCode, web, App, HttpResponse, 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, FieldReader, Limits, MultipartFormConfig,
},
Field, MultipartError,
};
use crate::form::{bytes::Bytes, tempfile::TempFile, text::Text, MultipartFormConfig};
pub async fn send_form(
srv: &TestServer,
@ -751,49 +734,4 @@ 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;
}
}

View File

@ -1,7 +1,5 @@
//! Multipart form support for Actix Web.
//!
//! # Examples
//!
//! ```no_run
//! use actix_web::{post, App, HttpServer, Responder};
//!

View File

@ -465,12 +465,7 @@ 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()
.expect("Field should not be polled after completion")
.get_mut(&this.safety)
{
if let Some(mut buffer) = inner.payload.as_ref().unwrap().get_mut(&this.safety) {
// check safety and poll read payload to buffer.
buffer.poll_stream(cx)?;
} else if !this.safety.is_clean() {
@ -501,7 +496,6 @@ 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,
@ -649,12 +643,7 @@ impl InnerField {
return Poll::Ready(None);
}
let result = if let Some(mut payload) = self
.payload
.as_ref()
.expect("Field should not be polled after completion")
.get_mut(s)
{
let result = if let Some(mut payload) = self.payload.as_ref().unwrap().get_mut(s) {
if !self.eof {
let res = if let Some(ref mut len) = self.length {
InnerField::read_len(&mut payload, len)
@ -685,10 +674,8 @@ impl InnerField {
};
if let Poll::Ready(None) = result {
// drop payload buffer and make future un-poll-able
let _ = self.payload.take();
self.payload.take();
}
result
}
}

View File

@ -12,11 +12,9 @@ repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"http::*",
"serde::*",
]
[lib]
name = "actix_router"
path = "src/lib.rs"
[features]
default = ["http", "unicode"]

View File

@ -18,22 +18,6 @@ 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 = []

View File

@ -1,45 +0,0 @@
# `actix-test`
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-test?label=latest)](https://crates.io/crates/actix-test)
[![Documentation](https://docs.rs/actix-test/badge.svg?version=0.1.5)](https://docs.rs/actix-test/0.1.5)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-test.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-test/0.1.5/status.svg)](https://deps.rs/crate/actix-test/0.1.5)
[![Download](https://img.shields.io/crates/d/actix-test.svg)](https://crates.io/crates/actix-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](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 -->

View File

@ -5,7 +5,6 @@
//! real-world cases than using `init_service`, which skips HTTP encoding and decoding.
//!
//! # Examples
//!
//! ```
//! use actix_web::{get, web, test, App, HttpResponse, Error, Responder};
//!

View File

@ -9,15 +9,9 @@ repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0"
edition = "2021"
[package.metadata.cargo_check_external_types]
allowed_external_types = [
"actix::*",
"actix_http::*",
"actix_web::*",
"bytes::*",
"bytestring::*",
"futures_core::*",
]
[lib]
name = "actix_web_actors"
path = "src/lib.rs"
[dependencies]
actix = { version = ">=0.12, <0.14", default-features = false }

View File

@ -2,18 +2,9 @@
## 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.
- `ConnectionInfo::realip_remote_addr()` now handles IPv6 addresses from `Forwarded` header correctly. Previously, it sometimes returned the forwarded port as well.
## 4.7.0

View File

@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.8.0"
version = "4.7.0"
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
@ -35,31 +35,9 @@ features = [
"secure-cookies",
]
[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::*",
]
[lib]
name = "actix_web"
path = "src/lib.rs"
[features]
default = [
@ -93,18 +71,18 @@ secure-cookies = ["cookies", "cookie/secure"]
http2 = ["actix-http/http2"]
# TLS via OpenSSL
openssl = ["__tls", "http2", "actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
openssl = ["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 = ["__tls", "http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"]
rustls-0_20 = ["http2", "actix-http/rustls-0_20", "actix-tls/accept", "actix-tls/rustls-0_20"]
# TLS via Rustls v0.21
rustls-0_21 = ["__tls", "http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"]
rustls-0_21 = ["http2", "actix-http/rustls-0_21", "actix-tls/accept", "actix-tls/rustls-0_21"]
# TLS via Rustls v0.22
rustls-0_22 = ["__tls", "http2", "actix-http/rustls-0_22", "actix-tls/accept", "actix-tls/rustls-0_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 = ["__tls", "http2", "actix-http/rustls-0_23", "actix-tls/accept", "actix-tls/rustls-0_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"]
@ -113,10 +91,6 @@ 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"]

View File

@ -8,10 +8,10 @@
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.8.0)](https://docs.rs/actix-web/4.8.0)
[![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.8.0/status.svg)](https://deps.rs/crate/actix-web/4.8.0)
[![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)
@ -109,4 +109,4 @@ This project is licensed under either of the following licenses, at your option:
## 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.
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.

View File

@ -234,6 +234,7 @@ 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,

View File

@ -6,7 +6,7 @@ use crate::{HttpResponse, ResponseError};
/// General purpose Actix Web error.
///
/// An Actix Web error is used to carry errors from `std::error` through actix in a convenient way.
/// An Actix Web error is used to carry errors from `std::error` through Actix in a convenient way.
/// It can be created through converting errors with `into()`.
///
/// Whenever it is created from an external object a response error is created for it that can be
@ -14,6 +14,7 @@ use crate::{HttpResponse, ResponseError};
/// you can always get a `ResponseError` reference from it.
pub struct Error {
cause: Box<dyn ResponseError>,
response_mappers: Vec<Box<dyn Fn(HttpResponse) -> HttpResponse>>,
}
impl Error {
@ -29,7 +30,20 @@ impl Error {
/// Shortcut for creating an `HttpResponse`.
pub fn error_response(&self) -> HttpResponse {
self.cause.error_response()
let mut res = self.cause.error_response();
for mapper in &self.response_mappers {
res = (mapper)(res);
}
res
}
pub fn add_mapper<F, B>(&mut self, mapper: F)
where
F: Fn(HttpResponse) -> HttpResponse + 'static,
{
self.response_mappers.push(Box::new(mapper))
}
}
@ -56,6 +70,7 @@ impl<T: ResponseError + 'static> From<T> for Error {
fn from(err: T) -> Error {
Error {
cause: Box::new(err),
response_mappers: Vec::new(),
}
}
}

View File

@ -100,7 +100,6 @@ 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,
}
@ -233,7 +232,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::UNSUPPORTED_MEDIA_TYPE);
assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
}
#[test]

View File

@ -27,8 +27,7 @@ fn bare_address(val: &str) -> &str {
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
// This shouldn't *actually* ever happen
.unwrap_or(val)
} else {
val.split(':').next().unwrap_or(val)

View File

@ -91,35 +91,6 @@ 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 {
@ -992,27 +963,4 @@ 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",
);
}
}

View File

@ -213,6 +213,7 @@ 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};

View File

@ -7,7 +7,13 @@ use std::{
time::Duration,
};
#[cfg(feature = "__tls")]
#[cfg(any(
feature = "openssl",
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};
use actix_server::{Server, ServerBuilder};
@ -184,7 +190,7 @@ where
/// By default max connections is set to a 256.
#[allow(unused_variables)]
pub fn max_connection_rate(self, num: usize) -> Self {
#[cfg(feature = "__tls")]
#[cfg(any(feature = "rustls-0_20", feature = "rustls-0_21", feature = "openssl"))]
actix_tls::accept::max_concurrent_tls_connect(num);
self
}
@ -237,7 +243,13 @@ where
/// time, the connection is closed.
///
/// By default, the handshake timeout is 3 seconds.
#[cfg(feature = "__tls")]
#[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()

View File

@ -1,66 +0,0 @@
//! 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>"));
}
}

View File

@ -3,7 +3,6 @@
mod either;
mod form;
mod header;
mod html;
mod json;
mod path;
mod payload;
@ -14,7 +13,6 @@ pub use self::{
either::Either,
form::{Form, FormConfig, UrlEncoded},
header::Header,
html::Html,
json::{Json, JsonBody, JsonConfig},
path::{Path, PathConfig},
payload::{Payload, PayloadConfig},

View File

@ -15,6 +15,10 @@ 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 = [
@ -29,27 +33,6 @@ 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"]
@ -151,7 +134,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"

View File

@ -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 "__tls,__compress,tokio-uring,io-uring,experimental-io-uring" \
--arg exclusions "tokio-uring,io-uring,experimental-io-uring" \
'add | unique | . - ($exclusions | split(",")) | join(",")'
```
@ -32,14 +32,6 @@ 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 }}
@ -61,32 +53,9 @@ 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: && 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"
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:
@ -96,46 +65,4 @@ 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}}