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

Compare commits

...

18 Commits

Author SHA1 Message Date
Rob Ede
9ba326aed0 chore(actix-http): prepare release 3.9.0 2024-08-10 03:09:09 +01:00
Rob Ede
882fb3d25b chore(actors): add version marker in changelog 2024-08-10 03:08:18 +01:00
Rob Ede
be28a0bd6d feat: add from_fn middleware (#3447) 2024-08-10 01:41:27 +01:00
Rob Ede
a431b7356c feat: add ThinData wrapper (#3446) 2024-08-10 00:42:34 +01:00
Rob Ede
5be53820f0 docs(actors): add maintenance badge 2024-08-07 04:32:16 +01:00
Rob Ede
d7d9000b19 chore: address clippy warnings 2024-08-07 04:06:18 +01:00
Rob Ede
e4e4bb799c chore(actix-web-actors): prepare release 4.3.1 2024-08-07 04:02:30 +01:00
dependabot[bot]
323d1fa64f build(deps): bump taiki-e/install-action from 2.42.9 to 2.42.17 (#3442)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.42.9 to 2.42.17.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.42.9...v2.42.17)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-08-07 01:05:32 +00:00
dependabot[bot]
9aa62112aa build(deps): bump taiki-e/install-action from 2.42.4 to 2.42.9 (#3441)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.42.4 to 2.42.9.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.42.4...v2.42.9)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-29 00:25:30 +00:00
dependabot[bot]
270a6a3b70 build(deps): bump taiki-e/install-action from 2.41.17 to 2.42.4 (#3440)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.41.17 to 2.42.4.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.41.17...v2.42.4)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-minor
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-22 00:49:25 +00:00
Onè
07f720f716 docs: fix typo (#3439) 2024-07-21 17:34:42 +00:00
dependabot[bot]
f71f9ca66b build(deps): bump taiki-e/install-action from 2.41.10 to 2.41.17 (#3431)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.41.10 to 2.41.17.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.41.10...v2.41.17)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-20 00:13:57 +00:00
dependabot[bot]
b6bee346f7 build(deps): bump taiki-e/install-action from 2.41.7 to 2.41.10 (#3423)
Bumps [taiki-e/install-action](https://github.com/taiki-e/install-action) from 2.41.7 to 2.41.10.
- [Release notes](https://github.com/taiki-e/install-action/releases)
- [Changelog](https://github.com/taiki-e/install-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/taiki-e/install-action/compare/v2.41.7...v2.41.10)

---
updated-dependencies:
- dependency-name: taiki-e/install-action
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-07-08 09:31:50 +00:00
Rob Ede
5c6e0e17d3 feat(http): impl FromIter for HeaderMap 2024-07-07 21:16:25 +01:00
Rob Ede
e97e28db4f docs(multipart): improve crate root docs 2024-07-07 20:32:56 +01:00
Rob Ede
16125bd3be docs(multipart): doc PayloadBuffer::readline 2024-07-07 20:19:56 +01:00
Rob Ede
e9ccfbc866 refactor(multipart): clean up InnerField::poll 2024-07-07 20:19:35 +01:00
Rob Ede
e0e4d1e661 chore: move deny lints to manifests 2024-07-07 03:54:00 +01:00
70 changed files with 999 additions and 307 deletions

View File

@@ -49,7 +49,7 @@ jobs:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@@ -83,7 +83,7 @@ jobs:
uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Install just, cargo-hack
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: just,cargo-hack

View File

@@ -64,7 +64,7 @@ jobs:
toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean
@@ -113,7 +113,7 @@ jobs:
toolchain: nightly
- name: Install just
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: just

View File

@@ -24,7 +24,7 @@ jobs:
components: llvm-tools
- name: Install just, cargo-llvm-cov, cargo-nextest
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: just,cargo-llvm-cov,cargo-nextest

View File

@@ -76,7 +76,7 @@ jobs:
toolchain: nightly-2024-05-01
- name: Install just
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: just
@@ -105,7 +105,7 @@ jobs:
toolchain: nightly-2024-06-07
- name: Install cargo-public-api
uses: taiki-e/install-action@v2.41.7
uses: taiki-e/install-action@v2.42.17
with:
tool: cargo-public-api

View File

@@ -51,3 +51,11 @@ awc = { path = "awc" }
# actix-utils = { path = "../actix-net/actix-utils" }
# actix-tls = { path = "../actix-net/actix-tls" }
# actix-server = { path = "../actix-net/actix-server" }
[workspace.lints.rust]
rust_2018_idioms = { level = "deny" }
future_incompatible = { level = "deny" }
nonstandard_style = { level = "deny" }
[workspace.lints.clippy]
# clone_on_ref_ptr = { level = "deny" }

View File

@@ -54,3 +54,6 @@ actix-test = "0.1"
actix-web = "4"
env_logger = "0.11"
tempfile = "3.2"
[lints]
workspace = true

View File

@@ -11,8 +11,7 @@
//! .service(Files::new("/static", ".").prefer_utf8(true));
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
#![warn(missing_docs, missing_debug_implementations)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -59,3 +59,6 @@ tokio = { version = "1.24.2", features = ["sync"] }
[dev-dependencies]
actix-http = "3"
[lints]
workspace = true

View File

@@ -1,7 +1,5 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -2,6 +2,12 @@
## Unreleased
## 3.9.0
### Added
- Implement `FromIterator<(HeaderName, HeaderValue)>` for `HeaderMap`.
## 3.8.0
### Added

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.8.0"
version = "3.9.0"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@@ -167,6 +167,9 @@ tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls_023 = { package = "rustls", version = "0.23" }
tokio = { version = "1.24.2", features = ["net", "rt", "macros"] }
[lints]
workspace = true
[[example]]
name = "ws"
required-features = ["ws", "rustls-0_23"]

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

@@ -541,6 +541,6 @@ where
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data)
Dispatcher::new(io, Rc::clone(&self.flow), self.cfg.clone(), addr, conn_data)
}
}

View File

@@ -434,7 +434,7 @@ where
H2ServiceHandlerResponse {
state: State::Handshake(
Some(self.flow.clone()),
Some(Rc::clone(&self.flow)),
Some(self.cfg.clone()),
addr,
on_connect_data,

View File

@@ -13,8 +13,9 @@ use super::AsHeaderName;
/// `HeaderMap` is a "multi-map" of [`HeaderName`] to one or more [`HeaderValue`]s.
///
/// # Examples
///
/// ```
/// use actix_http::header::{self, HeaderMap, HeaderValue};
/// # use actix_http::header::{self, HeaderMap, HeaderValue};
///
/// let mut map = HeaderMap::new();
///
@@ -29,6 +30,21 @@ use super::AsHeaderName;
///
/// assert!(!map.contains_key(header::ORIGIN));
/// ```
///
/// Construct a header map using the [`FromIterator`] implementation. Note that it uses the append
/// strategy, so duplicate header names are preserved.
///
/// ```
/// use actix_http::header::{self, HeaderMap, HeaderValue};
///
/// let headers = HeaderMap::from_iter([
/// (header::CONTENT_TYPE, HeaderValue::from_static("text/plain")),
/// (header::COOKIE, HeaderValue::from_static("foo=1")),
/// (header::COOKIE, HeaderValue::from_static("bar=1")),
/// ]);
///
/// assert_eq!(headers.len(), 3);
/// ```
#[derive(Debug, Clone, Default)]
pub struct HeaderMap {
pub(crate) inner: AHashMap<HeaderName, Value>,
@@ -368,8 +384,8 @@ impl HeaderMap {
/// let removed = map.insert(header::ACCEPT, HeaderValue::from_static("text/html"));
/// assert!(!removed.is_empty());
/// ```
pub fn insert(&mut self, key: HeaderName, val: HeaderValue) -> Removed {
let value = self.inner.insert(key, Value::one(val));
pub fn insert(&mut self, name: HeaderName, val: HeaderValue) -> Removed {
let value = self.inner.insert(name, Value::one(val));
Removed::new(value)
}
@@ -636,6 +652,16 @@ impl<'a> IntoIterator for &'a HeaderMap {
}
}
impl FromIterator<(HeaderName, HeaderValue)> for HeaderMap {
fn from_iter<T: IntoIterator<Item = (HeaderName, HeaderValue)>>(iter: T) -> Self {
iter.into_iter()
.fold(Self::new(), |mut map, (name, value)| {
map.append(name, value);
map
})
}
}
/// Convert a `http::HeaderMap` to our `HeaderMap`.
impl From<http::HeaderMap> for HeaderMap {
fn from(mut map: http::HeaderMap) -> Self {

View File

@@ -20,8 +20,6 @@
//! [rustls]: https://crates.io/crates/rustls
//! [trust-dns]: https://crates.io/crates/trust-dns
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![allow(
clippy::type_complexity,
clippy::too_many_arguments,

View File

@@ -66,7 +66,7 @@ impl<T: Head> ops::DerefMut for Message<T> {
impl<T: Head> Drop for Message<T> {
fn drop(&mut self) {
T::with_pool(|p| p.release(self.head.clone()))
T::with_pool(|p| p.release(Rc::clone(&self.head)))
}
}

View File

@@ -910,7 +910,7 @@ where
handshake: Some((
crate::h2::handshake_with_timeout(io, &self.cfg),
self.cfg.clone(),
self.flow.clone(),
Rc::clone(&self.flow),
conn_data,
peer_addr,
)),
@@ -926,7 +926,7 @@ where
state: State::H1 {
dispatcher: h1::Dispatcher::new(
io,
self.flow.clone(),
Rc::clone(&self.flow),
self.cfg.clone(),
peer_addr,
conn_data,

View File

@@ -159,8 +159,8 @@ impl TestBuffer {
#[allow(dead_code)]
pub(crate) fn clone(&self) -> Self {
Self {
read_buf: self.read_buf.clone(),
write_buf: self.write_buf.clone(),
read_buf: Rc::clone(&self.read_buf),
write_buf: Rc::clone(&self.write_buf),
err: self.err.clone(),
}
}

View File

@@ -29,3 +29,6 @@ actix-multipart = "0.7"
actix-web = "4"
rustversion = "1"
trybuild = "1"
[lints]
workspace = true

View File

@@ -2,8 +2,6 @@
//!
//! See [`macro@MultipartForm`] for usage examples.
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -4,13 +4,14 @@ version = "0.7.2"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Jacob Halsey <jacob@jhalsey.com>",
"Rob Ede <robjtede@icloud.com>",
]
description = "Multipart form support 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"
description = "Multipart request & form support for Actix Web"
keywords = ["http", "actix", "web", "multipart", "form"]
homepage.workspace = true
repository.workspace = true
license.workspace = true
edition.workspace = true
[package.metadata.docs.rs]
rustdoc-args = ["--cfg", "docsrs"]
@@ -71,7 +72,5 @@ multer = "3"
tokio = { version = "1.24.2", features = ["sync"] }
tokio-stream = "0.1"
[lints.rust]
future_incompatible = { level = "deny" }
rust_2018_idioms = { level = "deny" }
nonstandard_style = { level = "deny" }
[lints]
workspace = true

View File

@@ -15,14 +15,18 @@
<!-- cargo-rdme start -->
Multipart form support for Actix Web.
Multipart request & form support for Actix Web.
The [`Multipart`] extractor aims to support all kinds of `multipart/*` requests, including `multipart/form-data`, `multipart/related` and `multipart/mixed`. This is a lower-level extractor which supports reading [multipart fields](Field), in the order they are sent by the client.
Due to additional requirements for `multipart/form-data` requests, the higher level [`MultipartForm`] extractor and derive macro only supports this media type.
## Examples
```rust
use actix_web::{post, App, HttpServer, Responder};
use actix_multipart::form::{json::Json as MPJson, tempfile::TempFile, MultipartForm};
use actix_multipart::form::{json::Json as MpJson, tempfile::TempFile, MultipartForm};
use serde::Deserialize;
#[derive(Debug, Deserialize)]
@@ -34,7 +38,7 @@ struct Metadata {
struct UploadForm {
#[multipart(limit = "100MB")]
file: TempFile,
json: MPJson<Metadata>,
json: MpJson<Metadata>,
}
#[post("/videos")]
@@ -63,6 +67,8 @@ curl -v --request POST \
-F file=@./Cargo.lock
```
[`MultipartForm`]: struct@form::MultipartForm
<!-- cargo-rdme end -->
[More available in the examples repo &rarr;](https://github.com/actix/examples/tree/master/forms/multipart)

View File

@@ -209,8 +209,14 @@ impl fmt::Debug for Field {
pub(crate) struct InnerField {
/// Payload is initialized as Some and is `take`n when the field stream finishes.
payload: Option<PayloadRef>,
/// Field boundary (without "--" prefix).
boundary: String,
/// True if request payload has been exhausted.
eof: bool,
/// Field data's stated size according to it's Content-Length header.
length: Option<u64>,
}
@@ -286,6 +292,7 @@ impl InnerField {
let mut pos = 0;
let len = payload.buf.len();
if len == 0 {
return if payload.eof {
Poll::Ready(Some(Err(Error::Incomplete)))
@@ -296,7 +303,7 @@ impl InnerField {
// check boundary
if len > 4 && payload.buf[0] == b'\r' {
let b_len = if &payload.buf[..2] == b"\r\n" && &payload.buf[2..4] == b"--" {
let b_len = if payload.buf.starts_with(b"\r\n") && &payload.buf[2..4] == b"--" {
Some(4)
} else if &payload.buf[1..3] == b"--" {
Some(3)
@@ -357,41 +364,42 @@ impl InnerField {
return Poll::Ready(None);
}
let result = if let Some(mut payload) = self
let Some(mut payload) = self
.payload
.as_ref()
.expect("Field should not be polled after completion")
.get_mut(safety)
{
if !self.eof {
let res = if let Some(ref mut len) = self.length {
InnerField::read_len(&mut payload, len)
} else {
InnerField::read_stream(&mut payload, &self.boundary)
};
match res {
Poll::Pending => return Poll::Pending,
Poll::Ready(Some(Ok(bytes))) => return Poll::Ready(Some(Ok(bytes))),
Poll::Ready(Some(Err(err))) => return Poll::Ready(Some(Err(err))),
Poll::Ready(None) => self.eof = true,
}
}
match payload.readline() {
Ok(None) => Poll::Pending,
Ok(Some(line)) => {
if line.as_ref() != b"\r\n" {
log::warn!("multipart field did not read all the data or it is malformed");
}
Poll::Ready(None)
}
Err(err) => Poll::Ready(Some(Err(err))),
}
} else {
Poll::Pending
else {
return Poll::Pending;
};
if !self.eof {
let res = if let Some(ref mut len) = self.length {
Self::read_len(&mut payload, len)
} else {
Self::read_stream(&mut payload, &self.boundary)
};
match ready!(res) {
Some(Ok(bytes)) => return Poll::Ready(Some(Ok(bytes))),
Some(Err(err)) => return Poll::Ready(Some(Err(err))),
None => self.eof = true,
}
}
let result = match payload.readline() {
Ok(None) => Poll::Pending,
Ok(Some(line)) => {
if line.as_ref() != b"\r\n" {
log::warn!("multipart field did not read all the data or it is malformed");
}
Poll::Ready(None)
}
Err(err) => Poll::Ready(Some(Err(err))),
};
drop(payload);
if let Poll::Ready(None) = result {
// drop payload buffer and make future un-poll-able
let _ = self.payload.take();

View File

@@ -1,4 +1,4 @@
//! Process and extract typed data from a multipart stream.
//! Extract and process typed data from fields of a `multipart/form-data` request.
use std::{
any::Any,

View File

@@ -1,4 +1,12 @@
//! Multipart form support for Actix Web.
//! Multipart request & form support for Actix Web.
//!
//! The [`Multipart`] extractor aims to support all kinds of `multipart/*` requests, including
//! `multipart/form-data`, `multipart/related` and `multipart/mixed`. This is a lower-level
//! extractor which supports reading [multipart fields](Field), in the order they are sent by the
//! client.
//!
//! Due to additional requirements for `multipart/form-data` requests, the higher level
//! [`MultipartForm`] extractor and derive macro only supports this media type.
//!
//! # Examples
//!
@@ -45,6 +53,8 @@
//! -F 'json={"name": "Cargo.lock"};type=application/json' \
//! -F file=@./Cargo.lock
//! ```
//!
//! [`MultipartForm`]: struct@form::MultipartForm
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]

View File

@@ -483,7 +483,7 @@ mod tests {
};
use assert_matches::assert_matches;
use futures_test::stream::StreamTestExt as _;
use futures_util::{future::lazy, stream, StreamExt as _};
use futures_util::{stream, StreamExt as _};
use tokio::sync::mpsc;
use tokio_stream::wrappers::UnboundedReceiverStream;
@@ -718,100 +718,6 @@ mod tests {
}
}
#[actix_rt::test]
async fn test_basic() {
let (_, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(payload.buf.len(), 0);
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(None, payload.read_max(1).unwrap());
}
#[actix_rt::test]
async fn test_eof() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(4).unwrap());
sender.feed_data(Bytes::from("data"));
sender.feed_eof();
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap());
assert_eq!(payload.buf.len(), 0);
assert!(payload.read_max(1).is_err());
assert!(payload.eof);
}
#[actix_rt::test]
async fn test_err() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(1).unwrap());
sender.set_error(PayloadError::Incomplete(None));
lazy(|cx| payload.poll_stream(cx)).await.err().unwrap();
}
#[actix_rt::test]
async fn read_max() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(payload.buf.len(), 10);
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap());
assert_eq!(payload.buf.len(), 5);
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap());
assert_eq!(payload.buf.len(), 0);
}
#[actix_rt::test]
async fn read_exactly() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_exact(2));
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
assert_eq!(payload.buf.len(), 8);
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
assert_eq!(payload.buf.len(), 4);
}
#[actix_rt::test]
async fn read_until() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_until(b"ne").unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(
Some(Bytes::from("line")),
payload.read_until(b"ne").unwrap()
);
assert_eq!(payload.buf.len(), 6);
assert_eq!(
Some(Bytes::from("1line2")),
payload.read_until(b"2").unwrap()
);
assert_eq!(payload.buf.len(), 0);
}
#[actix_rt::test]
async fn test_multipart_from_error() {
let err = Error::ContentTypeMissing;

View File

@@ -122,7 +122,13 @@ impl PayloadBuffer {
}
}
/// Reads bytes until new line delimiter.
/// Reads bytes until new line delimiter (`\n`, `0x0A`).
///
/// Returns:
///
/// - `Ok(Some(chunk))` - `needle` is found, with chunk ending after needle
/// - `Err(Incomplete)` - `needle` is not found and we're at EOF
/// - `Ok(None)` - `needle` is not found otherwise
#[inline]
pub(crate) fn readline(&mut self) -> Result<Option<Bytes>, Error> {
self.read_until(b"\n")
@@ -145,3 +151,105 @@ impl PayloadBuffer {
self.buf.extend_from_slice(&buf);
}
}
#[cfg(test)]
mod tests {
use actix_http::h1;
use futures_util::future::lazy;
use super::*;
#[actix_rt::test]
async fn basic() {
let (_, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(payload.buf.len(), 0);
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(None, payload.read_max(1).unwrap());
}
#[actix_rt::test]
async fn eof() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(4).unwrap());
sender.feed_data(Bytes::from("data"));
sender.feed_eof();
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(Some(Bytes::from("data")), payload.read_max(4).unwrap());
assert_eq!(payload.buf.len(), 0);
assert!(payload.read_max(1).is_err());
assert!(payload.eof);
}
#[actix_rt::test]
async fn err() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_max(1).unwrap());
sender.set_error(PayloadError::Incomplete(None));
lazy(|cx| payload.poll_stream(cx)).await.err().unwrap();
}
#[actix_rt::test]
async fn read_max() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(payload.buf.len(), 10);
assert_eq!(Some(Bytes::from("line1")), payload.read_max(5).unwrap());
assert_eq!(payload.buf.len(), 5);
assert_eq!(Some(Bytes::from("line2")), payload.read_max(5).unwrap());
assert_eq!(payload.buf.len(), 0);
}
#[actix_rt::test]
async fn read_exactly() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_exact(2));
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(Some(Bytes::from_static(b"li")), payload.read_exact(2));
assert_eq!(payload.buf.len(), 8);
assert_eq!(Some(Bytes::from_static(b"ne1l")), payload.read_exact(4));
assert_eq!(payload.buf.len(), 4);
}
#[actix_rt::test]
async fn read_until() {
let (mut sender, payload) = h1::Payload::create(false);
let mut payload = PayloadBuffer::new(payload);
assert_eq!(None, payload.read_until(b"ne").unwrap());
sender.feed_data(Bytes::from("line1"));
sender.feed_data(Bytes::from("line2"));
lazy(|cx| payload.poll_stream(cx)).await.unwrap();
assert_eq!(
Some(Bytes::from("line")),
payload.read_until(b"ne").unwrap()
);
assert_eq!(payload.buf.len(), 6);
assert_eq!(
Some(Bytes::from("1line2")),
payload.read_until(b"2").unwrap()
);
assert_eq!(payload.buf.len(), 0);
}
}

View File

@@ -38,6 +38,9 @@ http = "0.2.7"
serde = { version = "1", features = ["derive"] }
percent-encoding = "2.1"
[lints]
workspace = true
[[bench]]
name = "router"
harness = false

View File

@@ -511,11 +511,6 @@ mod tests {
value: String,
}
#[derive(Deserialize)]
struct Id {
_id: String,
}
#[derive(Debug, Deserialize)]
struct Test1(String, u32);

View File

@@ -1,7 +1,5 @@
//! Resource path matching and router.
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -73,3 +73,6 @@ 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"] }
[lints]
workspace = true

View File

@@ -27,8 +27,6 @@
//! }
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -2,7 +2,10 @@
## Unreleased
- Take the encoded buffer when yielding bytes in the response stream rather than splitting the buffer, reducing memory use
## 4.3.1 <!-- v4.3.1+deprecated -->
- Reduce memory usage by `take`-ing (rather than `split`-ing) the encoded buffer when yielding bytes in the response stream.
- Mark crate as deprecated.
- Minimum supported Rust version (MSRV) is now 1.72.
## 4.3.0

View File

@@ -1,13 +1,14 @@
[package]
name = "actix-web-actors"
version = "4.3.0"
version = "4.3.1+deprecated"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"]
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.cargo_check_external_types]
allowed_external_types = [
@@ -41,3 +42,6 @@ actix-web = { version = "4", features = ["macros"] }
env_logger = "0.11"
futures-util = { version = "0.3.17", default-features = false, features = ["std"] }
mime = "0.3"
[lints]
workspace = true

View File

@@ -1,15 +1,17 @@
# `actix-web-actors`
> Actix actors support for Actix Web.
>
> This crate is deprecated. Migrate to [`actix-ws`](https://crates.io/crates/actix-ws).
<!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.3.0)](https://docs.rs/actix-web-actors/4.3.0)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.3.1)](https://docs.rs/actix-web-actors/4.3.1)
![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.3.0/status.svg)](https://deps.rs/crate/actix-web-actors/4.3.0)
![maintenance-status](https://img.shields.io/badge/maintenance-deprecated-red.svg)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -1,5 +1,7 @@
//! Actix actors support for Actix Web.
//!
//! This crate is deprecated. Migrate to [`actix-ws`](https://crates.io/crates/actix-ws).
//!
//! # Examples
//!
//! ```no_run
@@ -55,8 +57,6 @@
//! * [`HttpContext`]: This struct provides actor support for streaming HTTP responses.
//!
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -35,3 +35,6 @@ actix-web = "4"
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
trybuild = "1"
rustversion = "1"
[lints]
workspace = true

View File

@@ -73,8 +73,6 @@
//! [DELETE]: macro@delete
#![recursion_limit = "512"]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]

View File

@@ -145,7 +145,7 @@ async fn custom_resource_name_test<'a>(req: HttpRequest) -> impl Responder {
mod guard_module {
use actix_web::{guard::GuardContext, http::header};
pub fn guard(ctx: &GuardContext) -> bool {
pub fn guard(ctx: &GuardContext<'_>) -> bool {
ctx.header::<header::Accept>()
.map(|h| h.preference() == "image/*")
.unwrap_or(false)

View File

@@ -1,7 +1,7 @@
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 {
pub fn image_guard(ctx: &GuardContext<'_>) -> bool {
ctx.header::<header::Accept>()
.map(|h| h.preference() == "image/*")
.unwrap_or(false)

View File

@@ -2,6 +2,11 @@
## Unreleased
### Added
- Add `middleware::from_fn()` helper.
- Add `web::ThinData` extractor.
## 4.8.0
### Added

View File

@@ -151,6 +151,7 @@ encoding_rs = "0.8"
futures-core = { version = "0.3.17", default-features = false }
futures-util = { version = "0.3.17", default-features = false }
itoa = "1"
impl-more = "0.1.4"
language-tags = "0.3"
log = "0.4"
mime = "0.3"
@@ -188,6 +189,9 @@ tls-rustls = { package = "rustls", version = "0.23" }
tokio = { version = "1.24.2", features = ["rt-multi-thread", "macros"] }
zstd = "0.13"
[lints]
workspace = true
[[test]]
name = "test_server"
required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]

View File

@@ -2,11 +2,9 @@ use std::{future::Future, time::Instant};
use actix_http::body::BoxBody;
use actix_utils::future::{ready, Ready};
use actix_web::{
error, http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder,
};
use actix_web::{http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder};
use criterion::{criterion_group, criterion_main, Criterion};
use futures_util::future::{join_all, Either};
use futures_util::future::join_all;
// responder simulate the old responder trait.
trait FutureResponder {
@@ -16,9 +14,6 @@ trait FutureResponder {
fn future_respond_to(self, req: &HttpRequest) -> Self::Future;
}
// a simple option responder type.
struct OptionResponder<T>(Option<T>);
// a simple wrapper type around string
struct StringResponder(String);
@@ -34,22 +29,6 @@ impl FutureResponder for StringResponder {
}
}
impl<T> FutureResponder for OptionResponder<T>
where
T: FutureResponder,
T::Future: Future<Output = Result<HttpResponse, Error>>,
{
type Error = Error;
type Future = Either<T::Future, Ready<Result<HttpResponse, Self::Error>>>;
fn future_respond_to(self, req: &HttpRequest) -> Self::Future {
match self.0 {
Some(t) => Either::Left(t.future_respond_to(req)),
None => Either::Right(ready(Err(error::ErrorInternalServerError("err")))),
}
}
}
impl Responder for StringResponder {
type Body = BoxBody;
@@ -60,17 +39,6 @@ impl Responder for StringResponder {
}
}
impl<T: Responder> Responder for OptionResponder<T> {
type Body = BoxBody;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
match self.0 {
Some(t) => t.respond_to(req).map_into_boxed_body(),
None => HttpResponse::from_error(error::ErrorInternalServerError("err")),
}
}
}
fn future_responder(c: &mut Criterion) {
let rt = actix_rt::System::new();
let req = TestRequest::default().to_http_request();

View File

@@ -0,0 +1,127 @@
//! Shows a couple of ways to use the `from_fn` middleware.
use std::{collections::HashMap, io, rc::Rc, time::Duration};
use actix_web::{
body::MessageBody,
dev::{Service, ServiceRequest, ServiceResponse, Transform},
http::header::{self, HeaderValue, Range},
middleware::{from_fn, Logger, Next},
web::{self, Header, Query},
App, Error, HttpResponse, HttpServer,
};
async fn noop<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
next.call(req).await
}
async fn print_range_header<B>(
range_header: Option<Header<Range>>,
req: ServiceRequest,
next: Next<B>,
) -> Result<ServiceResponse<B>, Error> {
if let Some(Header(range)) = range_header {
println!("Range: {range}");
} else {
println!("No Range header");
}
next.call(req).await
}
async fn mutate_body_type(
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
let res = next.call(req).await?;
Ok(res.map_into_left_body::<()>())
}
async fn mutate_body_type_with_extractors(
string_body: String,
query: Query<HashMap<String, String>>,
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
println!("body is: {string_body}");
println!("query string: {query:?}");
let res = next.call(req).await?;
Ok(res.map_body(move |_, _| string_body))
}
async fn timeout_10secs(
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
match tokio::time::timeout(Duration::from_secs(10), next.call(req)).await {
Ok(res) => res,
Err(_err) => Err(actix_web::error::ErrorRequestTimeout("")),
}
}
struct MyMw(bool);
impl MyMw {
async fn mw_cb(
&self,
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
let mut res = match self.0 {
true => req.into_response("short-circuited").map_into_right_body(),
false => next.call(req).await?.map_into_left_body(),
};
res.headers_mut()
.insert(header::WARNING, HeaderValue::from_static("42"));
Ok(res)
}
pub fn into_middleware<S, B>(
self,
) -> impl Transform<
S,
ServiceRequest,
Response = ServiceResponse<impl MessageBody>,
Error = Error,
InitError = (),
>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: MessageBody + 'static,
{
let this = Rc::new(self);
from_fn(move |req, next| {
let this = Rc::clone(&this);
async move { Self::mw_cb(&this, req, next).await }
})
}
}
#[actix_web::main]
async fn main() -> io::Result<()> {
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
let bind = ("127.0.0.1", 8080);
log::info!("staring server at http://{}:{}", &bind.0, &bind.1);
HttpServer::new(|| {
App::new()
.wrap(from_fn(noop))
.wrap(from_fn(print_range_header))
.wrap(from_fn(mutate_body_type))
.wrap(from_fn(mutate_body_type_with_extractors))
.wrap(from_fn(timeout_10secs))
// switch bool to true to observe early response
.wrap(MyMw(false).into_middleware())
.wrap(Logger::default())
.default_service(web::to(HttpResponse::Ok))
})
.workers(1)
.bind(bind)?
.run()
.await
}

View File

@@ -39,7 +39,7 @@ impl App<AppEntry> {
let factory_ref = Rc::new(RefCell::new(None));
App {
endpoint: AppEntry::new(factory_ref.clone()),
endpoint: AppEntry::new(Rc::clone(&factory_ref)),
data_factories: Vec::new(),
services: Vec::new(),
default: None,

View File

@@ -71,7 +71,7 @@ where
});
// create App config to pass to child services
let mut config = AppService::new(config, default.clone());
let mut config = AppService::new(config, Rc::clone(&default));
// register services
mem::take(&mut *self.services.borrow_mut())

View File

@@ -68,7 +68,7 @@ impl AppService {
pub(crate) fn clone_config(&self) -> Self {
AppService {
config: self.config.clone(),
default: self.default.clone(),
default: Rc::clone(&self.default),
services: Vec::new(),
root: false,
}
@@ -81,7 +81,7 @@ impl AppService {
/// Returns default handler factory.
pub fn default_service(&self) -> Rc<BoxedHttpServiceFactory> {
self.default.clone()
Rc::clone(&self.default)
}
/// Register HTTP service.

View File

@@ -184,7 +184,7 @@ impl<T: ?Sized + 'static> FromRequest for Data<T> {
impl<T: ?Sized + 'static> DataFactory for Data<T> {
fn create(&self, extensions: &mut Extensions) -> bool {
extensions.insert(Data(self.0.clone()));
extensions.insert(Data(Arc::clone(&self.0)));
true
}
}

View File

@@ -19,7 +19,7 @@ use crate::{
/// 1. It is an async function (or a function/closure that returns an appropriate future);
/// 1. The function parameters (up to 12) implement [`FromRequest`];
/// 1. The async function (or future) resolves to a type that can be converted into an
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
/// [`HttpResponse`] (i.e., it implements the [`Responder`] trait).
///
///
/// # Compiler Errors

View File

@@ -493,7 +493,7 @@ impl Header for ContentDisposition {
}
fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> {
if let Some(h) = msg.headers().get(&Self::name()) {
if let Some(h) = msg.headers().get(Self::name()) {
Self::from_raw(h)
} else {
Err(crate::error::ParseError::Header)

View File

@@ -107,16 +107,16 @@ impl ByteRangeSpec {
/// satisfiable if they meet the following conditions:
///
/// > If a valid byte-range-set includes at least one byte-range-spec with a first-byte-pos that
/// is less than the current length of the representation, or at least one
/// suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set
/// is satisfiable. Otherwise, the byte-range-set is unsatisfiable.
/// > is less than the current length of the representation, or at least one
/// > suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set is
/// > satisfiable. Otherwise, the byte-range-set is unsatisfiable.
///
/// The function also computes remainder ranges based on the RFC:
///
/// > If the last-byte-pos value is absent, or if the value is greater than or equal to the
/// current length of the representation data, the byte range is interpreted as the remainder
/// of the representation (i.e., the server replaces the value of last-byte-pos with a value
/// that is one less than the current length of the selected representation).
/// > current length of the representation data, the byte range is interpreted as the remainder
/// > of the representation (i.e., the server replaces the value of last-byte-pos with a value
/// > that is one less than the current length of the selected representation).
///
/// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233
pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> {
@@ -270,7 +270,7 @@ impl Header for Range {
#[inline]
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> {
header::from_one_raw_str(msg.headers().get(&Self::name()))
header::from_one_raw_str(msg.headers().get(Self::name()))
}
}

View File

@@ -70,8 +70,6 @@
//! - `rustls-0_23` - HTTPS support via `rustls` 0.23 crate, supports `HTTP/2`
//! - `secure-cookies` - secure cookies support
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))]
@@ -106,6 +104,7 @@ mod scope;
mod server;
mod service;
pub mod test;
mod thin_data;
pub(crate) mod types;
pub mod web;

View File

@@ -141,7 +141,7 @@ where
actix_service::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let inner = self.inner.clone();
let inner = Rc::clone(&self.inner);
let fut = self.service.call(req);
DefaultHeaderFuture {

View File

@@ -220,16 +220,20 @@ impl<B> ErrorHandlers<B> {
/// [`.handler()`][ErrorHandlers::handler]) will fall back on this.
///
/// Note that this will overwrite any default handlers previously set by calling
/// [`.default_handler_client()`][ErrorHandlers::default_handler_client] or
/// [`.default_handler_server()`][ErrorHandlers::default_handler_server], but not any set by
/// calling [`.handler()`][ErrorHandlers::handler].
/// [`default_handler_client()`] or [`.default_handler_server()`], but not any set by calling
/// [`.handler()`].
///
/// [`default_handler_client()`]: ErrorHandlers::default_handler_client
/// [`.default_handler_server()`]: ErrorHandlers::default_handler_server
/// [`.handler()`]: ErrorHandlers::handler
pub fn default_handler<F>(self, handler: F) -> Self
where
F: Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> + 'static,
{
let handler = Rc::new(handler);
let handler2 = Rc::clone(&handler);
Self {
default_server: Some(handler.clone()),
default_server: Some(handler2),
default_client: Some(handler),
..self
}
@@ -288,7 +292,7 @@ where
type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
let handlers = self.handlers.clone();
let handlers = Rc::clone(&self.handlers);
let default_client = self.default_client.clone();
let default_server = self.default_server.clone();
Box::pin(async move {
@@ -323,7 +327,7 @@ where
actix_service::forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
let handlers = self.handlers.clone();
let handlers = Rc::clone(&self.handlers);
let default_client = self.default_client.clone();
let default_server = self.default_server.clone();
let fut = self.service.call(req);

View File

@@ -0,0 +1,349 @@
use std::{future::Future, marker::PhantomData, rc::Rc};
use actix_service::boxed::{self, BoxFuture, RcService};
use actix_utils::future::{ready, Ready};
use futures_core::future::LocalBoxFuture;
use crate::{
body::MessageBody,
dev::{forward_ready, Service, ServiceRequest, ServiceResponse, Transform},
Error, FromRequest,
};
/// Wraps an async function to be used as a middleware.
///
/// # Examples
///
/// The wrapped function should have the following form:
///
/// ```
/// # use actix_web::{
/// # App, Error,
/// # body::MessageBody,
/// # dev::{ServiceRequest, ServiceResponse, Service as _},
/// # };
/// use actix_web::middleware::{self, Next};
///
/// async fn my_mw(
/// req: ServiceRequest,
/// next: Next<impl MessageBody>,
/// ) -> Result<ServiceResponse<impl MessageBody>, Error> {
/// // pre-processing
/// next.call(req).await
/// // post-processing
/// }
/// # App::new().wrap(middleware::from_fn(my_mw));
/// ```
///
/// Then use in an app builder like this:
///
/// ```
/// use actix_web::{
/// App, Error,
/// dev::{ServiceRequest, ServiceResponse, Service as _},
/// };
/// use actix_web::middleware::from_fn;
/// # use actix_web::middleware::Next;
/// # async fn my_mw<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
/// # next.call(req).await
/// # }
///
/// App::new()
/// .wrap(from_fn(my_mw))
/// # ;
/// ```
///
/// It is also possible to write a middleware that automatically uses extractors, similar to request
/// handlers, by declaring them as the first parameters. As usual, **take care with extractors that
/// consume the body stream**, since handlers will no longer be able to read it again without
/// putting the body "back" into the request object within your middleware.
///
/// ```
/// # use std::collections::HashMap;
/// # use actix_web::{
/// # App, Error,
/// # body::MessageBody,
/// # dev::{ServiceRequest, ServiceResponse},
/// # http::header::{Accept, Date},
/// # web::{Header, Query},
/// # };
/// use actix_web::middleware::Next;
///
/// async fn my_extracting_mw(
/// accept: Header<Accept>,
/// query: Query<HashMap<String, String>>,
/// req: ServiceRequest,
/// next: Next<impl MessageBody>,
/// ) -> Result<ServiceResponse<impl MessageBody>, Error> {
/// // pre-processing
/// next.call(req).await
/// // post-processing
/// }
/// # App::new().wrap(actix_web::middleware::from_fn(my_extracting_mw));
pub fn from_fn<F, Es>(mw_fn: F) -> MiddlewareFn<F, Es> {
MiddlewareFn {
mw_fn: Rc::new(mw_fn),
_phantom: PhantomData,
}
}
/// Middleware transform for [`from_fn`].
#[allow(missing_debug_implementations)]
pub struct MiddlewareFn<F, Es> {
mw_fn: Rc<F>,
_phantom: PhantomData<Es>,
}
impl<S, F, Fut, B, B2> Transform<S, ServiceRequest> for MiddlewareFn<F, ()>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
F: Fn(ServiceRequest, Next<B>) -> Fut + 'static,
Fut: Future<Output = Result<ServiceResponse<B2>, Error>>,
B2: MessageBody,
{
type Response = ServiceResponse<B2>;
type Error = Error;
type Transform = MiddlewareFnService<F, B, ()>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(MiddlewareFnService {
service: boxed::rc_service(service),
mw_fn: Rc::clone(&self.mw_fn),
_phantom: PhantomData,
}))
}
}
/// Middleware service for [`from_fn`].
#[allow(missing_debug_implementations)]
pub struct MiddlewareFnService<F, B, Es> {
service: RcService<ServiceRequest, ServiceResponse<B>, Error>,
mw_fn: Rc<F>,
_phantom: PhantomData<(B, Es)>,
}
impl<F, Fut, B, B2> Service<ServiceRequest> for MiddlewareFnService<F, B, ()>
where
F: Fn(ServiceRequest, Next<B>) -> Fut,
Fut: Future<Output = Result<ServiceResponse<B2>, Error>>,
B2: MessageBody,
{
type Response = ServiceResponse<B2>;
type Error = Error;
type Future = Fut;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
(self.mw_fn)(
req,
Next::<B> {
service: Rc::clone(&self.service),
},
)
}
}
macro_rules! impl_middleware_fn_service {
($($ext_type:ident),*) => {
impl<S, F, Fut, B, B2, $($ext_type),*> Transform<S, ServiceRequest> for MiddlewareFn<F, ($($ext_type),*,)>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
F: Fn($($ext_type),*, ServiceRequest, Next<B>) -> Fut + 'static,
$($ext_type: FromRequest + 'static,)*
Fut: Future<Output = Result<ServiceResponse<B2>, Error>> + 'static,
B: MessageBody + 'static,
B2: MessageBody + 'static,
{
type Response = ServiceResponse<B2>;
type Error = Error;
type Transform = MiddlewareFnService<F, B, ($($ext_type,)*)>;
type InitError = ();
type Future = Ready<Result<Self::Transform, Self::InitError>>;
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(MiddlewareFnService {
service: boxed::rc_service(service),
mw_fn: Rc::clone(&self.mw_fn),
_phantom: PhantomData,
}))
}
}
impl<F, $($ext_type),*, Fut, B: 'static, B2> Service<ServiceRequest>
for MiddlewareFnService<F, B, ($($ext_type),*,)>
where
F: Fn(
$($ext_type),*,
ServiceRequest,
Next<B>
) -> Fut + 'static,
$($ext_type: FromRequest + 'static,)*
Fut: Future<Output = Result<ServiceResponse<B2>, Error>> + 'static,
B2: MessageBody + 'static,
{
type Response = ServiceResponse<B2>;
type Error = Error;
type Future = LocalBoxFuture<'static, Result<Self::Response, Self::Error>>;
forward_ready!(service);
#[allow(nonstandard_style)]
fn call(&self, mut req: ServiceRequest) -> Self::Future {
let mw_fn = Rc::clone(&self.mw_fn);
let service = Rc::clone(&self.service);
Box::pin(async move {
let ($($ext_type,)*) = req.extract::<($($ext_type,)*)>().await?;
(mw_fn)($($ext_type),*, req, Next::<B> { service }).await
})
}
}
};
}
impl_middleware_fn_service!(E1);
impl_middleware_fn_service!(E1, E2);
impl_middleware_fn_service!(E1, E2, E3);
impl_middleware_fn_service!(E1, E2, E3, E4);
impl_middleware_fn_service!(E1, E2, E3, E4, E5);
impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6);
impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7);
impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7, E8);
impl_middleware_fn_service!(E1, E2, E3, E4, E5, E6, E7, E8, E9);
/// Wraps the "next" service in the middleware chain.
#[allow(missing_debug_implementations)]
pub struct Next<B> {
service: RcService<ServiceRequest, ServiceResponse<B>, Error>,
}
impl<B> Next<B> {
/// Equivalent to `Service::call(self, req)`.
pub fn call(&self, req: ServiceRequest) -> <Self as Service<ServiceRequest>>::Future {
Service::call(self, req)
}
}
impl<B> Service<ServiceRequest> for Next<B> {
type Response = ServiceResponse<B>;
type Error = Error;
type Future = BoxFuture<Result<Self::Response, Self::Error>>;
forward_ready!(service);
fn call(&self, req: ServiceRequest) -> Self::Future {
self.service.call(req)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::{
http::header::{self, HeaderValue},
middleware::{Compat, Logger},
test, web, App, HttpResponse,
};
async fn noop<B>(req: ServiceRequest, next: Next<B>) -> Result<ServiceResponse<B>, Error> {
next.call(req).await
}
async fn add_res_header<B>(
req: ServiceRequest,
next: Next<B>,
) -> Result<ServiceResponse<B>, Error> {
let mut res = next.call(req).await?;
res.headers_mut()
.insert(header::WARNING, HeaderValue::from_static("42"));
Ok(res)
}
async fn mutate_body_type(
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
let res = next.call(req).await?;
Ok(res.map_into_left_body::<()>())
}
struct MyMw(bool);
impl MyMw {
async fn mw_cb(
&self,
req: ServiceRequest,
next: Next<impl MessageBody + 'static>,
) -> Result<ServiceResponse<impl MessageBody>, Error> {
let mut res = match self.0 {
true => req.into_response("short-circuited").map_into_right_body(),
false => next.call(req).await?.map_into_left_body(),
};
res.headers_mut()
.insert(header::WARNING, HeaderValue::from_static("42"));
Ok(res)
}
pub fn into_middleware<S, B>(
self,
) -> impl Transform<
S,
ServiceRequest,
Response = ServiceResponse<impl MessageBody>,
Error = Error,
InitError = (),
>
where
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error> + 'static,
B: MessageBody + 'static,
{
let this = Rc::new(self);
from_fn(move |req, next| {
let this = Rc::clone(&this);
async move { Self::mw_cb(&this, req, next).await }
})
}
}
#[actix_rt::test]
async fn compat_compat() {
let _ = App::new().wrap(Compat::new(from_fn(noop)));
let _ = App::new().wrap(Compat::new(from_fn(mutate_body_type)));
}
#[actix_rt::test]
async fn permits_different_in_and_out_body_types() {
let app = test::init_service(
App::new()
.wrap(from_fn(mutate_body_type))
.wrap(from_fn(add_res_header))
.wrap(Logger::default())
.wrap(from_fn(noop))
.default_service(web::to(HttpResponse::NotFound)),
)
.await;
let req = test::TestRequest::default().to_request();
let res = test::call_service(&app, req).await;
assert!(res.headers().contains_key(header::WARNING));
}
#[actix_rt::test]
async fn closure_capture_and_return_from_fn() {
let app = test::init_service(
App::new()
.wrap(Logger::default())
.wrap(MyMw(true).into_middleware())
.wrap(Logger::default()),
)
.await;
let req = test::TestRequest::default().to_request();
let res = test::call_service(&app, req).await;
assert!(res.headers().contains_key(header::WARNING));
}
}

View File

@@ -276,7 +276,7 @@ where
ready(Ok(LoggerMiddleware {
service,
inner: self.0.clone(),
inner: Rc::clone(&self.0),
}))
}
}
@@ -622,11 +622,7 @@ impl FormatText {
FormatText::ResponseHeader(ref name) => {
let s = if let Some(val) = res.headers().get(name) {
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
val.to_str().unwrap_or("-")
} else {
"-"
};
@@ -670,11 +666,7 @@ impl FormatText {
FormatText::RequestTime => *self = FormatText::Str(now.format(&Rfc3339).unwrap()),
FormatText::RequestHeader(ref name) => {
let s = if let Some(val) = req.headers().get(name) {
if let Ok(s) = val.to_str() {
s
} else {
"-"
}
val.to_str().unwrap_or("-")
} else {
"-"
};

View File

@@ -15,10 +15,47 @@
//! - Access external services (e.g., [sessions](https://docs.rs/actix-session), etc.)
//!
//! Middleware is registered for each [`App`], [`Scope`](crate::Scope), or
//! [`Resource`](crate::Resource) and executed in opposite order as registration. In general, a
//! middleware is a pair of types that implements the [`Service`] trait and [`Transform`] trait,
//! respectively. The [`new_transform`] and [`call`] methods must return a [`Future`], though it
//! can often be [an immediately-ready one](actix_utils::future::Ready).
//! [`Resource`](crate::Resource) and executed in opposite order as registration.
//!
//! # Simple Middleware
//!
//! In many cases, you can model your middleware as an async function via the [`from_fn()`] helper
//! that provides a natural interface for implementing your desired behaviors.
//!
//! ```
//! # use actix_web::{
//! # App, Error,
//! # body::MessageBody,
//! # dev::{ServiceRequest, ServiceResponse, Service as _},
//! # };
//! use actix_web::middleware::{self, Next};
//!
//! async fn my_mw(
//! req: ServiceRequest,
//! next: Next<impl MessageBody>,
//! ) -> Result<ServiceResponse<impl MessageBody>, Error> {
//! // pre-processing
//!
//! // invoke the wrapped middleware or service
//! let res = next.call(req).await?;
//!
//! // post-processing
//!
//! Ok(res)
//! }
//!
//! App::new()
//! .wrap(middleware::from_fn(my_mw));
//! ```
//!
//! ## Complex Middleware
//!
//! In the more general ase, a middleware is a pair of types that implements the [`Service`] trait
//! and [`Transform`] trait, respectively. The [`new_transform`] and [`call`] methods must return a
//! [`Future`], though it can often be [an immediately-ready one](actix_utils::future::Ready).
//!
//! All the built-in middleware use this pattern with pairs of builder (`Transform`) +
//! implementation (`Service`) types.
//!
//! # Ordering
//!
@@ -67,7 +104,7 @@
//! Response
//! ```
//! The request _first_ gets processed by the middleware specified _last_ - `MiddlewareC`. It passes
//! the request (modified a modified one) to the next middleware - `MiddlewareB` - _or_ directly
//! the request (possibly a modified one) to the next middleware - `MiddlewareB` - _or_ directly
//! responds to the request (e.g. when the request was invalid or an error occurred). `MiddlewareB`
//! processes the request as well and passes it to `MiddlewareA`, which then passes it to the
//! [`Service`]. In the [`Service`], the extractors will run first. They don't pass the request on,
@@ -196,18 +233,6 @@
//! # }
//! ```
//!
//! # Simpler Middleware
//!
//! In many cases, you _can_ actually use an async function via a helper that will provide a more
//! natural flow for your behavior.
//!
//! The experimental `actix_web_lab` crate provides a [`from_fn`][lab_from_fn] utility which allows
//! an async fn to be wrapped and used in the same way as other middleware. See the
//! [`from_fn`][lab_from_fn] docs for more info and examples of it's use.
//!
//! While [`from_fn`][lab_from_fn] is experimental currently, it's likely this helper will graduate
//! to Actix Web in some form, so feedback is appreciated.
//!
//! [`Future`]: std::future::Future
//! [`App`]: crate::App
//! [`FromRequest`]: crate::FromRequest
@@ -215,7 +240,7 @@
//! [`Transform`]: crate::dev::Transform
//! [`call`]: crate::dev::Service::call()
//! [`new_transform`]: crate::dev::Transform::new_transform()
//! [lab_from_fn]: https://docs.rs/actix-web-lab/latest/actix_web_lab/middleware/fn.from_fn.html
//! [`from_fn`]: crate
mod compat;
#[cfg(feature = "__compress")]
@@ -223,6 +248,7 @@ mod compress;
mod condition;
mod default_headers;
mod err_handlers;
mod from_fn;
mod identity;
mod logger;
mod normalize;
@@ -234,6 +260,7 @@ pub use self::{
condition::Condition,
default_headers::DefaultHeaders,
err_handlers::{ErrorHandlerResponse, ErrorHandlers},
from_fn::{from_fn, Next},
identity::Identity,
logger::Logger,
normalize::{NormalizePath, TrailingSlash},

View File

@@ -62,14 +62,14 @@ pub struct Resource<T = ResourceEndpoint> {
impl Resource {
/// Constructs new resource that matches a `path` pattern.
pub fn new<T: IntoPatterns>(path: T) -> Resource {
let fref = Rc::new(RefCell::new(None));
let factory_ref = Rc::new(RefCell::new(None));
Resource {
routes: Vec::new(),
rdef: path.patterns(),
name: None,
endpoint: ResourceEndpoint::new(fref.clone()),
factory_ref: fref,
endpoint: ResourceEndpoint::new(Rc::clone(&factory_ref)),
factory_ref,
guards: Vec::new(),
app_data: None,
default: boxed::factory(fn_service(|req: ServiceRequest| async {

View File

@@ -463,7 +463,7 @@ mod tests {
// content type override
let res = HttpResponse::Ok()
.insert_header((CONTENT_TYPE, "text/json"))
.json(&vec!["v1", "v2", "v3"]);
.json(["v1", "v2", "v3"]);
let ct = res.headers().get(CONTENT_TYPE).unwrap();
assert_eq!(ct, HeaderValue::from_static("text/json"));
assert_body_eq!(res, br#"["v1","v2","v3"]"#);

View File

@@ -77,7 +77,7 @@ impl ServiceFactory<ServiceRequest> for Route {
fn new_service(&self, _: ()) -> Self::Future {
let fut = self.service.new_service(());
let guards = self.guards.clone();
let guards = Rc::clone(&self.guards);
Box::pin(async move {
let service = fut.await?;

View File

@@ -510,7 +510,7 @@ where
/// No changes are made to `lst`'s configuration. Ensure it is configured properly before
/// passing ownership to `listen()`.
pub fn listen(mut self, lst: net::TcpListener) -> io::Result<Self> {
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let factory = self.factory.clone();
let addr = lst.local_addr().unwrap();
@@ -554,7 +554,7 @@ where
/// Binds to existing listener for accepting incoming plaintext HTTP/1.x or HTTP/2 connections.
#[cfg(feature = "http2")]
pub fn listen_auto_h2c(mut self, lst: net::TcpListener) -> io::Result<Self> {
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let factory = self.factory.clone();
let addr = lst.local_addr().unwrap();
@@ -632,7 +632,7 @@ where
config: actix_tls::accept::rustls_0_20::reexports::ServerConfig,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
@@ -683,7 +683,7 @@ where
config: actix_tls::accept::rustls_0_21::reexports::ServerConfig,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
@@ -749,7 +749,7 @@ where
config: actix_tls::accept::rustls_0_22::reexports::ServerConfig,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
@@ -815,7 +815,7 @@ where
config: actix_tls::accept::rustls_0_23::reexports::ServerConfig,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
@@ -880,7 +880,7 @@ where
acceptor: SslAcceptor,
) -> io::Result<Self> {
let factory = self.factory.clone();
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let addr = lst.local_addr().unwrap();
self.sockets.push(Socket {
addr,
@@ -937,7 +937,7 @@ where
use actix_rt::net::UnixStream;
use actix_service::{fn_service, ServiceFactoryExt as _};
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let factory = self.factory.clone();
let socket_addr =
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080);
@@ -982,7 +982,7 @@ where
use actix_rt::net::UnixStream;
use actix_service::{fn_service, ServiceFactoryExt as _};
let cfg = self.config.clone();
let cfg = Arc::clone(&self.config);
let factory = self.factory.clone();
let socket_addr =
net::SocketAddr::new(net::IpAddr::V4(net::Ipv4Addr::new(127, 0, 0, 1)), 8080);

121
actix-web/src/thin_data.rs Normal file
View File

@@ -0,0 +1,121 @@
use std::any::type_name;
use actix_utils::future::{ready, Ready};
use crate::{dev::Payload, error, FromRequest, HttpRequest};
/// Application data wrapper and extractor for cheaply-cloned types.
///
/// Similar to the [`Data`] wrapper but for `Clone`/`Copy` types that are already an `Arc` internally,
/// share state using some other means when cloned, or is otherwise static data that is very cheap
/// to clone.
///
/// Unlike `Data`, this wrapper clones `T` during extraction. Therefore, it is the user's
/// responsibility to ensure that clones of `T` do actually share the same state, otherwise state
/// may be unexpectedly different across multiple requests.
///
/// Note that if your type is literally an `Arc<T>` then it's recommended to use the
/// [`Data::from(arc)`][data_from_arc] conversion instead.
///
/// # Examples
///
/// ```
/// use actix_web::{
/// web::{self, ThinData},
/// App, HttpResponse, Responder,
/// };
///
/// // Use the `ThinData<T>` extractor to access a database connection pool.
/// async fn index(ThinData(db_pool): ThinData<DbPool>) -> impl Responder {
/// // database action ...
///
/// HttpResponse::Ok()
/// }
///
/// # type DbPool = ();
/// let db_pool = DbPool::default();
///
/// App::new()
/// .app_data(ThinData(db_pool.clone()))
/// .service(web::resource("/").get(index))
/// # ;
/// ```
///
/// [`Data`]: crate::web::Data
/// [data_from_arc]: crate::web::Data#impl-From<Arc<T>>-for-Data<T>
#[derive(Debug, Clone)]
pub struct ThinData<T>(pub T);
impl_more::impl_as_ref!(ThinData<T> => T);
impl_more::impl_as_mut!(ThinData<T> => T);
impl_more::impl_deref_and_mut!(<T> in ThinData<T> => T);
impl<T: Clone + 'static> FromRequest for ThinData<T> {
type Error = crate::Error;
type Future = Ready<Result<Self, Self::Error>>;
#[inline]
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
ready(req.app_data::<Self>().cloned().ok_or_else(|| {
log::debug!(
"Failed to extract `ThinData<{}>` for `{}` handler. For the ThinData extractor to work \
correctly, wrap the data with `ThinData()` and pass it to `App::app_data()`. \
Ensure that types align in both the set and retrieve calls.",
type_name::<T>(),
req.match_name().unwrap_or(req.path())
);
error::ErrorInternalServerError(
"Requested application data is not configured correctly. \
View/enable debug logs for more details.",
)
}))
}
}
#[cfg(test)]
mod tests {
use std::sync::{Arc, Mutex};
use super::*;
use crate::{
http::StatusCode,
test::{call_service, init_service, TestRequest},
web, App, HttpResponse,
};
type TestT = Arc<Mutex<u32>>;
#[actix_rt::test]
async fn thin_data() {
let test_data = TestT::default();
let app = init_service(App::new().app_data(ThinData(test_data.clone())).service(
web::resource("/").to(|td: ThinData<TestT>| {
*td.lock().unwrap() += 1;
HttpResponse::Ok()
}),
))
.await;
for _ in 0..3 {
let req = TestRequest::default().to_request();
let resp = call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::OK);
}
assert_eq!(*test_data.lock().unwrap(), 3);
}
#[actix_rt::test]
async fn thin_data_missing() {
let app = init_service(
App::new().service(web::resource("/").to(|_: ThinData<u32>| HttpResponse::Ok())),
)
.await;
let req = TestRequest::default().to_request();
let resp = call_service(&app, req).await;
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
}
}

View File

@@ -2,6 +2,7 @@
//!
//! # Request Extractors
//! - [`Data`]: Application data item
//! - [`ThinData`]: Cheap-to-clone application data item
//! - [`ReqData`]: Request-local data item
//! - [`Path`]: URL path parameters / dynamic segments
//! - [`Query`]: URL query parameters
@@ -22,7 +23,8 @@ use actix_router::IntoPatterns;
pub use bytes::{Buf, BufMut, Bytes, BytesMut};
pub use crate::{
config::ServiceConfig, data::Data, redirect::Redirect, request_data::ReqData, types::*,
config::ServiceConfig, data::Data, redirect::Redirect, request_data::ReqData,
thin_data::ThinData, types::*,
};
use crate::{
error::BlockingError, http::Method, service::WebService, FromRequest, Handler, Resource,

View File

@@ -153,6 +153,9 @@ 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
[lints]
workspace = true
[[example]]
name = "client"
required-features = ["rustls-0_23-webpki-roots"]

View File

@@ -163,6 +163,7 @@ mod tests {
use super::*;
#[allow(dead_code)]
struct PinType(PhantomPinned);
impl MessageBody for PinType {

View File

@@ -173,12 +173,15 @@ where
};
// acquire an owned permit and carry it with connection
let permit = inner.permits.clone().acquire_owned().await.map_err(|_| {
ConnectError::Io(io::Error::new(
io::ErrorKind::Other,
"failed to acquire semaphore on client connection pool",
))
})?;
let permit = Arc::clone(&inner.permits)
.acquire_owned()
.await
.map_err(|_| {
ConnectError::Io(io::Error::new(
io::ErrorKind::Other,
"failed to acquire semaphore on client connection pool",
))
})?;
let conn = {
let mut conn = None;

View File

@@ -49,7 +49,7 @@ impl FrozenClientRequest {
where
B: MessageBody + 'static,
{
RequestSender::Rc(self.head.clone(), None).send_body(
RequestSender::Rc(Rc::clone(&self.head), None).send_body(
self.addr,
self.response_decompress,
self.timeout,
@@ -60,7 +60,7 @@ impl FrozenClientRequest {
/// Send a json body.
pub fn send_json<T: Serialize>(&self, value: &T) -> SendClientRequest {
RequestSender::Rc(self.head.clone(), None).send_json(
RequestSender::Rc(Rc::clone(&self.head), None).send_json(
self.addr,
self.response_decompress,
self.timeout,
@@ -71,7 +71,7 @@ impl FrozenClientRequest {
/// Send an urlencoded body.
pub fn send_form<T: Serialize>(&self, value: &T) -> SendClientRequest {
RequestSender::Rc(self.head.clone(), None).send_form(
RequestSender::Rc(Rc::clone(&self.head), None).send_form(
self.addr,
self.response_decompress,
self.timeout,
@@ -86,7 +86,7 @@ impl FrozenClientRequest {
S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<BoxError> + 'static,
{
RequestSender::Rc(self.head.clone(), None).send_stream(
RequestSender::Rc(Rc::clone(&self.head), None).send_stream(
self.addr,
self.response_decompress,
self.timeout,
@@ -97,7 +97,7 @@ impl FrozenClientRequest {
/// Send an empty body.
pub fn send(&self) -> SendClientRequest {
RequestSender::Rc(self.head.clone(), None).send(
RequestSender::Rc(Rc::clone(&self.head), None).send(
self.addr,
self.response_decompress,
self.timeout,

View File

@@ -100,8 +100,6 @@
//! # }
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![allow(unknown_lints)] // temp: #[allow(non_local_definitions)]
#![allow(
clippy::type_complexity,

View File

@@ -78,7 +78,7 @@ where
RedirectServiceFuture::Tunnel { fut }
}
ConnectRequest::Client(head, body, addr) => {
let connector = self.connector.clone();
let connector = Rc::clone(&self.connector);
let max_redirect_times = self.max_redirect_times;
// backup the uri and method for reuse schema and authority.