1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-11 09:08:09 +02:00

Compare commits

..

33 Commits

Author SHA1 Message Date
Rob Ede
151a15da74 prepare actix-http release 3.0.0-rc.4 2022-02-22 00:21:49 +00:00
Rob Ede
1ce58ecb30 fix dispatcher panic on pending flush
fixes thread panic in actix-http-3.0.0-rc.3 #2655
2022-02-22 00:19:48 +00:00
Luca Palmieri
f940653981 Edits to the migration notes (#2654) 2022-02-19 17:05:54 +00:00
Rob Ede
b291e29882 fix links 2022-02-18 03:41:10 +00:00
Xavier Lange
f843776f36 Fix links in README (#2653) 2022-02-18 03:34:12 +00:00
Rob Ede
52f7d96358 tweak migration document 2022-02-17 19:13:03 +00:00
Rob Ede
51e573b888 prepare actix-test release 0.1.0-beta.13 2022-02-16 03:13:41 +00:00
Rob Ede
38e015432b prepare actix-http-test release 3.0.0-beta.13 2022-02-16 03:13:22 +00:00
Rob Ede
f5895d5eff prepare actix-web-actors release 4.0.0-beta.12 2022-02-16 03:11:22 +00:00
Rob Ede
a0c4bf8d1b prepare awc release 3.0.0-beta.21 2022-02-16 03:10:01 +00:00
Rob Ede
594e3a6ef1 prepare actix-http release 3.0.0-rc.3 2022-02-16 03:07:12 +00:00
Rob Ede
a808a26d8c bump actix-codec to 0.5 2022-02-15 20:49:10 +00:00
Rob Ede
de62e8b025 add nextest to post-merge ci 2022-02-15 14:40:26 +00:00
Rob Ede
3486edabcf update migrations guide re tokio v1 2022-02-15 00:54:12 +00:00
Ibraheem Ahmed
4c59a34513 Remove clone implementation for Path (#2639) 2022-02-10 10:29:00 +00:00
Rob Ede
1b706b3069 update body type migration guide 2022-02-09 16:12:39 +00:00
Rob Ede
a9f445875a update migration guide 2022-02-09 12:31:06 +00:00
Rob Ede
e0f02c1d9e update migration guide 2022-02-08 16:53:09 +00:00
Rob Ede
092dbba5b9 update migration guide 2022-02-08 15:24:35 +00:00
Rob Ede
ff4b2d251f fix impl assertions 2022-02-08 14:32:57 +00:00
Rob Ede
98faa61afe fix impl assertions 2022-02-08 13:37:01 +00:00
Rob Ede
3f2db9e75c fix doc tests 2022-02-08 12:25:13 +00:00
Rob Ede
074d18209d better document relationship with tokio 2022-02-08 10:21:47 +00:00
Rob Ede
593fbde46a prepare actix-web release 4.0.0-rc.3 2022-02-08 09:31:48 +00:00
Rob Ede
161861997c prepare actix-http release 3.0.0-rc.2 2022-02-08 09:31:20 +00:00
Rob Ede
3d621677a5 clippy 2022-02-08 08:00:47 +00:00
Ali MJ Al-Nasrawy
0c144054cb make Condition generic over body type (#2635)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2022-02-08 07:50:05 +00:00
Rob Ede
b0fbe0dfd8 fix workers doc 2022-02-08 06:58:33 +00:00
Darin Gordon
b653bf557f added note to v4 migration guide about worker thread update (#2634) 2022-02-07 19:04:03 +00:00
Rob Ede
1d1a65282f RC refinements (#2625) 2022-02-04 20:37:33 +00:00
Rob Ede
b0a363a7ae add migration note about fromrequest::configure 2022-02-04 18:48:22 +00:00
Rob Ede
b4d3c2394d clean up migration guide 2022-02-04 18:22:38 +00:00
Rob Ede
5ca42df89a fix stuck connection when handler doesn't read payload (#2624) 2022-02-03 07:03:39 +00:00
58 changed files with 1006 additions and 263 deletions

View File

@@ -152,3 +152,34 @@ jobs:
# - name: Upload to Codecov
# uses: codecov/codecov-action@v1
# with: { file: cobertura.xml }
nextest:
name: nextest
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Install Rust
uses: actions-rs/toolchain@v1
with:
toolchain: stable
profile: minimal
override: true
- name: Generate Cargo.lock
uses: actions-rs/cargo@v1
with: { command: generate-lockfile }
- name: Cache Dependencies
uses: Swatinem/rust-cache@v1.3.0
- name: Install cargo-nextest
uses: actions-rs/cargo@v1
with:
command: install
args: cargo-nextest
- name: Test with cargo-nextest
uses: actions-rs/cargo@v1
with:
command: nextest
args: run

View File

@@ -22,10 +22,10 @@ path = "src/lib.rs"
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
[dependencies]
actix-http = "3.0.0-rc.1"
actix-http = "3.0.0-rc.4"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4.0.0-rc.2", default-features = false }
actix-web = { version = "4.0.0-rc.3", default-features = false }
askama_escape = "0.10"
bitflags = "1"
@@ -43,6 +43,6 @@ tokio-uring = { version = "0.2", optional = true, features = ["bytes"] }
[dev-dependencies]
actix-rt = "2.2"
actix-test = "0.1.0-beta.12"
actix-web = "4.0.0-rc.2"
actix-test = "0.1.0-beta.13"
actix-web = "4.0.0-rc.3"
tempfile = "3.2"

View File

@@ -13,6 +13,6 @@
## Documentation & Resources
- [API Documentation](https://docs.rs/actix-files/)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static_index)
- [API Documentation](https://docs.rs/actix-files)
- [Example Project](https://github.com/actix/examples/tree/master/basics/static-files)
- Minimum Supported Rust Version (MSRV): 1.54

View File

@@ -75,7 +75,7 @@ pub(crate) fn directory_listing(
if dir.is_visible(&entry) {
let entry = entry.unwrap();
let p = match entry.path().strip_prefix(&dir.path) {
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace("\\", "/"),
Ok(p) if cfg!(windows) => base.join(p).to_string_lossy().replace('\\', "/"),
Ok(p) => base.join(p).to_string_lossy().into_owned(),
Err(_) => continue,
};

View File

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 3.0.0-beta.13 - 2022-02-16
- No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2022-01-31
- No significant changes since `3.0.0-beta.11`.

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http-test"
version = "3.0.0-beta.12"
version = "3.0.0-beta.13"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"]
@@ -30,12 +30,12 @@ openssl = ["tls-openssl", "awc/openssl"]
[dependencies]
actix-service = "2.0.0"
actix-codec = "0.4.1"
actix-tls = "3.0.0"
actix-codec = "0.5"
actix-tls = "3"
actix-utils = "3.0.0"
actix-rt = "2.2"
actix-server = "2"
awc = { version = "3.0.0-beta.20", default-features = false }
awc = { version = "3.0.0-beta.21", default-features = false }
base64 = "0.13"
bytes = "1"
@@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tokio = { version = "1.8.4", features = ["sync"] }
[dev-dependencies]
actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-rc.1"
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-rc.4"

View File

@@ -3,11 +3,11 @@
> Various helpers for Actix applications to use during testing.
[![crates.io](https://img.shields.io/crates/v/actix-http-test?label=latest)](https://crates.io/crates/actix-http-test)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.12)](https://docs.rs/actix-http-test/3.0.0-beta.12)
[![Documentation](https://docs.rs/actix-http-test/badge.svg?version=3.0.0-beta.13)](https://docs.rs/actix-http-test/3.0.0-beta.13)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http-test)
<br>
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.12)
[![Dependency Status](https://deps.rs/crate/actix-http-test/3.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.13)
[![Download](https://img.shields.io/crates/d/actix-http-test.svg)](https://crates.io/crates/actix-http-test)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -3,6 +3,30 @@
## Unreleased - 2021-xx-xx
## 3.0.0-rc.4 - 2022-02-22
- Fix h1 dispatcher panic. [1ce58ecb]
[1ce58ecb]: https://github.com/actix/actix-web/commit/1ce58ecb305c60e51db06e6c913b7a1344e229ca
## 3.0.0-rc.3 - 2022-02-16
- No significant changes since `3.0.0-rc.2`.
## 3.0.0-rc.2 - 2022-02-08
### Added
- Implement `From<Vec<u8>>` for `Response<Vec<u8>>`. [#2625]
### Changed
- `error::DispatcherError` enum is now marked `#[non_exhaustive]`. [#2624]
### Fixed
- Issue where handlers that took payload but then dropped without reading it to EOF it would cause keep-alive connections to become stuck. [#2624]
[#2624]: https://github.com/actix/actix-web/pull/2624
[#2625]: https://github.com/actix/actix-web/pull/2625
## 3.0.0-rc.1 - 2022-01-31
### Added
- Implement `Default` for `KeepAlive`. [#2611]

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.0.0-rc.1"
version = "3.0.0-rc.4"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@@ -20,7 +20,7 @@ edition = "2018"
[package.metadata.docs.rs]
# features that docs.rs will build with
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
features = ["http2", "openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
[lib]
name = "actix_http"
@@ -57,7 +57,7 @@ __compress = []
[dependencies]
actix-service = "2"
actix-codec = "0.4.1"
actix-codec = "0.5"
actix-utils = "3"
actix-rt = { version = "2.2", default-features = false }
@@ -89,7 +89,7 @@ rand = { version = "0.8", optional = true }
sha-1 = { version = "0.10", optional = true }
# openssl/rustls
actix-tls = { version = "3.0.0", default-features = false, optional = true }
actix-tls = { version = "3", default-features = false, optional = true }
# compress-*
brotli = { version = "3.3.3", optional = true }
@@ -97,10 +97,10 @@ flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.10", optional = true }
[dev-dependencies]
actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
actix-server = "2"
actix-tls = { version = "3.0.0", features = ["openssl"] }
actix-web = "4.0.0-rc.2"
actix-tls = { version = "3", features = ["openssl"] }
actix-web = "4.0.0-rc.3"
async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }

View File

@@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem.
[![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.0.0-rc.1)](https://docs.rs/actix-http/3.0.0-rc.1)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-rc.4)](https://docs.rs/actix-http/3.0.0-rc.4)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![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.0.0-rc.1/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.1)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-rc.4/status.svg)](https://deps.rs/crate/actix-http/3.0.0-rc.4)
[![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

@@ -80,7 +80,7 @@ mod tests {
use futures_core::ready;
use futures_util::{stream, FutureExt as _};
use pin_project_lite::pin_project;
use static_assertions::{assert_impl_all, assert_not_impl_all};
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::body::to_bytes;
@@ -91,10 +91,10 @@ mod tests {
assert_impl_all!(BodyStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
assert_impl_all!(BodyStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
assert_not_impl_all!(BodyStream<stream::Empty<Bytes>>: MessageBody);
assert_not_impl_all!(BodyStream<stream::Repeat<Bytes>>: MessageBody);
assert_not_impl_any!(BodyStream<stream::Empty<Bytes>>: MessageBody);
assert_not_impl_any!(BodyStream<stream::Repeat<Bytes>>: MessageBody);
// crate::Error is not Clone
assert_not_impl_all!(BodyStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
assert_not_impl_any!(BodyStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
#[actix_rt::test]
async fn skips_empty_chunks() {

View File

@@ -105,14 +105,13 @@ impl MessageBody for BoxBody {
#[cfg(test)]
mod tests {
use static_assertions::{assert_impl_all, assert_not_impl_all};
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::body::to_bytes;
assert_impl_all!(BoxBody: MessageBody, fmt::Debug, Unpin);
assert_not_impl_all!(BoxBody: Send, Sync, Unpin);
assert_impl_all!(BoxBody: fmt::Debug, MessageBody, Unpin);
assert_not_impl_any!(BoxBody: Send, Sync);
#[actix_rt::test]
async fn nested_boxed_body() {

View File

@@ -10,6 +10,17 @@ use super::{BodySize, BoxBody, MessageBody};
use crate::Error;
pin_project! {
/// An "either" type specialized for body types.
///
/// It is common, in middleware especially, to conditionally return an inner service's unknown/
/// generic body `B` type or return early with a new response. This type's "right" variant
/// defaults to `BoxBody` since error responses are the common case.
///
/// For example, middleware will often have `type Response = ServiceResponse<EitherBody<B>>`.
/// This means that the inner service's response body type maps to the `Left` variant and the
/// middleware's own error responses use the default `Right` variant of `BoxBody`. Of course,
/// there's no reason it couldn't use `EitherBody<B, String>` instead if its alternative
/// responses have a known type.
#[project = EitherBodyProj]
#[derive(Debug, Clone)]
pub enum EitherBody<L, R = BoxBody> {
@@ -22,7 +33,10 @@ pin_project! {
}
impl<L> EitherBody<L, BoxBody> {
/// Creates new `EitherBody` using left variant and boxed right variant.
/// Creates new `EitherBody` left variant with a boxed right variant.
///
/// If the expected `R` type will be inferred and is not `BoxBody` then use the
/// [`left`](Self::left) constructor instead.
#[inline]
pub fn new(body: L) -> Self {
Self::Left { body }

View File

@@ -19,7 +19,7 @@ use super::{BodySize, BoxBody};
/// It is not usually necessary to create custom body types, this trait is already [implemented for
/// a large number of sensible body types](#foreign-impls) including:
/// - Empty body: `()`
/// - Text-based: `String`, `&'static str`, `ByteString`.
/// - Text-based: `String`, `&'static str`, [`ByteString`](https://docs.rs/bytestring/1).
/// - Byte-based: `Bytes`, `BytesMut`, `Vec<u8>`, `&'static [u8]`;
/// - Streams: [`BodyStream`](super::BodyStream), [`SizedStream`](super::SizedStream)
///

View File

@@ -76,7 +76,7 @@ mod tests {
use actix_rt::pin;
use actix_utils::future::poll_fn;
use futures_util::stream;
use static_assertions::{assert_impl_all, assert_not_impl_all};
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
use crate::body::to_bytes;
@@ -87,10 +87,10 @@ mod tests {
assert_impl_all!(SizedStream<stream::Empty<Result<Bytes, Infallible>>>: MessageBody);
assert_impl_all!(SizedStream<stream::Repeat<Result<Bytes, Infallible>>>: MessageBody);
assert_not_impl_all!(SizedStream<stream::Empty<Bytes>>: MessageBody);
assert_not_impl_all!(SizedStream<stream::Repeat<Bytes>>: MessageBody);
assert_not_impl_any!(SizedStream<stream::Empty<Bytes>>: MessageBody);
assert_not_impl_any!(SizedStream<stream::Repeat<Bytes>>: MessageBody);
// crate::Error is not Clone
assert_not_impl_all!(SizedStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
assert_not_impl_any!(SizedStream<stream::Repeat<Result<Bytes, crate::Error>>>: MessageBody);
#[actix_rt::test]
async fn skips_empty_chunks() {

View File

@@ -104,8 +104,13 @@ impl ServiceConfig {
self.0.date_service.now()
}
pub(crate) fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
let mut buf: [u8; 39] = [0; 39];
/// Writes date header to `dst` buffer.
///
/// Low-level method that utilizes the built-in efficient date service, requiring fewer syscalls
/// than normal. Note that a CRLF (`\r\n`) is included in what is written.
#[doc(hidden)]
pub fn write_date_header(&self, dst: &mut BytesMut, camel_case: bool) {
let mut buf: [u8; 37] = [0; 37];
buf[..6].copy_from_slice(if camel_case { b"Date: " } else { b"date: " });
@@ -113,7 +118,7 @@ impl ServiceConfig {
.date_service
.with_date(|date| buf[6..35].copy_from_slice(&date.bytes));
buf[35..].copy_from_slice(b"\r\n\r\n");
buf[35..].copy_from_slice(b"\r\n");
dst.extend_from_slice(&buf);
}

View File

@@ -340,6 +340,7 @@ impl From<PayloadError> for Error {
/// A set of errors that can occur during dispatching HTTP requests.
#[derive(Debug, Display, From)]
#[non_exhaustive]
pub enum DispatchError {
/// Service error.
#[display(fmt = "Service Error")]
@@ -373,6 +374,10 @@ pub enum DispatchError {
#[display(fmt = "Connection shutdown timeout")]
DisconnectTimeout,
/// Handler dropped payload before reading EOF.
#[display(fmt = "Handler dropped payload before reading EOF")]
HandlerDroppedPayload,
/// Internal error.
#[display(fmt = "Internal error")]
InternalError,

View File

@@ -128,7 +128,10 @@ impl Decoder for ClientCodec {
type Error = ParseError;
fn decode(&mut self, src: &mut BytesMut) -> Result<Option<Self::Item>, Self::Error> {
debug_assert!(!self.inner.payload.is_some(), "Payload decoder is set");
debug_assert!(
self.inner.payload.is_none(),
"Payload decoder should not be set"
);
if let Some((req, payload)) = self.inner.decoder.decode(src)? {
if let Some(conn_type) = req.conn_type() {

View File

@@ -125,11 +125,13 @@ impl Decoder for Codec {
self.flags.set(Flags::HEAD, head.method == Method::HEAD);
self.version = head.version;
self.conn_type = head.connection_type();
if self.conn_type == ConnectionType::KeepAlive
&& !self.flags.contains(Flags::KEEP_ALIVE_ENABLED)
{
self.conn_type = ConnectionType::Close
}
match payload {
PayloadType::None => self.payload = None,
PayloadType::Payload(pl) => self.payload = Some(pl),

View File

@@ -209,15 +209,16 @@ impl MessageType for Request {
let (len, method, uri, ver, h_len) = {
// SAFETY:
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is
// safe because the type we are claiming to have initialized here is a
// bunch of `MaybeUninit`s, which do not require initialization.
// Create an uninitialized array of `MaybeUninit`. The `assume_init` is safe because the
// type we are claiming to have initialized here is a bunch of `MaybeUninit`s, which
// do not require initialization.
let mut parsed = unsafe {
MaybeUninit::<[MaybeUninit<httparse::Header<'_>>; MAX_HEADERS]>::uninit()
.assume_init()
};
let mut req = httparse::Request::new(&mut []);
match req.parse_with_uninit_headers(src, &mut parsed)? {
httparse::Status::Complete(len) => {
let method = Method::from_bytes(req.method.unwrap().as_bytes())
@@ -232,6 +233,7 @@ impl MessageType for Request {
(len, method, uri, version, req.headers.len())
}
httparse::Status::Partial => {
return if src.len() >= MAX_BUFFER_SIZE {
trace!("MAX_BUFFER_SIZE unprocessed data reached, closing");

View File

@@ -21,7 +21,7 @@ use crate::{
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
Error, Extensions, OnConnectData, Request, Response, StatusCode,
ConnectionType, Error, Extensions, OnConnectData, Request, Response, StatusCode,
};
use super::{
@@ -151,7 +151,8 @@ pin_project! {
error: Option<DispatchError>,
#[pin]
state: State<S, B, X>,
pub(super) state: State<S, B, X>,
// when Some(_) dispatcher is in state of receiving request payload
payload: Option<PayloadSender>,
messages: VecDeque<DispatcherMessage>,
@@ -174,7 +175,7 @@ enum DispatcherMessage {
pin_project! {
#[project = StateProj]
enum State<S, B, X>
pub(super) enum State<S, B, X>
where
S: Service<Request>,
X: Service<Request, Response = Request>,
@@ -194,7 +195,7 @@ where
X: Service<Request, Response = Request>,
B: MessageBody,
{
fn is_none(&self) -> bool {
pub(super) fn is_none(&self) -> bool {
matches!(self, State::None)
}
}
@@ -686,12 +687,74 @@ where
let can_not_read = !self.can_read(cx);
// limit amount of non-processed requests
if pipeline_queue_full || can_not_read {
if pipeline_queue_full {
return Ok(false);
}
let mut this = self.as_mut().project();
if can_not_read {
log::debug!("cannot read request payload");
if let Some(sender) = &this.payload {
// ...maybe handler does not want to read any more payload...
if let PayloadStatus::Dropped = sender.need_read(cx) {
log::debug!("handler dropped payload early; attempt to clean connection");
// ...in which case poll request payload a few times
loop {
match this.codec.decode(this.read_buf)? {
Some(msg) => {
match msg {
// payload decoded did not yield EOF yet
Message::Chunk(Some(_)) => {
// if non-clean connection, next loop iter will detect empty
// read buffer and close connection
}
// connection is in clean state for next request
Message::Chunk(None) => {
log::debug!("connection successfully cleaned");
// reset dispatcher state
let _ = this.payload.take();
this.state.set(State::None);
// break out of payload decode loop
break;
}
// Either whole payload is read and loop is broken or more data
// was expected in which case connection is closed. In both
// situations dispatcher cannot get here.
Message::Item(_) => {
unreachable!("dispatcher is in payload receive state")
}
}
}
// not enough info to decide if connection is going to be clean or not
None => {
log::error!(
"handler did not read whole payload and dispatcher could not \
drain read buf; return 500 and close connection"
);
this.flags.insert(Flags::SHUTDOWN);
let mut res = Response::internal_server_error().drop_body();
res.head_mut().set_connection_type(ConnectionType::Close);
this.messages.push_back(DispatcherMessage::Error(res));
*this.error = Some(DispatchError::HandlerDroppedPayload);
return Ok(true);
}
}
}
}
} else {
// can_not_read and no request payload
return Ok(false);
}
}
let mut updated = false;
loop {
@@ -868,10 +931,16 @@ where
"dispatcher should not be in keep-alive phase if state is not none: {:?}",
this.state,
);
debug_assert!(
this.write_buf.is_empty(),
"dispatcher should not be in keep-alive phase if write_buf is not empty",
);
// Assert removed by @robjtede on account of issue #2655. There are cases where an I/O
// flush can be pending after entering the keep-alive state causing the subsequent flush
// wake up to panic here. This appears to be a Linux-only problem. Leaving original code
// below for posterity because a simple and reliable test could not be found to trigger
// the behavior.
// debug_assert!(
// this.write_buf.is_empty(),
// "dispatcher should not be in keep-alive phase if write_buf is not empty",
// );
// keep-alive timer has timed out
if timer.as_mut().poll(cx).is_ready() {

View File

@@ -1,6 +1,6 @@
use std::{future::Future, str, task::Poll, time::Duration};
use actix_rt::time::sleep;
use actix_rt::{pin, time::sleep};
use actix_service::fn_service;
use actix_utils::future::{ready, Ready};
use bytes::Bytes;
@@ -53,6 +53,14 @@ fn echo_path_service(
})
}
fn drop_payload_service(
) -> impl Service<Request, Response = Response<&'static str>, Error = Error> {
fn_service(|mut req: Request| async move {
let _ = req.take_payload();
Ok::<_, Error>(Response::with_body(StatusCode::OK, "payload dropped"))
})
}
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
fn_service(|mut req: Request| {
Box::pin(async move {
@@ -89,7 +97,7 @@ async fn late_request() {
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -156,7 +164,7 @@ async fn oneshot_connection() {
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -173,13 +181,16 @@ async fn oneshot_connection() {
stabilize_date_header(&mut res);
let res = &res[..];
let exp = b"\
HTTP/1.1 200 OK\r\n\
content-length: 5\r\n\
connection: close\r\n\
date: Thu, 01 Jan 1970 12:34:56 UTC\r\n\r\n\
/abcd\
";
let exp = http_msg(
r"
HTTP/1.1 200 OK
content-length: 5
connection: close
date: Thu, 01 Jan 1970 12:34:56 UTC
/abcd
",
);
assert_eq!(
res,
@@ -188,7 +199,7 @@ async fn oneshot_connection() {
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(exp)
String::from_utf8_lossy(&exp)
);
})
.await;
@@ -214,7 +225,7 @@ async fn keep_alive_timeout() {
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -293,7 +304,7 @@ async fn keep_alive_follow_up_req() {
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
lazy(|cx| {
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -413,7 +424,7 @@ async fn req_parse_err() {
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
match h1.as_mut().poll(cx) {
Poll::Pending => panic!(),
@@ -459,7 +470,7 @@ async fn pipelining_ok_then_ok() {
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -529,7 +540,7 @@ async fn pipelining_ok_then_bad() {
OnConnectData::default(),
);
actix_rt::pin!(h1);
pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -601,7 +612,7 @@ async fn expect_handling() {
",
);
actix_rt::pin!(h1);
pin!(h1);
assert!(h1.as_mut().poll(cx).is_pending());
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -678,7 +689,7 @@ async fn expect_eager() {
",
);
actix_rt::pin!(h1);
pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
@@ -761,7 +772,7 @@ async fn upgrade_handling() {
",
);
actix_rt::pin!(h1);
pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
@@ -771,3 +782,192 @@ async fn upgrade_handling() {
})
.await;
}
#[actix_rt::test]
async fn handler_drop_payload() {
let _ = env_logger::try_init();
let mut buf = TestBuffer::new(http_msg(
r"
POST /drop-payload HTTP/1.1
Content-Length: 3
abc
",
));
let services = HttpFlow::new(
drop_payload_service(),
ExpectHandler,
None::<UpgradeHandler>,
);
let h1 = Dispatcher::new(
buf.clone(),
services,
ServiceConfig::default(),
None,
OnConnectData::default(),
);
pin!(h1);
lazy(|cx| {
assert!(h1.as_mut().poll(cx).is_pending());
// polls: manual
assert_eq!(h1.poll_count, 1);
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
let exp = http_msg(
r"
HTTP/1.1 200 OK
content-length: 15
date: Thu, 01 Jan 1970 12:34:56 UTC
payload dropped
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
if let DispatcherStateProj::Normal { inner } = h1.as_mut().project().inner.project() {
assert!(inner.state.is_none());
}
})
.await;
lazy(|cx| {
// add message that claims to have payload longer than provided
buf.extend_read_buf(http_msg(
r"
POST /drop-payload HTTP/1.1
Content-Length: 200
abc
",
));
assert!(h1.as_mut().poll(cx).is_pending());
// polls: manual => manual
assert_eq!(h1.poll_count, 2);
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
// expect response immediately even though request side has not finished reading payload
let exp = http_msg(
r"
HTTP/1.1 200 OK
content-length: 15
date: Thu, 01 Jan 1970 12:34:56 UTC
payload dropped
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
})
.await;
lazy(|cx| {
assert!(h1.as_mut().poll(cx).is_ready());
// polls: manual => manual => manual
assert_eq!(h1.poll_count, 3);
let mut res = BytesMut::from(buf.take_write_buf().as_ref());
stabilize_date_header(&mut res);
let res = &res[..];
// expect that unrequested error response is sent back since connection could not be cleaned
let exp = http_msg(
r"
HTTP/1.1 500 Internal Server Error
content-length: 0
connection: close
date: Thu, 01 Jan 1970 12:34:56 UTC
",
);
assert_eq!(
res,
exp,
"\nexpected response not in write buffer:\n\
response: {:?}\n\
expected: {:?}",
String::from_utf8_lossy(res),
String::from_utf8_lossy(&exp)
);
})
.await;
}
fn http_msg(msg: impl AsRef<str>) -> BytesMut {
let mut msg = msg
.as_ref()
.trim()
.split('\n')
.into_iter()
.map(|line| [line.trim_start(), "\r"].concat())
.collect::<Vec<_>>()
.join("\n");
// remove trailing \r
msg.pop();
if !msg.is_empty() && !msg.contains("\r\n\r\n") {
msg.push_str("\r\n\r\n");
}
BytesMut::from(msg.as_bytes())
}
#[test]
fn http_msg_creates_msg() {
assert_eq!(http_msg(r""), "");
assert_eq!(
http_msg(
r"
POST / HTTP/1.1
Content-Length: 3
abc
"
),
"POST / HTTP/1.1\r\nContent-Length: 3\r\n\r\nabc"
);
assert_eq!(
http_msg(
r"
GET / HTTP/1.1
Content-Length: 3
"
),
"GET / HTTP/1.1\r\nContent-Length: 3\r\n\r\n"
);
}

View File

@@ -210,14 +210,14 @@ pub(crate) trait MessageType: Sized {
dst.advance_mut(pos);
}
// optimized date header, set_date writes \r\n
if !has_date {
// optimized date header, write_date_header writes its own \r\n
config.write_date_header(dst, camel_case);
} else {
// msg eof
dst.extend_from_slice(b"\r\n");
}
// end-of-headers marker
dst.extend_from_slice(b"\r\n");
Ok(())
}

View File

@@ -3,6 +3,7 @@
//! ## Crate Features
//! | Feature | Functionality |
//! | ------------------- | ------------------------------------------- |
//! | `http2` | HTTP/2 support via [h2]. |
//! | `openssl` | TLS support via [OpenSSL]. |
//! | `rustls` | TLS support via [rustls]. |
//! | `compress-brotli` | Payload compression support: Brotli. |
@@ -10,6 +11,7 @@
//! | `compress-zstd` | Payload compression support: Zstd. |
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
//!
//! [h2]: https://crates.io/crates/h2
//! [OpenSSL]: https://crates.io/crates/openssl
//! [rustls]: https://crates.io/crates/rustls
//! [trust-dns]: https://crates.io/crates/trust-dns

View File

@@ -285,6 +285,24 @@ impl From<&'static [u8]> for Response<&'static [u8]> {
}
}
impl From<Vec<u8>> for Response<Vec<u8>> {
fn from(val: Vec<u8>) -> Self {
let mut res = Response::with_body(StatusCode::OK, val);
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl From<&Vec<u8>> for Response<Vec<u8>> {
fn from(val: &Vec<u8>) -> Self {
let mut res = Response::with_body(StatusCode::OK, val.clone());
let mime = mime::APPLICATION_OCTET_STREAM.try_into_value().unwrap();
res.headers_mut().insert(header::CONTENT_TYPE, mime);
res
}
}
impl From<String> for Response<String> {
fn from(val: String) -> Self {
let mut res = Response::with_body(StatusCode::OK, val);

View File

@@ -15,7 +15,7 @@ path = "src/lib.rs"
[dependencies]
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-rc.2", default-features = false }
actix-web = { version = "4.0.0-rc.3", default-features = false }
bytes = "1"
derive_more = "0.99.5"
@@ -28,7 +28,7 @@ twoway = "0.2"
[dev-dependencies]
actix-rt = "2.2"
actix-http = "3.0.0-rc.1"
actix-http = "3.0.0-rc.4"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
tokio = { version = "1.8.4", features = ["sync"] }
tokio-stream = "0.1"

View File

@@ -145,7 +145,8 @@ macro_rules! register {
concat!("/user/keys"),
concat!("/user/keys/", $p1),
];
std::array::IntoIter::new(arr)
IntoIterator::into_iter(arr)
}};
}
@@ -158,7 +159,7 @@ fn call() -> impl Iterator<Item = &'static str> {
"/repos/rust-lang/rust/releases/1.51.0",
];
std::array::IntoIter::new(arr)
IntoIterator::into_iter(arr)
}
fn compare_routers(c: &mut Criterion) {

View File

@@ -898,7 +898,7 @@ impl ResourceDef {
}
let pattern_re_set = RegexSet::new(re_set).unwrap();
let segments = segments.unwrap_or_else(Vec::new);
let segments = segments.unwrap_or_default();
(
PatternType::DynamicSet(pattern_re_set, pattern_data),

View File

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 0.1.0-beta.13 - 2022-02-16
- No significant changes since `0.1.0-beta.12`.
## 0.1.0-beta.12 - 2022-01-31
- Rename `TestServerConfig::{client_timeout => client_request_timeout}`. [#2611]

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-test"
version = "0.1.0-beta.12"
version = "0.1.0-beta.13"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@@ -28,14 +28,14 @@ rustls = ["tls-rustls", "actix-http/rustls", "awc/rustls"]
openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies]
actix-codec = "0.4.1"
actix-http = "3.0.0-rc.1"
actix-http-test = "3.0.0-beta.12"
actix-codec = "0.5"
actix-http = "3.0.0-rc.4"
actix-http-test = "3.0.0-beta.13"
actix-rt = "2.1"
actix-service = "2.0.0"
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-rc.2", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.20", default-features = false, features = ["cookies"] }
actix-web = { version = "4.0.0-rc.3", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.21", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] }

View File

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 4.0.0-beta.12 - 2022-02-16
- No significant changes since `4.0.0-beta.11`.
## 4.0.0-beta.11 - 2022-01-31
- No significant changes since `4.0.0-beta.10`.

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web-actors"
version = "4.0.0-beta.11"
version = "4.0.0-beta.12"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"]
@@ -15,9 +15,9 @@ path = "src/lib.rs"
[dependencies]
actix = { version = "0.12.0", default-features = false }
actix-codec = "0.4.1"
actix-http = "3.0.0-rc.1"
actix-web = { version = "4.0.0-rc.2", default-features = false }
actix-codec = "0.5"
actix-http = "3.0.0-rc.4"
actix-web = { version = "4.0.0-rc.3", default-features = false }
bytes = "1"
bytestring = "1"
@@ -27,8 +27,8 @@ tokio = { version = "1.8.4", features = ["sync"] }
[dev-dependencies]
actix-rt = "2.2"
actix-test = "0.1.0-beta.12"
awc = { version = "3.0.0-beta.20", default-features = false }
actix-test = "0.1.0-beta.13"
awc = { version = "3.0.0-beta.21", default-features = false }
env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false }

View File

@@ -3,11 +3,11 @@
> Actix actors support for Actix Web.
[![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.0.0-beta.11)](https://docs.rs/actix-web-actors/4.0.0-beta.11)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.12)](https://docs.rs/actix-web-actors/4.0.0-beta.12)
[![Version](https://img.shields.io/badge/rustc-1.54+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.54.0.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.11)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.12)
[![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

@@ -23,9 +23,9 @@ syn = { version = "1", features = ["full", "parsing"] }
[dev-dependencies]
actix-macros = "0.2.3"
actix-rt = "2.2"
actix-test = "0.1.0-beta.12"
actix-test = "0.1.0-beta.13"
actix-utils = "3.0.0"
actix-web = "4.0.0-rc.2"
actix-web = "4.0.0-rc.3"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1"

View File

@@ -152,6 +152,10 @@ method_macro!(Patch, patch);
/// Marks async main function as the Actix Web system entry-point.
///
/// Note that Actix Web also works under `#[tokio::main]` since version 4.0. However, this macro is
/// still necessary for actor support (since actors use a `System`). Read more in the
/// [`actix_web::rt`](https://docs.rs/actix-web/4/actix_web/rt) module docs.
///
/// # Examples
/// ```
/// #[actix_web::main]

View File

@@ -3,6 +3,18 @@
## Unreleased - 2021-xx-xx
## 4.0.0-rc.3 - 2022-02-08
### Changed
- `middleware::Condition` gained a broader compatibility; `Compat` is needed in fewer cases. [#2635]
### Added
- Implement `Responder` for `Vec<u8>`. [#2625]
- Re-export `KeepAlive` in `http` mod. [#2625]
[#2625]: https://github.com/actix/actix-web/pull/2625
[#2635]: https://github.com/actix/actix-web/pull/2635
## 4.0.0-rc.2 - 2022-02-02
### Added
- On-by-default `macros` feature flag to enable routing and runtime macros. [#2619]

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.0.0-rc.2"
version = "4.0.0-rc.3"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>",
@@ -63,7 +63,7 @@ __compress = []
experimental-io-uring = ["actix-server/io-uring"]
[dependencies]
actix-codec = "0.4.1"
actix-codec = "0.5"
actix-macros = { version = "0.2.3", optional = true }
actix-rt = { version = "2.6", default-features = false }
actix-server = "2"
@@ -71,7 +71,7 @@ actix-service = "2"
actix-utils = "3"
actix-tls = { version = "3", default-features = false, optional = true }
actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] }
actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] }
actix-router = "0.5.0-rc.3"
actix-web-codegen = { version = "0.5.0-rc.2", optional = true }
@@ -100,8 +100,8 @@ url = "2.1"
[dev-dependencies]
actix-files = "0.6.0-beta.16"
actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.20", features = ["openssl"] }
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.21", features = ["openssl"] }
brotli = "3.3.3"
const-str = "0.3"
@@ -116,6 +116,7 @@ serde = { version = "1.0", features = ["derive"] }
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.20.0" }
tokio = { version = "1.13.1", features = ["rt-multi-thread", "macros"] }
zstd = "0.10"
[[test]]

View File

@@ -1,68 +1,110 @@
# Migrating to 4.0.0
It is assumed that migration is happening _from_ v3.x. If migration from older version of Actix Web, see the other historical migration notes in this folder.
This guide walks you through the process of migrating from v3.x.y to v4.x.y.
If you are migrating to v4.x.y from an older version of Actix Web (v2.x.y or earlier), check out the other historical migration notes in this folder.
This is not an exhaustive list of changes. Smaller or less impactful code changes are outlined, with links to the PRs that introduced them, are shown in [CHANGES.md](./CHANGES.md). If you think any of the changes not mentioned here deserve to be, submit an issue or PR.
This document is not designed to be exhaustive - it focuses on the most significant changes coming in v4.
You can find an exhaustive changelog in [CHANGES.md](./CHANGES.md), complete of PR links. If you think that some of the changes that we omitted deserve to be called out in this document, please open an issue or submit a PR.
Headings marked with :warning: are **breaking behavioral changes** and will probably not surface as compile-time errors. Automated tests _might_ detect their effects on your app.
Headings marked with :warning: are **breaking behavioral changes**. They will probably not surface as compile-time errors though automated tests _might_ detect their effects on your app.
## Table of Contents:
- [MSRV](#msrv)
- [Tokio v1 Ecosystem](#tokio-v1-ecosystem)
- [Module Structure](#module-structure)
- [`NormalizePath` Middleware :warning:](#normalizepath-middleware-warning)
- [Server Settings :warning:](#server-settings-warning)
- [`FromRequest` Trait](#fromrequest-trait)
- [Compression Feature Flags](#compression-feature-flags)
- [`web::Path`](#webpath)
- [Rustls](#rustls-crate-upgrade)
- [Rustls Crate Upgrade](#rustls-crate-upgrade)
- [Removed `awc` Client Re-export](#removed-awc-client-re-export)
- [Integration Testing Utils Moved To `actix-test`](#integration-testing-utils-moved-to-actix-test)
- [Header APIs](#header-apis)
- [Response Body Types](#response-body-types)
- [Middleware Trait APIs](#middleware-trait-apis)
- [`Responder` Trait](#responder-trait)
- [`App::data` Deprecation :warning:](#appdata-deprecation-warning)
- [Direct Dependency On `actix-rt` And `actix-service`](#direct-dependency-on-actix-rt-and-actix-service)
- [Server Must Be Polled :warning:](#server-must-be-polled-warning)
- [Guards API](#guards-api)
- [Returning `HttpResponse` synchronously](#returning-httpresponse-synchronously)
- [`#[actix_web::main]` and `#[tokio::main]`](#actixwebmain-and-tokiomain)
- [`web::block`](#webblock)
## MSRV
The MSRV of Actix Web has been raised from 1.42 to 1.54.
## Tokio v1 Ecosystem
Actix Web v4 is now underpinned by `tokio`'s v1 ecosystem.
`cargo` supports having multiple versions of the same crate within the same dependency tree, but `tokio` v1 does not interoperate transparently with its previous versions (v0.2, v0.1). Some of your dependencies might rely on `tokio`, either directly or indirectly - if they are using an older version of `tokio`, check if an update is available.
The following command can help you to identify these dependencies:
```sh
# Find all crates in your dependency tree that depend on `tokio`
# It also reports the different versions of `tokio` in your dependency tree.
cargo tree -i tokio
# if you depend on multiple versions of tokio, use this command to
# list the dependencies relying on a specific version of tokio:
cargo tree -i tokio:0.2.25
```
## Module Structure
Lots of modules has been organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", refer to the [documentation on docs.rs](https://docs.rs/actix-web) to to search for items' new locations.
Lots of modules have been re-organized in this release. If a compile error refers to "item XYZ not found in module..." or "module XYZ not found", check the [documentation on docs.rs](https://docs.rs/actix-web) to search for items' new locations.
## `NormalizePath` Middleware :warning:
The default `NormalizePath` behavior now strips trailing slashes by default. This was previously documented to be the case in v3 but the behavior now matches. The effect is that routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. As such, calling `NormalizePath::default()` will log a warning. It is advised that the `new` or `trim` methods be used instead.
The default `NormalizePath` behavior now strips trailing slashes by default.
This was the _documented_ behaviour in Actix Web v3, but the _actual_ behaviour differed - the discrepancy has now been fixed.
As a consequence of this change, routes defined with trailing slashes will become inaccessible when using `NormalizePath::default()`. Calling `NormalizePath::default()` will log a warning. We suggest to use `new` or `trim`.
```diff
- #[get("/test/")]`
+ #[get("/test")]`
- #[get("/test/")]
+ #[get("/test")]
async fn handler() {
App::new()
- .wrap(NormalizePath::default())`
+ .wrap(NormalizePath::trim())`
- .wrap(NormalizePath::default())
+ .wrap(NormalizePath::trim())
```
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
## Server Settings :warning:
Until Actix Web v4, the underlying `actix-server` crate used the number of available **logical** cores as the default number of worker threads. The new default is the number of [physical CPU cores available](https://github.com/actix/actix-net/commit/3a3d654c). For more information about this change, refer to [this analysis](https://github.com/actix/actix-web/issues/957).
If you notice performance regressions, please open a new issue detailing your observations.
## `FromRequest` Trait
The associated type `Config` of `FromRequest` was removed. If you have custom extractors, you can just remove this implementation and refer to config types directly, if required.
```diff
impl FromRequest for MyExtractor {
- `type Config = ();`
- type Config = ();
}
```
Consequently, the `FromRequest::configure` method was also removed. Config for extractors is still provided using `App::app_data` but should now be constructed in a standalone way.
## Compression Feature Flags
Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. The new flags are:
The `compress` feature flag has been split into more granular feature flags, one for each supported algorithm (brotli, gzip, zstd). By default, all compression algorithms are enabled. If you want to select specific compression codecs, the new flags are:
- `compress-brotli`
- `compress-gzip`
- `compress-zstd`
If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want to have compression enabled.
## `web::Path`
The inner field for `web::Path` was made private because It was causing too many issues when used with inner tuple types due to its `Deref` impl.
The inner field for `web::Path` is now private.
It was causing too many issues when used with inner tuple types due to its `Deref` implementation.
```diff
- async fn handler(web::Path((foo, bar)): web::Path<(String, String)>) {
@@ -72,62 +114,236 @@ The inner field for `web::Path` was made private because It was causing too many
## Rustls Crate Upgrade
Required version of `rustls` dependency was bumped to the latest version 0.20. As a result, the new server config builder has changed. [See the updated example project &rarr;.](https://github.com/actix/examples/tree/HEAD/security/rustls/)
Actix Web now depends on version 0.20 of `rustls`. As a result, the server config builder has changed. [See the updated example project.](https://github.com/actix/examples/tree/master/https-tls/rustls/)
## Removed `awc` Client Re-export
Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` its own release cadence and prevents its own breaking changes from being blocked due to a re-export.
Actix Web's sister crate `awc` is no longer re-exported through the `client` module. This allows `awc` to have its own release cadence - its breaking changes are no longer blocked by Actix Web's (more conservative) release schedule.
```diff
- use actix_web::client::Client;
+ use awc::Client;
```
## Integration Testing Utils Moved to `actix-test`
## Integration Testing Utils Moved To `actix-test`
Actix Web's `test` module used to contain `TestServer`. Since this required the `awc` client and it was removed as a re-export (see above), it was moved to its own crate [`actix-test`](https://docs.rs/actix-test).
`TestServer` has been moved to its own crate, [`actix-test`](https://docs.rs/actix-test).
```diff
- use use actix_web::test::start;
+ use use actix_test::start;
```
`TestServer` previously lived in `actix_web::test`, but it depends on `awc` which is no longer part of Actix Web's public API (see above).
## Header APIs
TODO
Header related APIs have been standardized across all `actix-*` crates. The terminology now better matches the underlying `HeaderMap` naming conventions.
## Body Types / Removal of Body+ResponseBody types / Addition of EitherBody
In short, "insert" always indicates that any existing headers with the same name are overridden, while "append" is used for adding with no removal (e.g. multi-valued headers).
TODO
For request and response builder APIs, the new methods provide a unified interface for adding key-value pairs _and_ typed headers, which can often be more expressive.
In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types.
```diff
- .set_header("Api-Key", "1234")
+ .insert_header(("Api-Key", "1234"))
- .header("Api-Key", "1234")
+ .append_header(("Api-Key", "1234"))
- .set(ContentType::json())
+ .insert_header(ContentType::json())
```
We chose to deprecate most of the old methods instead of removing them immediately - the warning notes will guide you on how to update.
## Response Body Types
There have been a lot of changes to response body types. They are now more expressive and their purpose should be more intuitive.
We have boosted the quality and completeness of the documentation for all items in the [`body` module](https://docs.rs/actix-web/4/actix_web/body).
### `ResponseBody`
`ResponseBody` is gone. Its purpose was confusing and has been replaced by better components.
### `Body`
`Body` is also gone. In combination with `ResponseBody`, the API it provided was sub-optimal and did not encourage expressive types. Here are the equivalents in the new system (check docs):
- `Body::None` => `body::None::new()`
- `Body::Empty` => `()` / `web::Bytes::new()`
- `Body::Bytes` => `web::Bytes::from(...)`
- `Body::Message` => `.boxed()` / `BoxBody`
### `BoxBody`
`BoxBody` is a new type-erased body type. It's used for all error response bodies.
Creating a boxed body is best done by calling [`.boxed()`](https://docs.rs/actix-web/4/actix_web/body/trait.MessageBody.html#method.boxed) on a `MessageBody` type.
### `EitherBody`
`EitherBody` is a new "either" type that is particularly useful in middlewares that can bail early, returning their own response plus body type.
### Error Handlers
TODO In particular, folks seem to be struggling with the `ErrorHandlers` middleware because of this change and the obscured nature of `EitherBody` within its types.
## Middleware Trait APIs
This section builds upon guidance from the [response body types](#response-body-types) section.
TODO
TODO: Also write the Middleware author's guide.
## `Responder` Trait
TODO
The `Responder` trait's interface has changed. Errors should be handled and converted to responses within the `respond_to` method. It's also no longer async so the associated `type Future` has been removed; there was no compelling use case found for it. These changes simplify the interface and implementation a lot.
## `App::data` deprecation
Now that more emphasis is placed on expressive body types, as explained in the [body types migration section](#response-body-types), this trait has introduced an associated `type Body`. The simplest migration will be to use `BoxBody` + `.map_into_boxed_body()` but if there is a more expressive type for your responder then try to use that instead.
TODO
```diff
impl Responder for &'static str {
- type Error = Error;
- type Future = Ready<Result<HttpResponse, Error>>;
+ type Body = &'static str;
## It's probably not necessary to import `actix-rt` or `actix-service` any more
- fn respond_to(self, req: &HttpRequest) -> Self::Future {
+ fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
let res = HttpResponse::build(StatusCode::OK)
.content_type("text/plain; charset=utf-8")
.body(self);
TODO
- ok(res)
+ res
}
}
```
## Server must be awaited in order to run :warning:
## `App::data` Deprecation :warning:
TODO
The `App::data` method is deprecated. Replace instances of this with `App::app_data`. Exposing both methods led to lots of confusion when trying to extract the data in handlers. Now, when using the `Data` wrapper, the type you put in to `app_data` is the same type you extract in handler arguments.
You may need to review the [guidance on shared mutable state](https://docs.rs/actix-web/4/actix_web/struct.App.html#shared-mutable-state) in order to migrate this correctly.
```diff
use actix_web::web::Data;
#[get("/")]
async fn handler(my_state: Data<MyState>) -> { todo!() }
HttpServer::new(|| {
- App::new()
- .data(MyState::default())
- .service(hander)
+ let my_state: Data<MyState> = Data::new(MyState::default());
+
+ App::new()
+ .app_data(my_state)
+ .service(hander)
})
```
## Direct Dependency On `actix-rt` And `actix-service`
Improvements to module management and re-exports have resulted in not needing direct dependencies on these underlying crates for the vast majority of cases. In particular:
- all traits necessary for creating middlewares are now re-exported through the `dev` modules;
- `#[actix_web::test]` now exists for async test definitions.
Relying on these re-exports will ease the transition to future versions of Actix Web.
```diff
- use actix_service::{Service, Transform};
+ use actix_web::dev::{Service, Transform};
```
```diff
- #[actix_rt::test]
+ #[actix_web::test]
async fn test_thing() {
```
## Server Must Be Polled :warning:
In order to _start_ serving requests, the `Server` object returned from `run` **must** be `poll`ed, `await`ed, or `spawn`ed. This was done to prevent unexpected behavior and ensure that things like signal handlers are able to function correctly when enabled.
For example, in this contrived example where the server is started and then the main thread is sent to sleep, the server will no longer be able to serve requests with v4.0:
```rust
#[actix_web::main]
async fn main() {
HttpServer::new(|| App::new().default_service(web::to(HttpResponse::Conflict)))
.bind(("127.0.0.1", 8080))
.unwrap()
.run();
thread::sleep(Duration::from_secs(1000));
}
```
## Guards API
TODO
Implementors of routing guards will need to use the modified interface of the `Guard` trait. The API is more flexible than before. See [guard module docs](https://docs.rs/actix-web/4/actix_web/guard/struct.GuardContext.html) for more details.
## HttpResponse no longer implements Future
```diff
struct MethodGuard(HttpMethod);
TODO
impl Guard for MethodGuard {
- fn check(&self, request: &RequestHead) -> bool {
+ fn check(&self, ctx: &GuardContext<'_>) -> bool {
- request.method == self.0
+ ctx.head().method == self.0
}
}
```
## Returning `HttpResponse` synchronously
The implementation of `Future` for `HttpResponse` was removed because it was largely useless for all but the simplest handlers like `web::to(|| HttpResponse::Ok().finish())`. It also caused false positives on the `async_yields_async` clippy lint in reasonable scenarios. The compiler errors will looks something like:
```
web::to(|| HttpResponse::Ok().finish())
^^^^^^^ the trait `Handler<_>` is not implemented for `[closure@...]`
```
This form should be replaced with explicit async functions and closures:
```diff
- fn handler() -> HttpResponse {
+ async fn handler() -> HttpResponse {
HttpResponse::Ok().finish()
}
```
```diff
- web::to(|| HttpResponse::Ok().finish())
+ web::to(|| async { HttpResponse::Ok().finish() })
```
Or, for these extremely simple cases, utilise an `HttpResponseBuilder`:
```diff
- web::to(|| HttpResponse::Ok().finish())
+ web::to(HttpResponse::Ok)
```
## `#[actix_web::main]` and `#[tokio::main]`
Actix Web now works seamlessly with the primary way of starting a multi-threaded Tokio runtime, `#[tokio::main]`. Therefore, it is no longer necessary to spawn a thread when you need to run something alongside Actix Web that uses Tokio's multi-threaded mode; you can simply await the server within this context or, if preferred, use `tokio::spawn` just like any other async task.
For now, `actix` actor support (and therefore WebSocket support via `actix-web-actors`) still requires `#[actix_web::main]` so that a `System` context is created. Designs are being created for an alternative WebSocket interface that does not require actors that should land sometime in the v4.x cycle.
## `web::block`
The `web::block` helper has changed return type from roughly `async fn(fn() -> Result<T, E>) Result<T, BlockingError<E>>` to `async fn(fn() -> T) Result<T, BlockingError>`. That's to say that the blocking function can now return things that are not `Result`s and it does not wrap error types anymore. If you still need to return `Result`s then you'll likely want to use double `?` after the `.await`.
```diff
- let n: u32 = web::block(|| Ok(123)).await?;
+ let n: u32 = web::block(|| 123).await?;
- let n: u32 = web::block(|| Ok(123)).await?;
+ let n: u32 = web::block(|| Ok(123)).await??;
```

View File

@@ -6,10 +6,10 @@
<p>
[![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.0.0-rc.2)](https://docs.rs/actix-web/4.0.0-rc.2)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-rc.3)](https://docs.rs/actix-web/4.0.0-rc.3)
![MSRV](https://img.shields.io/badge/rustc-1.54+-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.0.0-rc.2/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.2)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-rc.3/status.svg)](https://deps.rs/crate/actix-web/4.0.0-rc.3)
<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)
@@ -32,7 +32,7 @@
- Static assets
- SSL support using OpenSSL or Rustls
- Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
- Includes an async [HTTP client](https://docs.rs/awc/)
- Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
- Runs on stable Rust 1.54+
## Documentation
@@ -71,22 +71,24 @@ async fn main() -> std::io::Result<()> {
}
```
### More examples
### More Examples
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics/)
- [Application State](https://github.com/actix/examples/tree/master/basics/state/)
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json/)
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart/)
- [Diesel Integration](https://github.com/actix/examples/tree/master/database_interactions/diesel/)
- [r2d2 Integration](https://github.com/actix/examples/tree/master/database_interactions/r2d2/)
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets/websocket/)
- [Tera Templates](https://github.com/actix/examples/tree/master/template_engines/tera/)
- [Askama Templates](https://github.com/actix/examples/tree/master/template_engines/askama/)
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/security/rustls/)
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/security/openssl/)
- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat/)
- [Hello World](https://github.com/actix/examples/tree/master/basics/hello-world)
- [Basic Setup](https://github.com/actix/examples/tree/master/basics/basics)
- [Application State](https://github.com/actix/examples/tree/master/basics/state)
- [JSON Handling](https://github.com/actix/examples/tree/master/json/json)
- [Multipart Streams](https://github.com/actix/examples/tree/master/forms/multipart)
- [Diesel Integration](https://github.com/actix/examples/tree/master/databases/diesel)
- [SQLite Integration](https://github.com/actix/examples/tree/master/databases/sqlite)
- [Postgres Integration](https://github.com/actix/examples/tree/master/databases/postgres)
- [Tera Templates](https://github.com/actix/examples/tree/master/templating/tera)
- [Askama Templates](https://github.com/actix/examples/tree/master/templating/askama)
- [HTTPS using Rustls](https://github.com/actix/examples/tree/master/https-tls/rustls)
- [HTTPS using OpenSSL](https://github.com/actix/examples/tree/master/https-tls/openssl)
- [Simple WebSocket](https://github.com/actix/examples/tree/master/websockets)
- [WebSocket Chat](https://github.com/actix/examples/tree/master/websockets/chat)
You may consider checking out [this directory](https://github.com/actix/examples/tree/master/) for more examples.
You may consider checking out [this directory](https://github.com/actix/examples/tree/master) for more examples.
## Benchmarks
@@ -101,5 +103,4 @@ This project is licensed under either of the following licenses, at your option:
## Code of Conduct
Contribution to the actix-web repo is organized under the terms of the Contributor Covenant.
The Actix team promises to intervene to uphold that code of conduct.
Contribution to the actix-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

@@ -2,7 +2,7 @@
//! properties and pass them to a handler through request-local data.
//!
//! For an example of extracting a client TLS certificate, see:
//! <https://github.com/actix/examples/tree/HEAD/security/rustls-client-cert>
//! <https://github.com/actix/examples/tree/master/https-tls/rustls-client-cert>
use std::{any::Any, io, net::SocketAddr};

View File

@@ -10,12 +10,16 @@ use crate::{
/// The interface for request handlers.
///
/// # What Is A Request Handler
/// A request handler has three requirements:
/// In short, a handler is just an async function that receives request-based arguments, in any
/// order, and returns something that can be converted to a response.
///
/// In particular, a request handler has three requirements:
/// 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).
///
///
/// # Compiler Errors
/// If you get the error `the trait Handler<_> is not implemented`, then your handler does not
/// fulfill the _first_ of the above requirements. Missing other requirements manifest as errors on

View File

@@ -62,18 +62,18 @@ crate::http::header::common_header! {
#[cfg(test)]
mod tests {
use actix_http::test::TestRequest;
use super::IfNoneMatch;
use crate::http::header::{EntityTag, Header, IF_NONE_MATCH};
use actix_http::test::TestRequest;
#[test]
fn test_if_none_match() {
let mut if_none_match: Result<IfNoneMatch, _>;
let req = TestRequest::default()
.insert_header((IF_NONE_MATCH, "*"))
.finish();
if_none_match = Header::parse(&req);
let mut if_none_match = IfNoneMatch::parse(&req);
assert_eq!(if_none_match.ok(), Some(IfNoneMatch::Any));
let req = TestRequest::default()

View File

@@ -3,4 +3,4 @@
pub mod header;
// TODO: figure out how best to expose http::Error vs actix_http::Error
pub use actix_http::{uri, ConnectionType, Error, Method, StatusCode, Uri, Version};
pub use actix_http::{uri, ConnectionType, Error, KeepAlive, Method, StatusCode, Uri, Version};

View File

@@ -42,28 +42,29 @@
//! and otherwise utilizing them.
//!
//! # Features
//! * Supports *HTTP/1.x* and *HTTP/2*
//! * Streaming and pipelining
//! * Keep-alive and slow requests handling
//! * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
//! * Transparent content compression/decompression (br, gzip, deflate, zstd)
//! * Powerful [request routing](https://actix.rs/docs/url-dispatch/)
//! * Multipart streams
//! * Static assets
//! * SSL support using OpenSSL or Rustls
//! * Middlewares ([Logger, Session, CORS, etc](https://actix.rs/docs/middleware/))
//! * Includes an async [HTTP client](https://docs.rs/awc/)
//! * Runs on stable Rust 1.54+
//! - Supports HTTP/1.x and HTTP/2
//! - Streaming and pipelining
//! - Powerful [request routing](https://actix.rs/docs/url-dispatch/) with optional macros
//! - Full [Tokio](https://tokio.rs) compatibility
//! - Keep-alive and slow requests handling
//! - Client/server [WebSockets](https://actix.rs/docs/websockets/) support
//! - Transparent content compression/decompression (br, gzip, deflate, zstd)
//! - Multipart streams
//! - Static assets
//! - SSL support using OpenSSL or Rustls
//! - Middlewares ([Logger, Session, CORS, etc](middleware))
//! - Integrates with the [`awc` HTTP client](https://docs.rs/awc/)
//! - Runs on stable Rust 1.54+
//!
//! # Crate Features
//! * `cookies` - cookies support (enabled by default)
//! * `macros` - routing and runtime macros (enabled by default)
//! * `compress-brotli` - brotli content encoding compression support (enabled by default)
//! * `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
//! * `compress-zstd` - zstd content encoding compression support (enabled by default)
//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
//! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
//! * `secure-cookies` - secure cookies support
//! - `cookies` - cookies support (enabled by default)
//! - `macros` - routing and runtime macros (enabled by default)
//! - `compress-brotli` - brotli content encoding compression support (enabled by default)
//! - `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
//! - `compress-zstd` - zstd content encoding compression support (enabled by default)
//! - `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
//! - `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
//! - `secure-cookies` - secure cookies support
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]

View File

@@ -0,0 +1,13 @@
# Middleware Author's Guide
## What Is A Middleware?
## Middleware Traits
## Understanding Body Types
## Best Practices
## Error Propagation
## When To (Not) Use Middleware

View File

@@ -1,18 +1,22 @@
//! For middleware documentation, see [`Condition`].
use std::task::{Context, Poll};
use std::{
future::Future,
pin::Pin,
task::{Context, Poll},
};
use actix_service::{Service, Transform};
use actix_utils::future::Either;
use futures_core::future::LocalBoxFuture;
use futures_core::{future::LocalBoxFuture, ready};
use futures_util::future::FutureExt as _;
use pin_project_lite::pin_project;
use crate::{
body::EitherBody,
dev::{Service, ServiceResponse, Transform},
};
/// Middleware for conditionally enabling other middleware.
///
/// The controlled middleware must not change the `Service` interfaces. This means you cannot
/// control such middlewares like `Logger` or `Compress` directly. See the [`Compat`](super::Compat)
/// middleware for a workaround.
///
/// # Examples
/// ```
/// use actix_web::middleware::{Condition, NormalizePath};
@@ -36,16 +40,16 @@ impl<T> Condition<T> {
}
}
impl<S, T, Req> Transform<S, Req> for Condition<T>
impl<S, T, Req, BE, BD, Err> Transform<S, Req> for Condition<T>
where
S: Service<Req> + 'static,
T: Transform<S, Req, Response = S::Response, Error = S::Error>,
S: Service<Req, Response = ServiceResponse<BD>, Error = Err> + 'static,
T: Transform<S, Req, Response = ServiceResponse<BE>, Error = Err>,
T::Future: 'static,
T::InitError: 'static,
T::Transform: 'static,
{
type Response = S::Response;
type Error = S::Error;
type Response = ServiceResponse<EitherBody<BE, BD>>;
type Error = Err;
type Transform = ConditionMiddleware<T::Transform, S>;
type InitError = T::InitError;
type Future = LocalBoxFuture<'static, Result<Self::Transform, Self::InitError>>;
@@ -69,14 +73,14 @@ pub enum ConditionMiddleware<E, D> {
Disable(D),
}
impl<E, D, Req> Service<Req> for ConditionMiddleware<E, D>
impl<E, D, Req, BE, BD, Err> Service<Req> for ConditionMiddleware<E, D>
where
E: Service<Req>,
D: Service<Req, Response = E::Response, Error = E::Error>,
E: Service<Req, Response = ServiceResponse<BE>, Error = Err>,
D: Service<Req, Response = ServiceResponse<BD>, Error = Err>,
{
type Response = E::Response;
type Error = E::Error;
type Future = Either<E::Future, D::Future>;
type Response = ServiceResponse<EitherBody<BE, BD>>;
type Error = Err;
type Future = ConditionMiddlewareFuture<E::Future, D::Future>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
match self {
@@ -87,27 +91,59 @@ where
fn call(&self, req: Req) -> Self::Future {
match self {
ConditionMiddleware::Enable(service) => Either::left(service.call(req)),
ConditionMiddleware::Disable(service) => Either::right(service.call(req)),
ConditionMiddleware::Enable(service) => ConditionMiddlewareFuture::Enabled {
fut: service.call(req),
},
ConditionMiddleware::Disable(service) => ConditionMiddlewareFuture::Disabled {
fut: service.call(req),
},
}
}
}
pin_project! {
#[doc(hidden)]
#[project = ConditionProj]
pub enum ConditionMiddlewareFuture<E, D> {
Enabled { #[pin] fut: E, },
Disabled { #[pin] fut: D, },
}
}
impl<E, D, BE, BD, Err> Future for ConditionMiddlewareFuture<E, D>
where
E: Future<Output = Result<ServiceResponse<BE>, Err>>,
D: Future<Output = Result<ServiceResponse<BD>, Err>>,
{
type Output = Result<ServiceResponse<EitherBody<BE, BD>>, Err>;
#[inline]
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = match self.project() {
ConditionProj::Enabled { fut } => ready!(fut.poll(cx))?.map_into_left_body(),
ConditionProj::Disabled { fut } => ready!(fut.poll(cx))?.map_into_right_body(),
};
Poll::Ready(Ok(res))
}
}
#[cfg(test)]
mod tests {
use actix_service::IntoService;
use actix_utils::future::ok;
use actix_service::IntoService as _;
use super::*;
use crate::{
body::BoxBody,
dev::{ServiceRequest, ServiceResponse},
error::Result,
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
middleware::{err_handlers::*, Compat},
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
test::{self, TestRequest},
web::Bytes,
HttpResponse,
};
@@ -120,40 +156,52 @@ mod tests {
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}
#[test]
fn compat_with_builtin_middleware() {
let _ = Condition::new(true, middleware::Compat::noop());
let _ = Condition::new(true, middleware::Logger::default());
let _ = Condition::new(true, middleware::Compress::default());
let _ = Condition::new(true, middleware::NormalizePath::trim());
let _ = Condition::new(true, middleware::DefaultHeaders::new());
let _ = Condition::new(true, middleware::ErrorHandlers::<BoxBody>::new());
let _ = Condition::new(true, middleware::ErrorHandlers::<Bytes>::new());
}
#[actix_rt::test]
async fn test_handler_enabled() {
let srv = |req: ServiceRequest| {
ok(req.into_response(HttpResponse::InternalServerError().finish()))
let srv = |req: ServiceRequest| async move {
let resp = HttpResponse::InternalServerError().message_body(String::new())?;
Ok(req.into_response(resp))
};
let mw = Compat::new(
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
);
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mw = Condition::new(true, mw)
.new_transform(srv.into_service())
.await
.unwrap();
let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await;
let resp: ServiceResponse<EitherBody<EitherBody<_, _>, String>> =
test::call_service(&mw, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
}
#[actix_rt::test]
async fn test_handler_disabled() {
let srv = |req: ServiceRequest| {
ok(req.into_response(HttpResponse::InternalServerError().finish()))
let srv = |req: ServiceRequest| async move {
let resp = HttpResponse::InternalServerError().message_body(String::new())?;
Ok(req.into_response(resp))
};
let mw = Compat::new(
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
);
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mw = Condition::new(false, mw)
.new_transform(srv.into_service())
.await
.unwrap();
let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await;
let resp: ServiceResponse<EitherBody<EitherBody<_, _>, String>> =
test::call_service(&mw, TestRequest::default().to_srv_request()).await;
assert_eq!(resp.headers().get(CONTENT_TYPE), None);
}
}

View File

@@ -7,7 +7,7 @@ use crate::{HttpRequest, HttpResponse, Responder};
/// Allows overriding status code and headers for a [`Responder`].
///
/// Created by the [`Responder::customize`] method.
/// Created by calling the [`customize`](Responder::customize) method on a [`Responder`] type.
pub struct CustomizeResponder<R> {
inner: CustomizeResponderInner<R>,
error: Option<HttpError>,

View File

@@ -47,6 +47,15 @@ pub trait Responder {
CustomizeResponder::new(self)
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Prefer `.customize().with_status(header)`.")]
fn with_status(self, status: StatusCode) -> CustomizeResponder<Self>
where
Self: Sized,
{
self.customize().with_status(status)
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Prefer `.customize().insert_header(header)`.")]
fn with_header(self, header: impl TryIntoHeaderPair) -> CustomizeResponder<Self>
@@ -132,6 +141,7 @@ macro_rules! impl_responder_by_forward_into_base_response {
}
impl_responder_by_forward_into_base_response!(&'static [u8]);
impl_responder_by_forward_into_base_response!(Vec<u8>);
impl_responder_by_forward_into_base_response!(Bytes);
impl_responder_by_forward_into_base_response!(BytesMut);

View File

@@ -1,9 +1,10 @@
//! A selection of re-exports from [`actix-rt`] and [`tokio`].
//! A selection of re-exports from [`tokio`] and [`actix-rt`].
//!
//! [`actix-rt`]: https://docs.rs/actix_rt
//! [`tokio`]: https://docs.rs/tokio
//! Actix Web runs on [Tokio], providing full[^compat] compatibility with its huge ecosystem of
//! crates. Each of the server's workers uses a single-threaded runtime. Read more about the
//! architecture in [`actix-rt`]'s docs.
//!
//! # Running Actix Web Macro-less
//! # Running Actix Web Without Macros
//! ```no_run
//! use actix_web::{middleware, rt, web, App, HttpRequest, HttpServer};
//!
@@ -12,19 +13,53 @@
//! "Hello world!\r\n"
//! }
//!
//! # fn main() -> std::io::Result<()> {
//! rt::System::new().block_on(
//! fn main() -> std::io::Result<()> {
//! rt::System::new().block_on(
//! HttpServer::new(|| {
//! App::new().service(web::resource("/").route(web::get().to(index)))
//! })
//! .bind(("127.0.0.1", 8080))?
//! .run()
//! )
//! }
//! ```
//!
//! # Running Actix Web Using `#[tokio::main]`
//! If you need to run something alongside Actix Web that uses Tokio's work stealing functionality,
//! you can run Actix Web under `#[tokio::main]`. The [`Server`](crate::dev::Server) object returned
//! from [`HttpServer::run`](crate::HttpServer::run) can also be [`spawn`]ed, if preferred.
//!
//! Note that `actix` actor support (and therefore WebSocket support through `actix-web-actors`)
//! still require `#[actix_web::main]` since they require a [`System`] to be set up.
//!
//! ```no_run
//! use actix_web::{get, middleware, rt, web, App, HttpRequest, HttpServer};
//!
//! #[get("/")]
//! async fn index(req: HttpRequest) -> &'static str {
//! println!("REQ: {:?}", req);
//! "Hello world!\r\n"
//! }
//!
//! #[tokio::main]
//! async fn main() -> std::io::Result<()> {
//! HttpServer::new(|| {
//! App::new()
//! .wrap(middleware::Logger::default())
//! .service(web::resource("/").route(web::get().to(index)))
//! App::new().service(index)
//! })
//! .bind(("127.0.0.1", 8080))?
//! .workers(1)
//! .run()
//! )
//! # }
//! .await
//! }
//! ```
//!
//! [^compat]: Crates that use Tokio's [`block_in_place`] will not work with Actix Web. Fortunately,
//! the vast majority of Tokio-based crates do not use it.
//!
//! [`actix-rt`]: https://docs.rs/actix-rt
//! [`tokio`]: https://docs.rs/tokio
//! [Tokio]: https://docs.rs/tokio
//! [`spawn`]: https://docs.rs/tokio/1/tokio/fn.spawn.html
//! [`block_in_place`]: https://docs.rs/tokio/1/tokio/task/fn.block_in_place.html
// In particular:
// - Omit the `Arbiter` types because they have limited value here.

View File

@@ -128,7 +128,7 @@ where
/// Set number of workers to start.
///
/// By default, server uses number of available logical CPU as thread count.
/// By default, the number of available physical CPUs is used as the worker count.
pub fn workers(mut self, num: usize) -> Self {
self.builder = self.builder.workers(num);
self

View File

@@ -53,9 +53,7 @@ use crate::{
/// format!("Welcome {}!", info.name)
/// }
/// ```
#[derive(
Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, AsRef, Display, From,
)]
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Deref, DerefMut, AsRef, Display, From)]
pub struct Path<T>(T);
impl<T> Path<T> {

View File

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 3.0.0-beta.21 - 2022-02-16
- No significant changes since `3.0.0-beta.20`.
## 3.0.0-beta.20 - 2022-01-31
- No significant changes since `3.0.0-beta.19`.

View File

@@ -1,6 +1,6 @@
[package]
name = "awc"
version = "3.0.0-beta.20"
version = "3.0.0-beta.21"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
@@ -58,11 +58,11 @@ __compress = []
dangerous-h2c = []
[dependencies]
actix-codec = "0.4.1"
actix-codec = "0.5"
actix-service = "2.0.0"
actix-http = { version = "3.0.0-rc.1", features = ["http2", "ws"] }
actix-http = { version = "3.0.0-rc.4", features = ["http2", "ws"] }
actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.0.0", features = ["connect", "uri"] }
actix-tls = { version = "3", features = ["connect", "uri"] }
actix-utils = "3.0.0"
ahash = "0.7"
@@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies]
actix-http = { version = "3.0.0-rc.1", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.12", features = ["openssl"] }
actix-http = { version = "3.0.0-rc.4", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.13", features = ["openssl"] }
actix-server = "2"
actix-test = { version = "0.1.0-beta.12", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.13", features = ["openssl", "rustls"] }
actix-tls = { version = "3", features = ["openssl", "rustls"] }
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-rc.2", features = ["openssl"] }
actix-web = { version = "4.0.0-rc.3", features = ["openssl"] }
brotli = "3.3.3"
const-str = "0.3"

View File

@@ -3,15 +3,15 @@
> Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.20)](https://docs.rs/awc/3.0.0-beta.20)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.21)](https://docs.rs/awc/3.0.0-beta.21)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.20/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.20)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.21/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.21)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources
- [API Documentation](https://docs.rs/awc)
- [Example Project](https://github.com/actix/examples/tree/HEAD/security/awc_https)
- [Example Project](https://github.com/actix/examples/tree/master/https-tls/awc-https)
- Minimum Supported Rust Version (MSRV): 1.54
## Example

View File

@@ -160,7 +160,7 @@ impl<S: fmt::Debug> fmt::Debug for AnyBody<S> {
mod tests {
use std::marker::PhantomPinned;
use static_assertions::{assert_impl_all, assert_not_impl_all};
use static_assertions::{assert_impl_all, assert_not_impl_any};
use super::*;
@@ -181,12 +181,12 @@ mod tests {
}
}
assert_impl_all!(AnyBody<()>: MessageBody, fmt::Debug, Send, Sync, Unpin);
assert_impl_all!(AnyBody<AnyBody<()>>: MessageBody, fmt::Debug, Send, Sync, Unpin);
assert_impl_all!(AnyBody<Bytes>: MessageBody, fmt::Debug, Send, Sync, Unpin);
assert_impl_all!(AnyBody: MessageBody, fmt::Debug, Unpin);
assert_impl_all!(AnyBody<PinType>: MessageBody);
assert_impl_all!(AnyBody<()>: Send, Sync, Unpin, fmt::Debug, MessageBody);
assert_impl_all!(AnyBody<AnyBody<()>>: Send, Sync, Unpin, fmt::Debug, MessageBody);
assert_impl_all!(AnyBody<Bytes>: Send, Sync, Unpin, fmt::Debug, MessageBody);
assert_impl_all!(AnyBody: Unpin, fmt::Debug, MessageBody);
assert_impl_all!(AnyBody<PinType>: Send, Sync, MessageBody);
assert_not_impl_all!(AnyBody: Send, Sync, Unpin);
assert_not_impl_all!(AnyBody<PinType>: Send, Sync, Unpin);
assert_not_impl_any!(AnyBody: Send, Sync);
assert_not_impl_any!(AnyBody<PinType>: Unpin);
}

View File

@@ -337,7 +337,7 @@ where
match self.get_mut() {
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_write(cx, buf),
_ => unreachable!(H2_UNREACHABLE_WRITE),
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
}
}
@@ -345,7 +345,7 @@ where
match self.get_mut() {
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx),
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_flush(cx),
_ => unreachable!(H2_UNREACHABLE_WRITE),
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
}
}
@@ -353,7 +353,7 @@ where
match self.get_mut() {
Connection::Tcp(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
Connection::Tls(ConnectionType::H1(conn)) => Pin::new(conn).poll_shutdown(cx),
_ => unreachable!(H2_UNREACHABLE_WRITE),
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
}
}
@@ -369,7 +369,7 @@ where
Connection::Tls(ConnectionType::H1(conn)) => {
Pin::new(conn).poll_write_vectored(cx, bufs)
}
_ => unreachable!(H2_UNREACHABLE_WRITE),
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
}
}
@@ -377,7 +377,7 @@ where
match *self {
Connection::Tcp(ConnectionType::H1(ref conn)) => conn.is_write_vectored(),
Connection::Tls(ConnectionType::H1(ref conn)) => conn.is_write_vectored(),
_ => unreachable!(H2_UNREACHABLE_WRITE),
_ => unreachable!("{}", H2_UNREACHABLE_WRITE),
}
}
}

View File

@@ -34,7 +34,7 @@ digraph {
"utils" -> { "service" "rt" "codec" }
"tracing" -> { "service" }
"tls" -> { "service" "codec" "utils" }
"server" -> { "service" "rt" "codec" "utils" }
"server" -> { "service" "rt" "utils" }
"rt" -> { "macros" }
{ rank=same; "utils" "codec" };

View File

@@ -9,7 +9,16 @@ unreleased_for() {
DIR=$1
CARGO_MANIFEST=$DIR/Cargo.toml
CHANGELOG_FILE=$DIR/CHANGES.md
# determine changelog file name
if [ -f "$DIR/CHANGES.md" ]; then
CHANGELOG_FILE=$DIR/CHANGES.md
elif [ -f "$DIR/CHANGELOG.md" ]; then
CHANGELOG_FILE=$DIR/CHANGELOG.md
else
echo "No changelog file found"
exit 1
fi
# get current version
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
@@ -36,6 +45,6 @@ unreleased_for() {
cat "$CHANGE_CHUNK_FILE"
}
for f in $(fd --absolute-path CHANGES.md); do
for f in $(fd --absolute-path 'CHANGE\w+.md'); do
unreleased_for $(dirname $f)
done