1
0
mirror of https://github.com/fafhrd91/actix-web synced 2024-11-24 00:21:08 +01:00

Merge branch 'master' into feat/field-in-deserialize-error

This commit is contained in:
Roms1383 2024-08-27 12:01:02 +07:00
commit b47f65063b
88 changed files with 1049 additions and 401 deletions

7
.clippy.toml Normal file
View File

@ -0,0 +1,7 @@
disallowed-names = [
"e", # no single letter error bindings
]
disallowed-methods = [
"std::cell::RefCell::default()",
"std::rc::Rc::default()",
]

View File

@ -49,7 +49,7 @@ jobs:
toolchain: ${{ matrix.version.version }} toolchain: ${{ matrix.version.version }}
- name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean - name: Install just, cargo-hack, cargo-nextest, cargo-ci-cache-clean
uses: taiki-e/install-action@v2.41.10 uses: taiki-e/install-action@v2.42.33
with: with:
tool: just,cargo-hack,cargo-nextest,cargo-ci-cache-clean 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 uses: actions-rust-lang/setup-rust-toolchain@v1.9.0
- name: Install just, cargo-hack - name: Install just, cargo-hack
uses: taiki-e/install-action@v2.41.10 uses: taiki-e/install-action@v2.42.33
with: with:
tool: just,cargo-hack tool: just,cargo-hack

View File

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

View File

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

View File

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

View File

@ -19,7 +19,7 @@ homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web" repository = "https://github.com/actix/actix-web"
license = "MIT OR Apache-2.0" license = "MIT OR Apache-2.0"
edition = "2021" edition = "2021"
rust-version = "1.72" rust-version = "1.75"
[profile.dev] [profile.dev]
# Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much. # Disabling debug info speeds up builds a bunch and we don't rely on it for debugging that much.

View File

@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.6.6 ## 0.6.6
- Update `tokio-uring` dependency to `0.4`. - Update `tokio-uring` dependency to `0.4`.

View File

@ -33,7 +33,7 @@ actix-web = { version = "4", default-features = false }
bitflags = "2" bitflags = "2"
bytes = "1" bytes = "1"
derive_more = "0.99.5" derive_more = { version = "1", features = ["display", "error", "from"] }
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
http-range = "0.1.4" http-range = "0.1.4"
log = "0.4" log = "0.4"

View File

@ -1,16 +1,16 @@
use actix_web::{http::StatusCode, ResponseError}; use actix_web::{http::StatusCode, ResponseError};
use derive_more::Display; use derive_more::derive::Display;
/// Errors which can occur when serving static files. /// Errors which can occur when serving static files.
#[derive(Debug, PartialEq, Eq, Display)] #[derive(Debug, PartialEq, Eq, Display)]
pub enum FilesError { pub enum FilesError {
/// Path is not a directory. /// Path is not a directory.
#[allow(dead_code)] #[allow(dead_code)]
#[display(fmt = "path is not a directory. Unable to serve static files")] #[display("path is not a directory. Unable to serve static files")]
IsNotDirectory, IsNotDirectory,
/// Cannot render directory. /// Cannot render directory.
#[display(fmt = "unable to render directory without index file")] #[display("unable to render directory without index file")]
IsDirectory, IsDirectory,
} }
@ -25,19 +25,19 @@ impl ResponseError for FilesError {
#[non_exhaustive] #[non_exhaustive]
pub enum UriSegmentError { pub enum UriSegmentError {
/// Segment started with the wrapped invalid character. /// Segment started with the wrapped invalid character.
#[display(fmt = "segment started with invalid character: ('{_0}')")] #[display("segment started with invalid character: ('{_0}')")]
BadStart(char), BadStart(char),
/// Segment contained the wrapped invalid character. /// Segment contained the wrapped invalid character.
#[display(fmt = "segment contained invalid character ('{_0}')")] #[display("segment contained invalid character ('{_0}')")]
BadChar(char), BadChar(char),
/// Segment ended with the wrapped invalid character. /// Segment ended with the wrapped invalid character.
#[display(fmt = "segment ended with invalid character: ('{_0}')")] #[display("segment ended with invalid character: ('{_0}')")]
BadEnd(char), BadEnd(char),
/// Path is not a valid UTF-8 string after percent-decoding. /// Path is not a valid UTF-8 string after percent-decoding.
#[display(fmt = "path is not a valid UTF-8 string after percent-decoding")] #[display("path is not a valid UTF-8 string after percent-decoding")]
NotValidUtf8, NotValidUtf8,
} }

View File

@ -21,7 +21,7 @@ use actix_web::{
Error, HttpMessage, HttpRequest, HttpResponse, Responder, Error, HttpMessage, HttpRequest, HttpResponse, Responder,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use derive_more::{Deref, DerefMut}; use derive_more::derive::{Deref, DerefMut};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use mime::Mime; use mime::Mime;

View File

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use derive_more::Error; use derive_more::derive::Error;
/// Copy of `http_range::HttpRangeParseError`. /// Copy of `http_range::HttpRangeParseError`.
#[derive(Debug, Clone)] #[derive(Debug, Clone)]

View File

@ -79,7 +79,7 @@ impl FilesService {
let (req, _) = req.into_parts(); let (req, _) = req.into_parts();
(self.renderer)(&dir, &req).unwrap_or_else(|e| ServiceResponse::from_err(e, req)) (self.renderer)(&dir, &req).unwrap_or_else(|err| ServiceResponse::from_err(err, req))
} }
} }

View File

@ -106,7 +106,7 @@ pub async fn test_server_with_addr<F: ServerServiceFactory<TcpStream>>(
builder.set_verify(SslVerifyMode::NONE); builder.set_verify(SslVerifyMode::NONE);
let _ = builder let _ = builder
.set_alpn_protos(b"\x02h2\x08http/1.1") .set_alpn_protos(b"\x02h2\x08http/1.1")
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e)); .map_err(|err| log::error!("Can not set ALPN protocol: {err}"));
Connector::new() Connector::new()
.conn_lifetime(Duration::from_secs(0)) .conn_lifetime(Duration::from_secs(0))

View File

@ -2,6 +2,10 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.75.
## 3.9.0
### Added ### Added
- Implement `FromIterator<(HeaderName, HeaderValue)>` for `HeaderMap`. - Implement `FromIterator<(HeaderName, HeaderValue)>` for `HeaderMap`.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.8.0" version = "3.9.0"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@ -110,7 +110,7 @@ ahash = "0.8"
bitflags = "2" bitflags = "2"
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
derive_more = "0.99.5" derive_more = { version = "1", features = ["as_ref", "deref", "deref_mut", "display", "error", "from"] }
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
http = "0.2.7" http = "0.2.7"
@ -160,7 +160,7 @@ rcgen = "0.13"
regex = "1.3" regex = "1.3"
rustversion = "1" rustversion = "1"
rustls-pemfile = "2" rustls-pemfile = "2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1", features = ["derive"] }
serde_json = "1.0" serde_json = "1.0"
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" } tls-openssl = { package = "openssl", version = "0.10.55" }

View File

@ -5,11 +5,11 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http) [![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) ![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) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br /> <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) [![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) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@ -1,10 +1,10 @@
use actix_http::HttpService; use actix_http::HttpService;
use actix_server::Server; use actix_server::Server;
use actix_service::map_config; use actix_service::map_config;
use actix_web::{dev::AppConfig, get, App}; use actix_web::{dev::AppConfig, get, App, Responder};
#[get("/")] #[get("/")]
async fn index() -> &'static str { async fn index() -> impl Responder {
"Hello, world. From Actix Web!" "Hello, world. From Actix Web!"
} }

View File

@ -23,7 +23,7 @@ async fn main() -> io::Result<()> {
body.extend_from_slice(&item?); body.extend_from_slice(&item?);
} }
info!("request body: {:?}", body); info!("request body: {body:?}");
let res = Response::build(StatusCode::OK) let res = Response::build(StatusCode::OK)
.insert_header(("x-head", HeaderValue::from_static("dummy value!"))) .insert_header(("x-head", HeaderValue::from_static("dummy value!")))
@ -31,8 +31,7 @@ async fn main() -> io::Result<()> {
Ok::<_, Error>(res) Ok::<_, Error>(res)
}) })
// No TLS .tcp() // No TLS
.tcp()
})? })?
.run() .run()
.await .await

View File

@ -17,7 +17,7 @@ async fn main() -> io::Result<()> {
ext.insert(42u32); ext.insert(42u32);
}) })
.finish(|req: Request| async move { .finish(|req: Request| async move {
info!("{:?}", req); info!("{req:?}");
let mut res = Response::build(StatusCode::OK); let mut res = Response::build(StatusCode::OK);
res.insert_header(("x-head", HeaderValue::from_static("dummy value!"))); res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));

View File

@ -22,16 +22,16 @@ async fn main() -> io::Result<()> {
.bind("streaming-error", ("127.0.0.1", 8080), || { .bind("streaming-error", ("127.0.0.1", 8080), || {
HttpService::build() HttpService::build()
.finish(|req| async move { .finish(|req| async move {
info!("{:?}", req); info!("{req:?}");
let res = Response::ok(); let res = Response::ok();
Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! { Ok::<_, Infallible>(res.set_body(BodyStream::new(stream! {
yield Ok(Bytes::from("123")); yield Ok(Bytes::from("123"));
yield Ok(Bytes::from("456")); yield Ok(Bytes::from("456"));
actix_rt::time::sleep(Duration::from_millis(1000)).await; actix_rt::time::sleep(Duration::from_secs(1)).await;
yield Err(io::Error::new(io::ErrorKind::Other, "")); yield Err(io::Error::new(io::ErrorKind::Other, "abc"));
}))) })))
}) })
.tcp() .tcp()

View File

@ -17,7 +17,6 @@ use bytes::{Bytes, BytesMut};
use bytestring::ByteString; use bytestring::ByteString;
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use tokio_util::codec::Encoder; use tokio_util::codec::Encoder;
use tracing::{info, trace};
#[actix_rt::main] #[actix_rt::main]
async fn main() -> io::Result<()> { async fn main() -> io::Result<()> {
@ -37,12 +36,12 @@ async fn main() -> io::Result<()> {
} }
async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> { async fn handler(req: Request) -> Result<Response<BodyStream<Heartbeat>>, Error> {
info!("handshaking"); tracing::info!("handshaking");
let mut res = ws::handshake(req.head())?; let mut res = ws::handshake(req.head())?;
// handshake will always fail under HTTP/2 // handshake will always fail under HTTP/2
info!("responding"); tracing::info!("responding");
res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new()))) res.message_body(BodyStream::new(Heartbeat::new(ws::Codec::new())))
} }
@ -64,7 +63,7 @@ impl Stream for Heartbeat {
type Item = Result<Bytes, Error>; type Item = Result<Bytes, Error>;
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> { fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
trace!("poll"); tracing::trace!("poll");
ready!(self.as_mut().interval.poll_tick(cx)); ready!(self.as_mut().interval.poll_tick(cx));

View File

@ -75,7 +75,7 @@ mod tests {
time::{sleep, Sleep}, time::{sleep, Sleep},
}; };
use actix_utils::future::poll_fn; use actix_utils::future::poll_fn;
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_core::ready; use futures_core::ready;
use futures_util::{stream, FutureExt as _}; use futures_util::{stream, FutureExt as _};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
@ -131,7 +131,7 @@ mod tests {
assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12"))); assert_eq!(to_bytes(body).await.ok(), Some(Bytes::from("12")));
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "stream error")] #[display("stream error")]
struct StreamErr; struct StreamErr;
#[actix_rt::test] #[actix_rt::test]

View File

@ -3,7 +3,7 @@ use std::task::Poll;
use actix_rt::pin; use actix_rt::pin;
use actix_utils::future::poll_fn; use actix_utils::future::poll_fn;
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_core::ready; use futures_core::ready;
use super::{BodySize, MessageBody}; use super::{BodySize, MessageBody};
@ -38,7 +38,7 @@ pub async fn to_bytes<B: MessageBody>(body: B) -> Result<Bytes, B::Error> {
/// Error type returned from [`to_bytes_limited`] when body produced exceeds limit. /// Error type returned from [`to_bytes_limited`] when body produced exceeds limit.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "limit exceeded while collecting body bytes")] #[display("limit exceeded while collecting body bytes")]
#[non_exhaustive] #[non_exhaustive]
pub struct BodyLimitExceeded; pub struct BodyLimitExceeded;

View File

@ -10,7 +10,7 @@ use std::{
use actix_rt::task::{spawn_blocking, JoinHandle}; use actix_rt::task::{spawn_blocking, JoinHandle};
use bytes::Bytes; use bytes::Bytes;
use derive_more::Display; use derive_more::derive::Display;
#[cfg(feature = "compress-gzip")] #[cfg(feature = "compress-gzip")]
use flate2::write::{GzEncoder, ZlibEncoder}; use flate2::write::{GzEncoder, ZlibEncoder};
use futures_core::ready; use futures_core::ready;
@ -415,11 +415,11 @@ fn new_brotli_compressor() -> Box<brotli::CompressorWriter<Writer>> {
#[non_exhaustive] #[non_exhaustive]
pub enum EncoderError { pub enum EncoderError {
/// Wrapped body stream error. /// Wrapped body stream error.
#[display(fmt = "body")] #[display("body")]
Body(Box<dyn StdError>), Body(Box<dyn StdError>),
/// Generic I/O error. /// Generic I/O error.
#[display(fmt = "io")] #[display("io")]
Io(io::Error), Io(io::Error),
} }

View File

@ -2,7 +2,7 @@
use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error}; use std::{error::Error as StdError, fmt, io, str::Utf8Error, string::FromUtf8Error};
use derive_more::{Display, Error, From}; use derive_more::derive::{Display, Error, From};
pub use http::{status::InvalidStatusCode, Error as HttpError}; pub use http::{status::InvalidStatusCode, Error as HttpError};
use http::{uri::InvalidUri, StatusCode}; use http::{uri::InvalidUri, StatusCode};
@ -80,28 +80,28 @@ impl From<Error> for Response<BoxBody> {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Display)]
pub(crate) enum Kind { pub(crate) enum Kind {
#[display(fmt = "error processing HTTP")] #[display("error processing HTTP")]
Http, Http,
#[display(fmt = "error parsing HTTP message")] #[display("error parsing HTTP message")]
Parse, Parse,
#[display(fmt = "request payload read error")] #[display("request payload read error")]
Payload, Payload,
#[display(fmt = "response body write error")] #[display("response body write error")]
Body, Body,
#[display(fmt = "send response error")] #[display("send response error")]
SendResponse, SendResponse,
#[display(fmt = "error in WebSocket process")] #[display("error in WebSocket process")]
Ws, Ws,
#[display(fmt = "connection error")] #[display("connection error")]
Io, Io,
#[display(fmt = "encoder error")] #[display("encoder error")]
Encoder, Encoder,
} }
@ -160,44 +160,44 @@ impl From<crate::ws::ProtocolError> for Error {
#[non_exhaustive] #[non_exhaustive]
pub enum ParseError { pub enum ParseError {
/// An invalid `Method`, such as `GE.T`. /// An invalid `Method`, such as `GE.T`.
#[display(fmt = "invalid method specified")] #[display("invalid method specified")]
Method, Method,
/// An invalid `Uri`, such as `exam ple.domain`. /// An invalid `Uri`, such as `exam ple.domain`.
#[display(fmt = "URI error: {}", _0)] #[display("URI error: {}", _0)]
Uri(InvalidUri), Uri(InvalidUri),
/// An invalid `HttpVersion`, such as `HTP/1.1` /// An invalid `HttpVersion`, such as `HTP/1.1`
#[display(fmt = "invalid HTTP version specified")] #[display("invalid HTTP version specified")]
Version, Version,
/// An invalid `Header`. /// An invalid `Header`.
#[display(fmt = "invalid Header provided")] #[display("invalid Header provided")]
Header, Header,
/// A message head is too large to be reasonable. /// A message head is too large to be reasonable.
#[display(fmt = "message head is too large")] #[display("message head is too large")]
TooLarge, TooLarge,
/// A message reached EOF, but is not complete. /// A message reached EOF, but is not complete.
#[display(fmt = "message is incomplete")] #[display("message is incomplete")]
Incomplete, Incomplete,
/// An invalid `Status`, such as `1337 ELITE`. /// An invalid `Status`, such as `1337 ELITE`.
#[display(fmt = "invalid status provided")] #[display("invalid status provided")]
Status, Status,
/// A timeout occurred waiting for an IO event. /// A timeout occurred waiting for an IO event.
#[allow(dead_code)] #[allow(dead_code)]
#[display(fmt = "timeout")] #[display("timeout")]
Timeout, Timeout,
/// An I/O error that occurred while trying to read or write to a network stream. /// An I/O error that occurred while trying to read or write to a network stream.
#[display(fmt = "I/O error: {}", _0)] #[display("I/O error: {}", _0)]
Io(io::Error), Io(io::Error),
/// Parsing a field as string failed. /// Parsing a field as string failed.
#[display(fmt = "UTF-8 error: {}", _0)] #[display("UTF-8 error: {}", _0)]
Utf8(Utf8Error), Utf8(Utf8Error),
} }
@ -256,28 +256,28 @@ impl From<ParseError> for Response<BoxBody> {
#[non_exhaustive] #[non_exhaustive]
pub enum PayloadError { pub enum PayloadError {
/// A payload reached EOF, but is not complete. /// A payload reached EOF, but is not complete.
#[display(fmt = "payload reached EOF before completing: {:?}", _0)] #[display("payload reached EOF before completing: {:?}", _0)]
Incomplete(Option<io::Error>), Incomplete(Option<io::Error>),
/// Content encoding stream corruption. /// Content encoding stream corruption.
#[display(fmt = "can not decode content-encoding")] #[display("can not decode content-encoding")]
EncodingCorrupted, EncodingCorrupted,
/// Payload reached size limit. /// Payload reached size limit.
#[display(fmt = "payload reached size limit")] #[display("payload reached size limit")]
Overflow, Overflow,
/// Payload length is unknown. /// Payload length is unknown.
#[display(fmt = "payload length is unknown")] #[display("payload length is unknown")]
UnknownLength, UnknownLength,
/// HTTP/2 payload error. /// HTTP/2 payload error.
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
#[display(fmt = "{}", _0)] #[display("{}", _0)]
Http2Payload(::h2::Error), Http2Payload(::h2::Error),
/// Generic I/O error. /// Generic I/O error.
#[display(fmt = "{}", _0)] #[display("{}", _0)]
Io(io::Error), Io(io::Error),
} }
@ -326,44 +326,44 @@ impl From<PayloadError> for Error {
#[non_exhaustive] #[non_exhaustive]
pub enum DispatchError { pub enum DispatchError {
/// Service error. /// Service error.
#[display(fmt = "service error")] #[display("service error")]
Service(Response<BoxBody>), Service(Response<BoxBody>),
/// Body streaming error. /// Body streaming error.
#[display(fmt = "body error: {}", _0)] #[display("body error: {}", _0)]
Body(Box<dyn StdError>), Body(Box<dyn StdError>),
/// Upgrade service error. /// Upgrade service error.
#[display(fmt = "upgrade error")] #[display("upgrade error")]
Upgrade, Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network stream. /// An `io::Error` that occurred while trying to read or write to a network stream.
#[display(fmt = "I/O error: {}", _0)] #[display("I/O error: {}", _0)]
Io(io::Error), Io(io::Error),
/// Request parse error. /// Request parse error.
#[display(fmt = "request parse error: {}", _0)] #[display("request parse error: {}", _0)]
Parse(ParseError), Parse(ParseError),
/// HTTP/2 error. /// HTTP/2 error.
#[display(fmt = "{}", _0)] #[display("{}", _0)]
#[cfg(feature = "http2")] #[cfg(feature = "http2")]
H2(h2::Error), H2(h2::Error),
/// The first request did not complete within the specified timeout. /// The first request did not complete within the specified timeout.
#[display(fmt = "request did not complete within the specified timeout")] #[display("request did not complete within the specified timeout")]
SlowRequestTimeout, SlowRequestTimeout,
/// Disconnect timeout. Makes sense for TLS streams. /// Disconnect timeout. Makes sense for TLS streams.
#[display(fmt = "connection shutdown timeout")] #[display("connection shutdown timeout")]
DisconnectTimeout, DisconnectTimeout,
/// Handler dropped payload before reading EOF. /// Handler dropped payload before reading EOF.
#[display(fmt = "handler dropped payload before reading EOF")] #[display("handler dropped payload before reading EOF")]
HandlerDroppedPayload, HandlerDroppedPayload,
/// Internal error. /// Internal error.
#[display(fmt = "internal error")] #[display("internal error")]
InternalError, InternalError,
} }
@ -389,11 +389,11 @@ impl StdError for DispatchError {
#[non_exhaustive] #[non_exhaustive]
pub enum ContentTypeError { pub enum ContentTypeError {
/// Can not parse content type. /// Can not parse content type.
#[display(fmt = "could not parse content type")] #[display("could not parse content type")]
ParseError, ParseError,
/// Unknown content encoding. /// Unknown content encoding.
#[display(fmt = "unknown content encoding")] #[display("unknown content encoding")]
UnknownEncoding, UnknownEncoding,
} }

View File

@ -313,7 +313,7 @@ impl MessageType for RequestHeadType {
_ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")), _ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")),
} }
) )
.map_err(|e| io::Error::new(io::ErrorKind::Other, e)) .map_err(|err| io::Error::new(io::ErrorKind::Other, err))
} }
} }
@ -433,7 +433,7 @@ impl TransferEncoding {
buf.extend_from_slice(b"0\r\n\r\n"); buf.extend_from_slice(b"0\r\n\r\n");
} else { } else {
writeln!(helpers::MutWriter(buf), "{:X}\r", msg.len()) writeln!(helpers::MutWriter(buf), "{:X}\r", msg.len())
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))?; .map_err(|err| io::Error::new(io::ErrorKind::Other, err))?;
buf.reserve(msg.len() + 2); buf.reserve(msg.len() + 2);
buf.extend_from_slice(msg); buf.extend_from_slice(msg);

View File

@ -480,15 +480,15 @@ where
let cfg = self.cfg.clone(); let cfg = self.cfg.clone();
Box::pin(async move { Box::pin(async move {
let expect = expect let expect = expect.await.map_err(|err| {
.await tracing::error!("Initialization of HTTP expect service error: {err:?}");
.map_err(|e| error!("Init http expect service error: {:?}", e))?; })?;
let upgrade = match upgrade { let upgrade = match upgrade {
Some(upgrade) => { Some(upgrade) => {
let upgrade = upgrade let upgrade = upgrade.await.map_err(|err| {
.await tracing::error!("Initialization of HTTP upgrade service error: {err:?}");
.map_err(|e| error!("Init http upgrade service error: {:?}", e))?; })?;
Some(upgrade) Some(upgrade)
} }
None => None, None => None,
@ -496,7 +496,7 @@ where
let service = service let service = service
.await .await
.map_err(|e| error!("Init http service error: {:?}", e))?; .map_err(|err| error!("Initialization of HTTP service error: {err:?}"))?;
Ok(H1ServiceHandler::new( Ok(H1ServiceHandler::new(
cfg, cfg,

View File

@ -1,6 +1,6 @@
use std::str::FromStr; use std::str::FromStr;
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use http::header::InvalidHeaderValue; use http::header::InvalidHeaderValue;
use crate::{ use crate::{
@ -11,7 +11,7 @@ use crate::{
/// Error returned when a content encoding is unknown. /// Error returned when a content encoding is unknown.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "unsupported content encoding")] #[display("unsupported content encoding")]
pub struct ContentEncodingParseError; pub struct ContentEncodingParseError;
/// Represents a supported content encoding. /// Represents a supported content encoding.

View File

@ -1,6 +1,6 @@
use std::fmt; use std::fmt;
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
const MAX_QUALITY_INT: u16 = 1000; const MAX_QUALITY_INT: u16 = 1000;
const MAX_QUALITY_FLOAT: f32 = 1.0; const MAX_QUALITY_FLOAT: f32 = 1.0;
@ -125,7 +125,7 @@ pub fn itoa_fmt<W: fmt::Write, V: itoa::Integer>(mut wr: W, value: V) -> fmt::Re
} }
#[derive(Debug, Clone, Display, Error)] #[derive(Debug, Clone, Display, Error)]
#[display(fmt = "quality out of bounds")] #[display("quality out of bounds")]
#[non_exhaustive] #[non_exhaustive]
pub struct QualityOutOfBounds; pub struct QualityOutOfBounds;

View File

@ -775,23 +775,23 @@ where
let cfg = self.cfg.clone(); let cfg = self.cfg.clone();
Box::pin(async move { Box::pin(async move {
let expect = expect let expect = expect.await.map_err(|err| {
.await tracing::error!("Initialization of HTTP expect service error: {err:?}");
.map_err(|e| error!("Init http expect service error: {:?}", e))?; })?;
let upgrade = match upgrade { let upgrade = match upgrade {
Some(upgrade) => { Some(upgrade) => {
let upgrade = upgrade let upgrade = upgrade.await.map_err(|err| {
.await tracing::error!("Initialization of HTTP upgrade service error: {err:?}");
.map_err(|e| error!("Init http upgrade service error: {:?}", e))?; })?;
Some(upgrade) Some(upgrade)
} }
None => None, None => None,
}; };
let service = service let service = service.await.map_err(|err| {
.await tracing::error!("Initialization of HTTP service error: {err:?}");
.map_err(|e| error!("Init http service error: {:?}", e))?; })?;
Ok(HttpServiceHandler::new( Ok(HttpServiceHandler::new(
cfg, cfg,

View File

@ -114,14 +114,14 @@ mod inner {
{ {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
DispatcherError::Service(ref e) => { DispatcherError::Service(ref err) => {
write!(fmt, "DispatcherError::Service({:?})", e) write!(fmt, "DispatcherError::Service({err:?})")
} }
DispatcherError::Encoder(ref e) => { DispatcherError::Encoder(ref err) => {
write!(fmt, "DispatcherError::Encoder({:?})", e) write!(fmt, "DispatcherError::Encoder({err:?})")
} }
DispatcherError::Decoder(ref e) => { DispatcherError::Decoder(ref err) => {
write!(fmt, "DispatcherError::Decoder({:?})", e) write!(fmt, "DispatcherError::Decoder({err:?})")
} }
} }
} }
@ -136,9 +136,9 @@ mod inner {
{ {
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result { fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
match *self { match *self {
DispatcherError::Service(ref e) => write!(fmt, "{}", e), DispatcherError::Service(ref err) => write!(fmt, "{err}"),
DispatcherError::Encoder(ref e) => write!(fmt, "{:?}", e), DispatcherError::Encoder(ref err) => write!(fmt, "{err:?}"),
DispatcherError::Decoder(ref e) => write!(fmt, "{:?}", e), DispatcherError::Decoder(ref err) => write!(fmt, "{err:?}"),
} }
} }
} }

View File

@ -5,7 +5,7 @@
use std::io; use std::io;
use derive_more::{Display, Error, From}; use derive_more::derive::{Display, Error, From};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder}; use crate::{body::BoxBody, header::HeaderValue, RequestHead, Response, ResponseBuilder};
@ -27,43 +27,43 @@ pub use self::{
#[derive(Debug, Display, Error, From)] #[derive(Debug, Display, Error, From)]
pub enum ProtocolError { pub enum ProtocolError {
/// Received an unmasked frame from client. /// Received an unmasked frame from client.
#[display(fmt = "received an unmasked frame from client")] #[display("received an unmasked frame from client")]
UnmaskedFrame, UnmaskedFrame,
/// Received a masked frame from server. /// Received a masked frame from server.
#[display(fmt = "received a masked frame from server")] #[display("received a masked frame from server")]
MaskedFrame, MaskedFrame,
/// Encountered invalid opcode. /// Encountered invalid opcode.
#[display(fmt = "invalid opcode ({})", _0)] #[display("invalid opcode ({})", _0)]
InvalidOpcode(#[error(not(source))] u8), InvalidOpcode(#[error(not(source))] u8),
/// Invalid control frame length /// Invalid control frame length
#[display(fmt = "invalid control frame length ({})", _0)] #[display("invalid control frame length ({})", _0)]
InvalidLength(#[error(not(source))] usize), InvalidLength(#[error(not(source))] usize),
/// Bad opcode. /// Bad opcode.
#[display(fmt = "bad opcode")] #[display("bad opcode")]
BadOpCode, BadOpCode,
/// A payload reached size limit. /// A payload reached size limit.
#[display(fmt = "payload reached size limit")] #[display("payload reached size limit")]
Overflow, Overflow,
/// Continuation has not started. /// Continuation has not started.
#[display(fmt = "continuation has not started")] #[display("continuation has not started")]
ContinuationNotStarted, ContinuationNotStarted,
/// Received new continuation but it is already started. /// Received new continuation but it is already started.
#[display(fmt = "received new continuation but it has already started")] #[display("received new continuation but it has already started")]
ContinuationStarted, ContinuationStarted,
/// Unknown continuation fragment. /// Unknown continuation fragment.
#[display(fmt = "unknown continuation fragment: {}", _0)] #[display("unknown continuation fragment: {}", _0)]
ContinuationFragment(#[error(not(source))] OpCode), ContinuationFragment(#[error(not(source))] OpCode),
/// I/O error. /// I/O error.
#[display(fmt = "I/O error: {}", _0)] #[display("I/O error: {}", _0)]
Io(io::Error), Io(io::Error),
} }
@ -71,27 +71,27 @@ pub enum ProtocolError {
#[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Display, Error)]
pub enum HandshakeError { pub enum HandshakeError {
/// Only get method is allowed. /// Only get method is allowed.
#[display(fmt = "method not allowed")] #[display("method not allowed")]
GetMethodRequired, GetMethodRequired,
/// Upgrade header if not set to WebSocket. /// Upgrade header if not set to WebSocket.
#[display(fmt = "WebSocket upgrade is expected")] #[display("WebSocket upgrade is expected")]
NoWebsocketUpgrade, NoWebsocketUpgrade,
/// Connection header is not set to upgrade. /// Connection header is not set to upgrade.
#[display(fmt = "connection upgrade is expected")] #[display("connection upgrade is expected")]
NoConnectionUpgrade, NoConnectionUpgrade,
/// WebSocket version header is not set. /// WebSocket version header is not set.
#[display(fmt = "WebSocket version header is required")] #[display("WebSocket version header is required")]
NoVersionHeader, NoVersionHeader,
/// Unsupported WebSocket version. /// Unsupported WebSocket version.
#[display(fmt = "unsupported WebSocket version")] #[display("unsupported WebSocket version")]
UnsupportedVersion, UnsupportedVersion,
/// WebSocket key is not set or wrong. /// WebSocket key is not set or wrong.
#[display(fmt = "unknown WebSocket key")] #[display("unknown WebSocket key")]
BadWebsocketKey, BadWebsocketKey,
} }

View File

@ -5,7 +5,7 @@ use actix_http_test::test_server;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
use actix_utils::future; use actix_utils::future;
use bytes::Bytes; use bytes::Bytes;
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_util::StreamExt as _; use futures_util::StreamExt as _;
const STR: &str = "Hello World Hello World Hello World Hello World Hello World \ const STR: &str = "Hello World Hello World Hello World Hello World Hello World \
@ -94,7 +94,7 @@ async fn with_query_parameter() {
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "expect failed")] #[display("expect failed")]
struct ExpectFailed; struct ExpectFailed;
impl From<ExpectFailed> for Response<BoxBody> { impl From<ExpectFailed> for Response<BoxBody> {

View File

@ -14,7 +14,7 @@ use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt}; use actix_service::{fn_service, ServiceFactoryExt};
use actix_utils::future::{err, ok, ready}; use actix_utils::future::{err, ok, ready};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_core::Stream; use futures_core::Stream;
use futures_util::{stream::once, StreamExt as _}; use futures_util::{stream::once, StreamExt as _};
use openssl::{ use openssl::{
@ -398,7 +398,7 @@ async fn h2_response_http_error_handling() {
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "error")] #[display("error")]
struct BadRequest; struct BadRequest;
impl From<BadRequest> for Response<BoxBody> { impl From<BadRequest> for Response<BoxBody> {

View File

@ -23,7 +23,7 @@ use actix_service::{fn_factory_with_config, fn_service};
use actix_tls::connect::rustls_0_23::webpki_roots_cert_store; use actix_tls::connect::rustls_0_23::webpki_roots_cert_store;
use actix_utils::future::{err, ok, poll_fn}; use actix_utils::future::{err, ok, poll_fn};
use bytes::{Bytes, BytesMut}; use bytes::{Bytes, BytesMut};
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_core::{ready, Stream}; use futures_core::{ready, Stream};
use futures_util::stream::once; use futures_util::stream::once;
use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig}; use rustls::{pki_types::ServerName, ServerConfig as RustlsServerConfig};
@ -480,7 +480,7 @@ async fn h2_response_http_error_handling() {
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "error")] #[display("error")]
struct BadRequest; struct BadRequest;
impl From<BadRequest> for Response<BoxBody> { impl From<BadRequest> for Response<BoxBody> {

View File

@ -14,7 +14,7 @@ use actix_rt::{net::TcpStream, time::sleep};
use actix_service::fn_service; use actix_service::fn_service;
use actix_utils::future::{err, ok, ready}; use actix_utils::future::{err, ok, ready};
use bytes::Bytes; use bytes::Bytes;
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_util::{stream::once, FutureExt as _, StreamExt as _}; use futures_util::{stream::once, FutureExt as _, StreamExt as _};
use regex::Regex; use regex::Regex;
@ -62,7 +62,7 @@ async fn h1_2() {
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "expect failed")] #[display("expect failed")]
struct ExpectFailed; struct ExpectFailed;
impl From<ExpectFailed> for Response<BoxBody> { impl From<ExpectFailed> for Response<BoxBody> {
@ -723,7 +723,7 @@ async fn h1_response_http_error_handling() {
} }
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "error")] #[display("error")]
struct BadRequest; struct BadRequest;
impl From<BadRequest> for Response<BoxBody> { impl From<BadRequest> for Response<BoxBody> {

View File

@ -14,7 +14,7 @@ use actix_http::{
use actix_http_test::test_server; use actix_http_test::test_server;
use actix_service::{fn_factory, Service}; use actix_service::{fn_factory, Service};
use bytes::Bytes; use bytes::Bytes;
use derive_more::{Display, Error, From}; use derive_more::derive::{Display, Error, From};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::{SinkExt as _, StreamExt as _}; use futures_util::{SinkExt as _, StreamExt as _};
@ -37,16 +37,16 @@ impl WsService {
#[derive(Debug, Display, Error, From)] #[derive(Debug, Display, Error, From)]
enum WsServiceError { enum WsServiceError {
#[display(fmt = "HTTP error")] #[display("HTTP error")]
Http(actix_http::Error), Http(actix_http::Error),
#[display(fmt = "WS handshake error")] #[display("WS handshake error")]
Ws(actix_http::ws::HandshakeError), Ws(actix_http::ws::HandshakeError),
#[display(fmt = "I/O error")] #[display("I/O error")]
Io(std::io::Error), Io(std::io::Error),
#[display(fmt = "dispatcher error")] #[display("dispatcher error")]
Dispatcher, Dispatcher,
} }

View File

@ -5,6 +5,7 @@
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
#![cfg_attr(docsrs, feature(doc_auto_cfg))] #![cfg_attr(docsrs, feature(doc_auto_cfg))]
#![allow(clippy::disallowed_names)] // false positives in some macro expansions
use std::collections::HashSet; use std::collections::HashSet;
@ -35,6 +36,7 @@ struct MultipartFormAttrs {
duplicate_field: DuplicateField, duplicate_field: DuplicateField,
} }
#[allow(clippy::disallowed_names)] // false positive in macro expansion
#[derive(FromField, Default)] #[derive(FromField, Default)]
#[darling(attributes(multipart), default)] #[darling(attributes(multipart), default)]
struct FieldAttrs { struct FieldAttrs {

View File

@ -2,6 +2,8 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.75.
## 0.7.2 ## 0.7.2
- Fix re-exported version of `actix-multipart-derive`. - Fix re-exported version of `actix-multipart-derive`.

View File

@ -42,7 +42,7 @@ actix-multipart-derive = { version = "=0.7.0", optional = true }
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4", default-features = false } actix-web = { version = "4", default-features = false }
derive_more = "0.99.5" derive_more = { version = "1", features = ["display", "error", "from"] }
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc"] }
httparse = "1.3" httparse = "1.3"

View File

@ -5,18 +5,18 @@ use actix_web::{
http::StatusCode, http::StatusCode,
ResponseError, ResponseError,
}; };
use derive_more::{Display, Error, From}; use derive_more::derive::{Display, Error, From};
/// A set of errors that can occur during parsing multipart streams. /// A set of errors that can occur during parsing multipart streams.
#[derive(Debug, Display, From, Error)] #[derive(Debug, Display, From, Error)]
#[non_exhaustive] #[non_exhaustive]
pub enum Error { pub enum Error {
/// Could not find Content-Type header. /// Could not find Content-Type header.
#[display(fmt = "Could not find Content-Type header")] #[display("Could not find Content-Type header")]
ContentTypeMissing, ContentTypeMissing,
/// Could not parse Content-Type header. /// Could not parse Content-Type header.
#[display(fmt = "Could not parse Content-Type header")] #[display("Could not parse Content-Type header")]
ContentTypeParse, ContentTypeParse,
/// Parsed Content-Type did not have "multipart" top-level media type. /// Parsed Content-Type did not have "multipart" top-level media type.
@ -25,11 +25,11 @@ pub enum Error {
/// "multipart/form-data" media type. /// "multipart/form-data" media type.
/// ///
/// [`MultipartForm`]: struct@crate::form::MultipartForm /// [`MultipartForm`]: struct@crate::form::MultipartForm
#[display(fmt = "Parsed Content-Type did not have "multipart" top-level media type")] #[display("Parsed Content-Type did not have 'multipart' top-level media type")]
ContentTypeIncompatible, ContentTypeIncompatible,
/// Multipart boundary is not found. /// Multipart boundary is not found.
#[display(fmt = "Multipart boundary is not found")] #[display("Multipart boundary is not found")]
BoundaryMissing, BoundaryMissing,
/// Content-Disposition header was not found or not of disposition type "form-data" when parsing /// Content-Disposition header was not found or not of disposition type "form-data" when parsing
@ -39,7 +39,7 @@ pub enum Error {
/// always be present and have a disposition type of "form-data". /// always be present and have a disposition type of "form-data".
/// ///
/// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2 /// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
#[display(fmt = "Content-Disposition header was not found when parsing a \"form-data\" field")] #[display("Content-Disposition header was not found when parsing a \"form-data\" field")]
ContentDispositionMissing, ContentDispositionMissing,
/// Content-Disposition name parameter was not found when parsing a "form-data" field. /// Content-Disposition name parameter was not found when parsing a "form-data" field.
@ -48,48 +48,48 @@ pub enum Error {
/// always include a "name" parameter. /// always include a "name" parameter.
/// ///
/// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2 /// [RFC 7578 §4.2]: https://datatracker.ietf.org/doc/html/rfc7578#section-4.2
#[display(fmt = "Content-Disposition header was not found when parsing a \"form-data\" field")] #[display("Content-Disposition header was not found when parsing a \"form-data\" field")]
ContentDispositionNameMissing, ContentDispositionNameMissing,
/// Nested multipart is not supported. /// Nested multipart is not supported.
#[display(fmt = "Nested multipart is not supported")] #[display("Nested multipart is not supported")]
Nested, Nested,
/// Multipart stream is incomplete. /// Multipart stream is incomplete.
#[display(fmt = "Multipart stream is incomplete")] #[display("Multipart stream is incomplete")]
Incomplete, Incomplete,
/// Field parsing failed. /// Field parsing failed.
#[display(fmt = "Error during field parsing")] #[display("Error during field parsing")]
Parse(ParseError), Parse(ParseError),
/// HTTP payload error. /// HTTP payload error.
#[display(fmt = "Payload error")] #[display("Payload error")]
Payload(PayloadError), Payload(PayloadError),
/// Stream is not consumed. /// Stream is not consumed.
#[display(fmt = "Stream is not consumed")] #[display("Stream is not consumed")]
NotConsumed, NotConsumed,
/// Form field handler raised error. /// Form field handler raised error.
#[display(fmt = "An error occurred processing field: {name}")] #[display("An error occurred processing field: {name}")]
Field { Field {
name: String, name: String,
source: actix_web::Error, source: actix_web::Error,
}, },
/// Duplicate field found (for structure that opted-in to denying duplicate fields). /// Duplicate field found (for structure that opted-in to denying duplicate fields).
#[display(fmt = "Duplicate field found: {_0}")] #[display("Duplicate field found: {_0}")]
#[from(ignore)] #[from(ignore)]
DuplicateField(#[error(not(source))] String), DuplicateField(#[error(not(source))] String),
/// Required field is missing. /// Required field is missing.
#[display(fmt = "Required field is missing: {_0}")] #[display("Required field is missing: {_0}")]
#[from(ignore)] #[from(ignore)]
MissingField(#[error(not(source))] String), MissingField(#[error(not(source))] String),
/// Unknown field (for structure that opted-in to denying unknown fields). /// Unknown field (for structure that opted-in to denying unknown fields).
#[display(fmt = "Unknown field: {_0}")] #[display("Unknown field: {_0}")]
#[from(ignore)] #[from(ignore)]
UnknownField(#[error(not(source))] String), UnknownField(#[error(not(source))] String),
} }

View File

@ -13,7 +13,7 @@ use actix_web::{
http::header::{self, ContentDisposition, HeaderMap}, http::header::{self, ContentDisposition, HeaderMap},
web::{Bytes, BytesMut}, web::{Bytes, BytesMut},
}; };
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_core::Stream; use futures_core::Stream;
use mime::Mime; use mime::Mime;
@ -25,7 +25,7 @@ use crate::{
/// Error type returned from [`Field::bytes()`] when field data is larger than limit. /// Error type returned from [`Field::bytes()`] when field data is larger than limit.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "size limit exceeded while collecting field data")] #[display("size limit exceeded while collecting field data")]
#[non_exhaustive] #[non_exhaustive]
pub struct LimitExceeded; pub struct LimitExceeded;

View File

@ -3,7 +3,7 @@
use std::sync::Arc; use std::sync::Arc;
use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError}; use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError};
use derive_more::{Deref, DerefMut, Display, Error}; use derive_more::derive::{Deref, DerefMut, Display, Error};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -66,11 +66,11 @@ where
#[non_exhaustive] #[non_exhaustive]
pub enum JsonFieldError { pub enum JsonFieldError {
/// Deserialize error. /// Deserialize error.
#[display(fmt = "Json deserialize error: {}", _0)] #[display("Json deserialize error: {}", _0)]
Deserialize(serde_json::Error), Deserialize(serde_json::Error),
/// Content type error. /// Content type error.
#[display(fmt = "Content type error")] #[display("Content type error")]
ContentType, ContentType,
} }

View File

@ -8,7 +8,7 @@ use std::{
}; };
use actix_web::{dev, error::PayloadError, web, Error, FromRequest, HttpRequest}; use actix_web::{dev, error::PayloadError, web, Error, FromRequest, HttpRequest};
use derive_more::{Deref, DerefMut}; use derive_more::derive::{Deref, DerefMut};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::{TryFutureExt as _, TryStreamExt as _}; use futures_util::{TryFutureExt as _, TryStreamExt as _};

View File

@ -7,7 +7,7 @@ use std::{
}; };
use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError}; use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError};
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use futures_util::TryStreamExt as _; use futures_util::TryStreamExt as _;
use mime::Mime; use mime::Mime;
@ -82,7 +82,7 @@ impl<'t> FieldReader<'t> for TempFile {
#[non_exhaustive] #[non_exhaustive]
pub enum TempFileError { pub enum TempFileError {
/// File I/O Error /// File I/O Error
#[display(fmt = "File I/O error: {}", _0)] #[display("File I/O error: {}", _0)]
FileIo(std::io::Error), FileIo(std::io::Error),
} }

View File

@ -3,7 +3,7 @@
use std::{str, sync::Arc}; use std::{str, sync::Arc};
use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError}; use actix_web::{http::StatusCode, web, Error, HttpRequest, ResponseError};
use derive_more::{Deref, DerefMut, Display, Error}; use derive_more::derive::{Deref, DerefMut, Display, Error};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
@ -77,15 +77,15 @@ where
#[non_exhaustive] #[non_exhaustive]
pub enum TextError { pub enum TextError {
/// UTF-8 decoding error. /// UTF-8 decoding error.
#[display(fmt = "UTF-8 decoding error: {}", _0)] #[display("UTF-8 decoding error: {}", _0)]
Utf8Error(str::Utf8Error), Utf8Error(str::Utf8Error),
/// Deserialize error. /// Deserialize error.
#[display(fmt = "Plain text deserialize error: {}", _0)] #[display("Plain text deserialize error: {}", _0)]
Deserialize(serde_plain::Error), Deserialize(serde_plain::Error),
/// Content type error. /// Content type error.
#[display(fmt = "Content type error")] #[display("Content type error")]
ContentType, ContentType,
} }

View File

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

View File

@ -143,9 +143,9 @@ impl<T: ResourcePath> Path<T> {
for (seg_name, val) in self.segments.iter() { for (seg_name, val) in self.segments.iter() {
if name == seg_name { if name == seg_name {
return match val { return match val {
PathItem::Static(ref s) => Some(s), PathItem::Static(ref seg) => Some(seg),
PathItem::Segment(s, e) => { PathItem::Segment(start, end) => {
Some(&self.path.path()[(*s as usize)..(*e as usize)]) Some(&self.path.path()[(*start as usize)..(*end as usize)])
} }
}; };
} }
@ -193,8 +193,10 @@ impl<'a, T: ResourcePath> Iterator for PathIter<'a, T> {
if self.idx < self.params.segment_count() { if self.idx < self.params.segment_count() {
let idx = self.idx; let idx = self.idx;
let res = match self.params.segments[idx].1 { let res = match self.params.segments[idx].1 {
PathItem::Static(ref s) => s, PathItem::Static(ref seg) => seg,
PathItem::Segment(s, e) => &self.params.path.path()[(s as usize)..(e as usize)], PathItem::Segment(start, end) => {
&self.params.path.path()[(start as usize)..(end as usize)]
}
}; };
self.idx += 1; self.idx += 1;
return Some((&self.params.segments[idx].0, res)); return Some((&self.params.segments[idx].0, res));
@ -217,8 +219,8 @@ impl<T: ResourcePath> Index<usize> for Path<T> {
fn index(&self, idx: usize) -> &str { fn index(&self, idx: usize) -> &str {
match self.segments[idx].1 { match self.segments[idx].1 {
PathItem::Static(ref s) => s, PathItem::Static(ref seg) => seg,
PathItem::Segment(s, e) => &self.path.path()[(s as usize)..(e as usize)], PathItem::Segment(start, end) => &self.path.path()[(start as usize)..(end as usize)],
} }
} }
} }

View File

@ -2,7 +2,10 @@
## Unreleased ## 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. - Minimum supported Rust version (MSRV) is now 1.72.
## 4.3.0 ## 4.3.0

View File

@ -1,13 +1,14 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "4.3.0" version = "4.3.1+deprecated"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web" description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
homepage = "https://actix.rs" homepage.workspace = true
repository = "https://github.com/actix/actix-web" repository.workspace = true
license = "MIT OR Apache-2.0" license.workspace = true
edition = "2021" edition.workspace = true
rust-version.workspace = true
[package.metadata.cargo_check_external_types] [package.metadata.cargo_check_external_types]
allowed_external_types = [ allowed_external_types = [

View File

@ -1,15 +1,17 @@
# `actix-web-actors` # `actix-web-actors`
> Actix actors support for Actix Web. > Actix actors support for Actix Web.
>
> This crate is deprecated. Migrate to [`actix-ws`](https://crates.io/crates/actix-ws).
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors) [![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) ![Version](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![License](https://img.shields.io/crates/l/actix-web-actors.svg) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <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) [![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) [![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. //! Actix actors support for Actix Web.
//! //!
//! This crate is deprecated. Migrate to [`actix-ws`](https://crates.io/crates/actix-ws).
//!
//! # Examples //! # Examples
//! //!
//! ```no_run //! ```no_run

View File

@ -796,11 +796,8 @@ where
Some(frm) => { Some(frm) => {
let msg = match frm { let msg = match frm {
Frame::Text(data) => { Frame::Text(data) => {
Message::Text(ByteString::try_from(data).map_err(|e| { Message::Text(ByteString::try_from(data).map_err(|err| {
ProtocolError::Io(io::Error::new( ProtocolError::Io(io::Error::new(io::ErrorKind::Other, err))
io::ErrorKind::Other,
format!("{}", e),
))
})?) })?)
} }
Frame::Binary(data) => Message::Binary(data), Frame::Binary(data) => Message::Binary(data),

View File

@ -2,6 +2,15 @@
## Unreleased ## Unreleased
- Minimum supported Rust version (MSRV) is now 1.75.
## 4.9.0
### Added
- Add `middleware::from_fn()` helper.
- Add `web::ThinData` extractor.
## 4.8.0 ## 4.8.0
### Added ### Added

View File

@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.8.0" version = "4.9.0"
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
@ -150,11 +150,12 @@ bytes = "1"
bytestring = "1" bytestring = "1"
cfg-if = "1" cfg-if = "1"
cookie = { version = "0.16", features = ["percent-encode"], optional = true } cookie = { version = "0.16", features = ["percent-encode"], optional = true }
derive_more = "0.99.8" derive_more = { version = "1", features = ["display", "error", "from"] }
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.17", default-features = false } futures-core = { version = "0.3.17", default-features = false }
futures-util = { version = "0.3.17", default-features = false } futures-util = { version = "0.3.17", default-features = false }
itoa = "1" itoa = "1"
impl-more = "0.1.4"
language-tags = "0.3" language-tags = "0.3"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
@ -186,7 +187,7 @@ futures-util = { version = "0.3.17", default-features = false, features = ["std"
rand = "0.8" rand = "0.8"
rcgen = "0.13" rcgen = "0.13"
rustls-pemfile = "2" rustls-pemfile = "2"
serde = { version = "1.0", features = ["derive"] } serde = { version = "1", features = ["derive"] }
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.55" } tls-openssl = { package = "openssl", version = "0.10.55" }
tls-rustls = { package = "rustls", version = "0.23" } tls-rustls = { package = "rustls", version = "0.23" }

View File

@ -8,10 +8,10 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.8.0)](https://docs.rs/actix-web/4.8.0) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.9.0)](https://docs.rs/actix-web/4.9.0)
![MSRV](https://img.shields.io/badge/rustc-1.72+-ab6000.svg) ![MSRV](https://img.shields.io/badge/rustc-1.72+-ab6000.svg)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.8.0/status.svg)](https://deps.rs/crate/actix-web/4.8.0) [![Dependency Status](https://deps.rs/crate/actix-web/4.9.0/status.svg)](https://deps.rs/crate/actix-web/4.9.0)
<br /> <br />
[![CI](https://github.com/actix/actix-web/actions/workflows/ci.yml/badge.svg)](https://github.com/actix/actix-web/actions/workflows/ci.yml) [![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) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View File

@ -2,11 +2,9 @@ use std::{future::Future, time::Instant};
use actix_http::body::BoxBody; use actix_http::body::BoxBody;
use actix_utils::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use actix_web::{ use actix_web::{http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder};
error, http::StatusCode, test::TestRequest, Error, HttpRequest, HttpResponse, Responder,
};
use criterion::{criterion_group, criterion_main, Criterion}; 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. // responder simulate the old responder trait.
trait FutureResponder { trait FutureResponder {
@ -16,9 +14,6 @@ trait FutureResponder {
fn future_respond_to(self, req: &HttpRequest) -> Self::Future; 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 // a simple wrapper type around string
struct StringResponder(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 { impl Responder for StringResponder {
type Body = BoxBody; 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) { fn future_responder(c: &mut Criterion) {
let rt = actix_rt::System::new(); let rt = actix_rt::System::new();
let req = TestRequest::default().to_http_request(); 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

@ -269,9 +269,9 @@ where
+ 'static, + 'static,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
let svc = svc let svc = svc.into_factory().map_init_err(|err| {
.into_factory() log::error!("Can not construct default service: {err:?}");
.map_init_err(|e| log::error!("Can not construct default service: {:?}", e)); });
self.default = Some(Rc::new(boxed::factory(svc))); self.default = Some(Rc::new(boxed::factory(svc)));

View File

@ -6,7 +6,7 @@
// //
// See <https://github.com/rust-lang/rust/issues/83375> // See <https://github.com/rust-lang/rust/issues/83375>
pub use actix_http::error::{ContentTypeError, DispatchError, HttpError, ParseError, PayloadError}; pub use actix_http::error::{ContentTypeError, DispatchError, HttpError, ParseError, PayloadError};
use derive_more::{Display, Error, From}; use derive_more::derive::{Display, Error, From};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
use serde_urlencoded::{de::Error as FormDeError, ser::Error as FormError}; use serde_urlencoded::{de::Error as FormDeError, ser::Error as FormError};
use url::ParseError as UrlParseError; use url::ParseError as UrlParseError;
@ -29,7 +29,7 @@ pub type Result<T, E = Error> = std::result::Result<T, E>;
/// An error representing a problem running a blocking task on a thread pool. /// An error representing a problem running a blocking task on a thread pool.
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[display(fmt = "Blocking thread pool is shut down unexpectedly")] #[display("Blocking thread pool is shut down unexpectedly")]
#[non_exhaustive] #[non_exhaustive]
pub struct BlockingError; pub struct BlockingError;
@ -40,15 +40,15 @@ impl ResponseError for crate::error::BlockingError {}
#[non_exhaustive] #[non_exhaustive]
pub enum UrlGenerationError { pub enum UrlGenerationError {
/// Resource not found. /// Resource not found.
#[display(fmt = "Resource not found")] #[display("Resource not found")]
ResourceNotFound, ResourceNotFound,
/// Not all URL parameters covered. /// Not all URL parameters covered.
#[display(fmt = "Not all URL parameters covered")] #[display("Not all URL parameters covered")]
NotEnoughElements, NotEnoughElements,
/// URL parse error. /// URL parse error.
#[display(fmt = "{}", _0)] #[display("{}", _0)]
ParseError(UrlParseError), ParseError(UrlParseError),
} }
@ -59,39 +59,39 @@ impl ResponseError for UrlGenerationError {}
#[non_exhaustive] #[non_exhaustive]
pub enum UrlencodedError { pub enum UrlencodedError {
/// Can not decode chunked transfer encoding. /// Can not decode chunked transfer encoding.
#[display(fmt = "Can not decode chunked transfer encoding.")] #[display("Can not decode chunked transfer encoding.")]
Chunked, Chunked,
/// Payload size is larger than allowed. (default limit: 256kB). /// Payload size is larger than allowed. (default limit: 256kB).
#[display( #[display(
fmt = "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).", "URL encoded payload is larger ({} bytes) than allowed (limit: {} bytes).",
size, size,
limit limit
)] )]
Overflow { size: usize, limit: usize }, Overflow { size: usize, limit: usize },
/// Payload size is now known. /// Payload size is now known.
#[display(fmt = "Payload size is now known.")] #[display("Payload size is now known.")]
UnknownLength, UnknownLength,
/// Content type error. /// Content type error.
#[display(fmt = "Content type error.")] #[display("Content type error.")]
ContentType, ContentType,
/// Parse error. /// Parse error.
#[display(fmt = "Parse error: {}.", _0)] #[display("Parse error: {}.", _0)]
Parse(FormDeError), Parse(FormDeError),
/// Encoding error. /// Encoding error.
#[display(fmt = "Encoding error.")] #[display("Encoding error.")]
Encoding, Encoding,
/// Serialize error. /// Serialize error.
#[display(fmt = "Serialize error: {}.", _0)] #[display("Serialize error: {}.", _0)]
Serialize(FormError), Serialize(FormError),
/// Payload error. /// Payload error.
#[display(fmt = "Error that occur during reading payload: {}.", _0)] #[display("Error that occur during reading payload: {}.", _0)]
Payload(PayloadError), Payload(PayloadError),
} }
@ -113,30 +113,30 @@ impl ResponseError for UrlencodedError {
pub enum JsonPayloadError { pub enum JsonPayloadError {
/// Payload size is bigger than allowed & content length header set. (default: 2MB) /// Payload size is bigger than allowed & content length header set. (default: 2MB)
#[display( #[display(
fmt = "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).", "JSON payload ({} bytes) is larger than allowed (limit: {} bytes).",
length, length,
limit limit
)] )]
OverflowKnownLength { length: usize, limit: usize }, OverflowKnownLength { length: usize, limit: usize },
/// Payload size is bigger than allowed but no content length header set. (default: 2MB) /// Payload size is bigger than allowed but no content length header set. (default: 2MB)
#[display(fmt = "JSON payload has exceeded limit ({} bytes).", limit)] #[display("JSON payload has exceeded limit ({} bytes).", limit)]
Overflow { limit: usize }, Overflow { limit: usize },
/// Content type error /// Content type error
#[display(fmt = "Content type error")] #[display("Content type error")]
ContentType, ContentType,
/// Deserialize error /// Deserialize error
#[display(fmt = "Json deserialize error: {}", _0)] #[display("Json deserialize error: {}", _0)]
Deserialize(JsonError), Deserialize(JsonError),
/// Serialize error /// Serialize error
#[display(fmt = "Json serialize error: {}", _0)] #[display("Json serialize error: {}", _0)]
Serialize(JsonError), Serialize(JsonError),
/// Payload error /// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)] #[display("Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(PayloadError),
} }
@ -166,7 +166,7 @@ impl ResponseError for JsonPayloadError {
#[non_exhaustive] #[non_exhaustive]
pub enum PathError { pub enum PathError {
/// Deserialize error /// Deserialize error
#[display(fmt = "Path deserialize error: {}", _0)] #[display("Path deserialize error: {}", _0)]
Deserialize(serde::de::value::Error), Deserialize(serde::de::value::Error),
} }
@ -182,7 +182,7 @@ impl ResponseError for PathError {
#[non_exhaustive] #[non_exhaustive]
pub enum QueryPayloadError { pub enum QueryPayloadError {
/// Query deserialize error. /// Query deserialize error.
#[display(fmt = "Query deserialize error: {}", _0)] #[display("Query deserialize error: {}", _0)]
Deserialize(serde::de::value::Error), Deserialize(serde::de::value::Error),
} }
@ -196,20 +196,20 @@ impl ResponseError for QueryPayloadError {
#[derive(Debug, Display, Error, From)] #[derive(Debug, Display, Error, From)]
#[non_exhaustive] #[non_exhaustive]
pub enum ReadlinesError { pub enum ReadlinesError {
#[display(fmt = "Encoding error")] #[display("Encoding error")]
/// Payload size is bigger than allowed. (default: 256kB) /// Payload size is bigger than allowed. (default: 256kB)
EncodingError, EncodingError,
/// Payload error. /// Payload error.
#[display(fmt = "Error that occur during reading payload: {}", _0)] #[display("Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(PayloadError),
/// Line limit exceeded. /// Line limit exceeded.
#[display(fmt = "Line limit exceeded")] #[display("Line limit exceeded")]
LimitOverflow, LimitOverflow,
/// ContentType error. /// ContentType error.
#[display(fmt = "Content-type error")] #[display("Content-type error")]
ContentTypeError(ContentTypeError), ContentTypeError(ContentTypeError),
} }

View File

@ -493,7 +493,7 @@ impl Header for ContentDisposition {
} }
fn parse<T: crate::HttpMessage>(msg: &T) -> Result<Self, crate::error::ParseError> { 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) Self::from_raw(h)
} else { } else {
Err(crate::error::ParseError::Header) Err(crate::error::ParseError::Header)

View File

@ -1,6 +1,6 @@
use std::{convert::Infallible, str}; use std::{convert::Infallible, str};
use derive_more::{Deref, DerefMut}; use derive_more::derive::{Deref, DerefMut};
use crate::{ use crate::{
error::ParseError, error::ParseError,

View File

@ -107,16 +107,16 @@ impl ByteRangeSpec {
/// satisfiable if they meet the following conditions: /// 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 /// > 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 /// > 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 /// > suffix-byte-range-spec with a non-zero suffix-length, then the byte-range-set is
/// is satisfiable. Otherwise, the byte-range-set is unsatisfiable. /// > satisfiable. Otherwise, the byte-range-set is unsatisfiable.
/// ///
/// The function also computes remainder ranges based on the RFC: /// 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 /// > 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 /// > 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 /// > 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). /// > that is one less than the current length of the selected representation).
/// ///
/// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233 /// [RFC 7233 §2.1]: https://datatracker.ietf.org/doc/html/rfc7233
pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> { pub fn to_satisfiable_range(&self, full_length: u64) -> Option<(u64, u64)> {
@ -270,7 +270,7 @@ impl Header for Range {
#[inline] #[inline]
fn parse<T: HttpMessage>(msg: &T) -> Result<Self, ParseError> { 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

@ -1,7 +1,7 @@
use std::{convert::Infallible, net::SocketAddr}; use std::{convert::Infallible, net::SocketAddr};
use actix_utils::future::{err, ok, Ready}; use actix_utils::future::{err, ok, Ready};
use derive_more::{Display, Error}; use derive_more::derive::{Display, Error};
use crate::{ use crate::{
dev::{AppConfig, Payload, RequestHead}, dev::{AppConfig, Payload, RequestHead},
@ -235,7 +235,7 @@ impl FromRequest for ConnectionInfo {
/// # let _svc = actix_web::web::to(handler); /// # let _svc = actix_web::web::to(handler);
/// ``` /// ```
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)] #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
#[display(fmt = "{}", _0)] #[display("{}", _0)]
pub struct PeerAddr(pub SocketAddr); pub struct PeerAddr(pub SocketAddr);
impl PeerAddr { impl PeerAddr {
@ -247,7 +247,7 @@ impl PeerAddr {
#[derive(Debug, Display, Error)] #[derive(Debug, Display, Error)]
#[non_exhaustive] #[non_exhaustive]
#[display(fmt = "Missing peer address")] #[display("Missing peer address")]
pub struct MissingPeerAddr; pub struct MissingPeerAddr;
impl ResponseError for MissingPeerAddr {} impl ResponseError for MissingPeerAddr {}

View File

@ -104,6 +104,7 @@ mod scope;
mod server; mod server;
mod service; mod service;
pub mod test; pub mod test;
mod thin_data;
pub(crate) mod types; pub(crate) mod types;
pub mod web; pub mod web;

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

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

View File

@ -15,10 +15,47 @@
//! - Access external services (e.g., [sessions](https://docs.rs/actix-session), etc.) //! - Access external services (e.g., [sessions](https://docs.rs/actix-session), etc.)
//! //!
//! Middleware is registered for each [`App`], [`Scope`](crate::Scope), or //! Middleware is registered for each [`App`], [`Scope`](crate::Scope), or
//! [`Resource`](crate::Resource) and executed in opposite order as registration. In general, a //! [`Resource`](crate::Resource) and executed in opposite order as registration.
//! 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 //! # Simple Middleware
//! can often be [an immediately-ready one](actix_utils::future::Ready). //!
//! 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 //! # Ordering
//! //!
@ -67,7 +104,7 @@
//! Response //! Response
//! ``` //! ```
//! The request _first_ gets processed by the middleware specified _last_ - `MiddlewareC`. It passes //! 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` //! 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 //! 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, //! [`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 //! [`Future`]: std::future::Future
//! [`App`]: crate::App //! [`App`]: crate::App
//! [`FromRequest`]: crate::FromRequest //! [`FromRequest`]: crate::FromRequest
@ -215,7 +240,7 @@
//! [`Transform`]: crate::dev::Transform //! [`Transform`]: crate::dev::Transform
//! [`call`]: crate::dev::Service::call() //! [`call`]: crate::dev::Service::call()
//! [`new_transform`]: crate::dev::Transform::new_transform() //! [`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; mod compat;
#[cfg(feature = "__compress")] #[cfg(feature = "__compress")]
@ -223,6 +248,7 @@ mod compress;
mod condition; mod condition;
mod default_headers; mod default_headers;
mod err_handlers; mod err_handlers;
mod from_fn;
mod identity; mod identity;
mod logger; mod logger;
mod normalize; mod normalize;
@ -234,6 +260,7 @@ pub use self::{
condition::Condition, condition::Condition,
default_headers::DefaultHeaders, default_headers::DefaultHeaders,
err_handlers::{ErrorHandlerResponse, ErrorHandlers}, err_handlers::{ErrorHandlerResponse, ErrorHandlers},
from_fn::{from_fn, Next},
identity::Identity, identity::Identity,
logger::Logger, logger::Logger,
normalize::{NormalizePath, TrailingSlash}, normalize::{NormalizePath, TrailingSlash},

View File

@ -358,10 +358,9 @@ where
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
{ {
// create and configure default resource // create and configure default resource
self.default = boxed::factory( self.default = boxed::factory(f.into_factory().map_init_err(|err| {
f.into_factory() log::error!("Can not construct default service: {err:?}");
.map_init_err(|e| log::error!("Can not construct default service: {:?}", e)), }));
);
self self
} }

View File

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

View File

@ -278,7 +278,9 @@ where
{ {
// create and configure default resource // create and configure default resource
self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err( self.default = Some(Rc::new(boxed::factory(f.into_factory().map_init_err(
|e| log::error!("Can not construct default service: {:?}", e), |err| {
log::error!("Can not construct default service: {err:?}");
},
)))); ))));
self self

View File

@ -1,6 +1,6 @@
//! Various helpers for Actix applications to use during testing. //! Various helpers for Actix applications to use during testing.
//! //!
//! # Creating A Test Service //! # Initializing A Test Service
//! - [`init_service`] //! - [`init_service`]
//! //!
//! # Off-The-Shelf Test Services //! # Off-The-Shelf Test Services
@ -49,6 +49,7 @@ pub use self::{
/// Must be used inside an async test. Works for both `ServiceRequest` and `HttpRequest`. /// Must be used inside an async test. Works for both `ServiceRequest` and `HttpRequest`.
/// ///
/// # Examples /// # Examples
///
/// ``` /// ```
/// use actix_web::{http::StatusCode, HttpResponse}; /// use actix_web::{http::StatusCode, HttpResponse};
/// ///

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

@ -400,7 +400,7 @@ impl<T: DeserializeOwned> JsonBody<T> {
_res: PhantomData, _res: PhantomData,
} }
} }
JsonBody::Error(e) => JsonBody::Error(e), JsonBody::Error(err) => JsonBody::Error(err),
} }
} }
} }
@ -453,7 +453,7 @@ impl<T: DeserializeOwned> Future for JsonBody<T> {
} }
} }
}, },
JsonBody::Error(e) => Poll::Ready(Err(e.take().unwrap())), JsonBody::Error(err) => Poll::Ready(Err(err.take().unwrap())),
} }
} }
} }

View File

@ -4,7 +4,7 @@ use std::sync::Arc;
use actix_router::PathDeserializer; use actix_router::PathDeserializer;
use actix_utils::future::{ready, Ready}; use actix_utils::future::{ready, Ready};
use derive_more::{AsRef, Deref, DerefMut, Display, From}; use derive_more::derive::{AsRef, Deref, DerefMut, Display, From};
use serde::de; use serde::de;
use crate::{ use crate::{
@ -89,8 +89,8 @@ where
); );
if let Some(error_handler) = error_handler { if let Some(error_handler) = error_handler {
let e = PathError::Deserialize(err); let err = PathError::Deserialize(err);
(error_handler)(e, req) (error_handler)(err, req)
} else { } else {
ErrorNotFound(err) ErrorNotFound(err)
} }
@ -152,14 +152,14 @@ impl PathConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_router::ResourceDef; use actix_router::ResourceDef;
use derive_more::Display; use derive_more::derive::Display;
use serde::Deserialize; use serde::Deserialize;
use super::*; use super::*;
use crate::{error, http, test::TestRequest, HttpResponse}; use crate::{error, http, test::TestRequest, HttpResponse};
#[derive(Deserialize, Debug, Display)] #[derive(Deserialize, Debug, Display)]
#[display(fmt = "MyStruct({}, {})", key, value)] #[display("MyStruct({}, {})", key, value)]
struct MyStruct { struct MyStruct {
key: String, key: String,
value: String, value: String,

View File

@ -2,7 +2,7 @@
use std::{fmt, ops, sync::Arc}; use std::{fmt, ops, sync::Arc};
use actix_utils::future::{err, ok, Ready}; use actix_utils::future::{ok, ready, Ready};
use serde::de::DeserializeOwned; use serde::de::DeserializeOwned;
#[cfg(feature = "beautify-errors")] #[cfg(feature = "beautify-errors")]
use url::form_urlencoded::parse; use url::form_urlencoded::parse;
@ -138,8 +138,8 @@ impl<T: DeserializeOwned> FromRequest for Query<T> {
serde_urlencoded::from_str::<T>(req.query_string()) serde_urlencoded::from_str::<T>(req.query_string())
.map(|val| ok(Query(val))) .map(|val| ok(Query(val)))
.unwrap_or_else(move |e| { .unwrap_or_else(move |err| {
let e = QueryPayloadError::Deserialize(e); let err = QueryPayloadError::Deserialize(err);
log::debug!( log::debug!(
"Failed during Query extractor deserialization. \ "Failed during Query extractor deserialization. \
@ -147,13 +147,13 @@ impl<T: DeserializeOwned> FromRequest for Query<T> {
req.path() req.path()
); );
let e = if let Some(error_handler) = error_handler { let err = if let Some(error_handler) = error_handler {
(error_handler)(e, req) (error_handler)(err, req)
} else { } else {
e.into() err.into()
}; };
err(e) ready(Err(err))
}) })
} }
@ -242,7 +242,7 @@ impl QueryConfig {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use actix_http::StatusCode; use actix_http::StatusCode;
use derive_more::Display; use derive_more::derive::Display;
use serde::Deserialize; use serde::Deserialize;
use super::*; use super::*;

View File

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

View File

@ -2,11 +2,18 @@
## Unreleased ## Unreleased
- Prevent panics on connection pool drop when Tokio runtime is shutdown early.
- Minimum supported Rust version (MSRV) is now 1.75.
## 3.5.1
- Fix WebSocket `Host` request header value when using a non-default port.
## 3.5.0 ## 3.5.0
- Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features. - Add `rustls-0_23`, `rustls-0_23-webpki-roots`, and `rustls-0_23-native-roots` crate features.
- Add `awc::Connector::rustls_0_23()` constructor. - Add `awc::Connector::rustls_0_23()` constructor.
- Fix `rustls-0_22-native-roots` root store lookup - Fix `rustls-0_22-native-roots` root store lookup.
- Update `brotli` dependency to `6`. - Update `brotli` dependency to `6`.
- Minimum supported Rust version (MSRV) is now 1.72. - Minimum supported Rust version (MSRV) is now 1.72.

View File

@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "3.5.0" version = "3.5.1"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Async HTTP and WebSocket client library" description = "Async HTTP and WebSocket client library"
keywords = ["actix", "http", "framework", "async", "web"] keywords = ["actix", "http", "framework", "async", "web"]
@ -106,7 +106,7 @@ actix-utils = "3"
base64 = "0.22" base64 = "0.22"
bytes = "1" bytes = "1"
cfg-if = "1" cfg-if = "1"
derive_more = "0.99.5" derive_more = { version = "1", features = ["display", "error", "from"] }
futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.17", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] } futures-util = { version = "0.3.17", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.26" h2 = "0.3.26"

View File

@ -5,9 +5,9 @@
<!-- prettier-ignore-start --> <!-- prettier-ignore-start -->
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc) [![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.5.0)](https://docs.rs/awc/3.5.0) [![Documentation](https://docs.rs/awc/badge.svg?version=3.5.1)](https://docs.rs/awc/3.5.1)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc) ![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.5.0/status.svg)](https://deps.rs/crate/awc/3.5.0) [![Dependency Status](https://deps.rs/crate/awc/3.5.1/status.svg)](https://deps.rs/crate/awc/3.5.1)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x) [![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
<!-- prettier-ignore-end --> <!-- prettier-ignore-end -->

View File

@ -3,7 +3,7 @@ use std::{fmt, io};
use actix_http::error::{HttpError, ParseError}; use actix_http::error::{HttpError, ParseError};
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
use actix_tls::accept::openssl::reexports::Error as OpensslError; use actix_tls::accept::openssl::reexports::Error as OpensslError;
use derive_more::{Display, From}; use derive_more::derive::{Display, From};
use crate::BoxError; use crate::BoxError;
@ -12,40 +12,40 @@ use crate::BoxError;
#[non_exhaustive] #[non_exhaustive]
pub enum ConnectError { pub enum ConnectError {
/// SSL feature is not enabled /// SSL feature is not enabled
#[display(fmt = "SSL is not supported")] #[display("SSL is not supported")]
SslIsNotSupported, SslIsNotSupported,
/// SSL error /// SSL error
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
#[display(fmt = "{}", _0)] #[display("{}", _0)]
SslError(OpensslError), SslError(OpensslError),
/// Failed to resolve the hostname /// Failed to resolve the hostname
#[display(fmt = "Failed resolving hostname: {}", _0)] #[display("Failed resolving hostname: {}", _0)]
Resolver(Box<dyn std::error::Error>), Resolver(Box<dyn std::error::Error>),
/// No dns records /// No dns records
#[display(fmt = "No DNS records found for the input")] #[display("No DNS records found for the input")]
NoRecords, NoRecords,
/// Http2 error /// Http2 error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
H2(h2::Error), H2(h2::Error),
/// Connecting took too long /// Connecting took too long
#[display(fmt = "Timeout while establishing connection")] #[display("Timeout while establishing connection")]
Timeout, Timeout,
/// Connector has been disconnected /// Connector has been disconnected
#[display(fmt = "Internal error: connector has been disconnected")] #[display("Internal error: connector has been disconnected")]
Disconnected, Disconnected,
/// Unresolved host name /// Unresolved host name
#[display(fmt = "Connector received `Connect` method with unresolved host")] #[display("Connector received `Connect` method with unresolved host")]
Unresolved, Unresolved,
/// Connection io error /// Connection io error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
Io(io::Error), Io(io::Error),
} }
@ -54,11 +54,11 @@ impl std::error::Error for ConnectError {}
impl From<actix_tls::connect::ConnectError> for ConnectError { impl From<actix_tls::connect::ConnectError> for ConnectError {
fn from(err: actix_tls::connect::ConnectError) -> ConnectError { fn from(err: actix_tls::connect::ConnectError) -> ConnectError {
match err { match err {
actix_tls::connect::ConnectError::Resolver(e) => ConnectError::Resolver(e), actix_tls::connect::ConnectError::Resolver(err) => ConnectError::Resolver(err),
actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords, actix_tls::connect::ConnectError::NoRecords => ConnectError::NoRecords,
actix_tls::connect::ConnectError::InvalidInput => panic!(), actix_tls::connect::ConnectError::InvalidInput => panic!(),
actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved, actix_tls::connect::ConnectError::Unresolved => ConnectError::Unresolved,
actix_tls::connect::ConnectError::Io(e) => ConnectError::Io(e), actix_tls::connect::ConnectError::Io(err) => ConnectError::Io(err),
} }
} }
} }
@ -66,16 +66,16 @@ impl From<actix_tls::connect::ConnectError> for ConnectError {
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
#[non_exhaustive] #[non_exhaustive]
pub enum InvalidUrl { pub enum InvalidUrl {
#[display(fmt = "Missing URL scheme")] #[display("Missing URL scheme")]
MissingScheme, MissingScheme,
#[display(fmt = "Unknown URL scheme")] #[display("Unknown URL scheme")]
UnknownScheme, UnknownScheme,
#[display(fmt = "Missing host name")] #[display("Missing host name")]
MissingHost, MissingHost,
#[display(fmt = "URL parse error: {}", _0)] #[display("URL parse error: {}", _0)]
HttpError(http::Error), HttpError(http::Error),
} }
@ -86,11 +86,11 @@ impl std::error::Error for InvalidUrl {}
#[non_exhaustive] #[non_exhaustive]
pub enum SendRequestError { pub enum SendRequestError {
/// Invalid URL /// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)] #[display("Invalid URL: {}", _0)]
Url(InvalidUrl), Url(InvalidUrl),
/// Failed to connect to host /// Failed to connect to host
#[display(fmt = "Failed to connect to host: {}", _0)] #[display("Failed to connect to host: {}", _0)]
Connect(ConnectError), Connect(ConnectError),
/// Error sending request /// Error sending request
@ -100,26 +100,26 @@ pub enum SendRequestError {
Response(ParseError), Response(ParseError),
/// Http error /// Http error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
Http(HttpError), Http(HttpError),
/// Http2 error /// Http2 error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
H2(h2::Error), H2(h2::Error),
/// Response took too long /// Response took too long
#[display(fmt = "Timeout while waiting for response")] #[display("Timeout while waiting for response")]
Timeout, Timeout,
/// Tunnels are not supported for HTTP/2 connection /// Tunnels are not supported for HTTP/2 connection
#[display(fmt = "Tunnels are not supported for http2 connection")] #[display("Tunnels are not supported for http2 connection")]
TunnelNotSupported, TunnelNotSupported,
/// Error sending request body /// Error sending request body
Body(BoxError), Body(BoxError),
/// Other errors that can occur after submitting a request. /// Other errors that can occur after submitting a request.
#[display(fmt = "{:?}: {}", _1, _0)] #[display("{:?}: {}", _1, _0)]
Custom(BoxError, Box<dyn fmt::Debug>), Custom(BoxError, Box<dyn fmt::Debug>),
} }
@ -130,15 +130,15 @@ impl std::error::Error for SendRequestError {}
#[non_exhaustive] #[non_exhaustive]
pub enum FreezeRequestError { pub enum FreezeRequestError {
/// Invalid URL /// Invalid URL
#[display(fmt = "Invalid URL: {}", _0)] #[display("Invalid URL: {}", _0)]
Url(InvalidUrl), Url(InvalidUrl),
/// HTTP error /// HTTP error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
Http(HttpError), Http(HttpError),
/// Other errors that can occur after submitting a request. /// Other errors that can occur after submitting a request.
#[display(fmt = "{:?}: {}", _1, _0)] #[display("{:?}: {}", _1, _0)]
Custom(BoxError, Box<dyn fmt::Debug>), Custom(BoxError, Box<dyn fmt::Debug>),
} }

View File

@ -31,7 +31,7 @@ use super::{
Connect, Connect,
}; };
#[derive(Hash, Eq, PartialEq, Clone, Debug)] #[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct Key { pub struct Key {
authority: Authority, authority: Authority,
} }
@ -42,8 +42,8 @@ impl From<Authority> for Key {
} }
} }
/// Connections pool to reuse I/O per [`Authority`].
#[doc(hidden)] #[doc(hidden)]
/// Connections pool for reuse Io type for certain [`http::uri::Authority`] as key.
pub struct ConnectionPool<S, Io> pub struct ConnectionPool<S, Io>
where where
Io: AsyncWrite + Unpin + 'static, Io: AsyncWrite + Unpin + 'static,
@ -52,7 +52,7 @@ where
inner: ConnectionPoolInner<Io>, inner: ConnectionPoolInner<Io>,
} }
/// wrapper type for check the ref count of Rc. /// Wrapper type for check the ref count of Rc.
pub struct ConnectionPoolInner<Io>(Rc<ConnectionPoolInnerPriv<Io>>) pub struct ConnectionPoolInner<Io>(Rc<ConnectionPoolInnerPriv<Io>>)
where where
Io: AsyncWrite + Unpin + 'static; Io: AsyncWrite + Unpin + 'static;
@ -63,7 +63,7 @@ where
{ {
fn new(config: ConnectorConfig) -> Self { fn new(config: ConnectorConfig) -> Self {
let permits = Arc::new(Semaphore::new(config.limit)); let permits = Arc::new(Semaphore::new(config.limit));
let available = RefCell::new(HashMap::default()); let available = RefCell::new(HashMap::new());
Self(Rc::new(ConnectionPoolInnerPriv { Self(Rc::new(ConnectionPoolInnerPriv {
config, config,
@ -72,15 +72,17 @@ where
})) }))
} }
/// spawn a async for graceful shutdown h1 Io type with a timeout. /// Spawns a graceful shutdown task for the underlying I/O with a timeout.
fn close(&self, conn: ConnectionInnerType<Io>) { fn close(&self, conn: ConnectionInnerType<Io>) {
if let Some(timeout) = self.config.disconnect_timeout { if let Some(timeout) = self.config.disconnect_timeout {
if let ConnectionInnerType::H1(io) = conn { if let ConnectionInnerType::H1(io) = conn {
if tokio::runtime::Handle::try_current().is_ok() {
actix_rt::spawn(CloseConnection::new(io, timeout)); actix_rt::spawn(CloseConnection::new(io, timeout));
} }
} }
} }
} }
}
impl<Io> Clone for ConnectionPoolInner<Io> impl<Io> Clone for ConnectionPoolInner<Io>
where where

View File

@ -7,7 +7,7 @@ pub use actix_http::{
ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError}, ws::{HandshakeError as WsHandshakeError, ProtocolError as WsProtocolError},
StatusCode, StatusCode,
}; };
use derive_more::{Display, From}; use derive_more::derive::{Display, From};
use serde_json::error::Error as JsonError; use serde_json::error::Error as JsonError;
pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError}; pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendRequestError};
@ -18,35 +18,35 @@ pub use crate::client::{ConnectError, FreezeRequestError, InvalidUrl, SendReques
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
pub enum WsClientError { pub enum WsClientError {
/// Invalid response status /// Invalid response status
#[display(fmt = "Invalid response status")] #[display("Invalid response status")]
InvalidResponseStatus(StatusCode), InvalidResponseStatus(StatusCode),
/// Invalid upgrade header /// Invalid upgrade header
#[display(fmt = "Invalid upgrade header")] #[display("Invalid upgrade header")]
InvalidUpgradeHeader, InvalidUpgradeHeader,
/// Invalid connection header /// Invalid connection header
#[display(fmt = "Invalid connection header")] #[display("Invalid connection header")]
InvalidConnectionHeader(HeaderValue), InvalidConnectionHeader(HeaderValue),
/// Missing Connection header /// Missing Connection header
#[display(fmt = "Missing Connection header")] #[display("Missing Connection header")]
MissingConnectionHeader, MissingConnectionHeader,
/// Missing Sec-Websocket-Accept header /// Missing Sec-Websocket-Accept header
#[display(fmt = "Missing Sec-Websocket-Accept header")] #[display("Missing Sec-Websocket-Accept header")]
MissingWebSocketAcceptHeader, MissingWebSocketAcceptHeader,
/// Invalid challenge response /// Invalid challenge response
#[display(fmt = "Invalid challenge response")] #[display("Invalid challenge response")]
InvalidChallengeResponse([u8; 28], HeaderValue), InvalidChallengeResponse([u8; 28], HeaderValue),
/// Protocol error /// Protocol error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
Protocol(WsProtocolError), Protocol(WsProtocolError),
/// Send request error /// Send request error
#[display(fmt = "{}", _0)] #[display("{}", _0)]
SendRequest(SendRequestError), SendRequest(SendRequestError),
} }
@ -68,13 +68,13 @@ impl From<HttpError> for WsClientError {
#[derive(Debug, Display, From)] #[derive(Debug, Display, From)]
pub enum JsonPayloadError { pub enum JsonPayloadError {
/// Content type error /// Content type error
#[display(fmt = "Content type error")] #[display("Content type error")]
ContentType, ContentType,
/// Deserialize error /// Deserialize error
#[display(fmt = "Json deserialize error: {}", _0)] #[display("Json deserialize error: {}", _0)]
Deserialize(JsonError), Deserialize(JsonError),
/// Payload error /// Payload error
#[display(fmt = "Error that occur during reading payload: {}", _0)] #[display("Error that occur during reading payload: {}", _0)]
Payload(PayloadError), Payload(PayloadError),
} }

View File

@ -147,8 +147,8 @@ impl FrozenSendBuilder {
/// Complete request construction and send a body. /// Complete request construction and send a body.
pub fn send_body(self, body: impl MessageBody + 'static) -> SendClientRequest { pub fn send_body(self, body: impl MessageBody + 'static) -> SendClientRequest {
if let Some(e) = self.err { if let Some(err) = self.err {
return e.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_body( RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_body(
@ -177,8 +177,8 @@ impl FrozenSendBuilder {
/// Complete request construction and send an urlencoded body. /// Complete request construction and send an urlencoded body.
pub fn send_form(self, value: impl Serialize) -> SendClientRequest { pub fn send_form(self, value: impl Serialize) -> SendClientRequest {
if let Some(e) = self.err { if let Some(err) = self.err {
return e.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_form( RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_form(
@ -196,8 +196,8 @@ impl FrozenSendBuilder {
S: Stream<Item = Result<Bytes, E>> + 'static, S: Stream<Item = Result<Bytes, E>> + 'static,
E: Into<BoxError> + 'static, E: Into<BoxError> + 'static,
{ {
if let Some(e) = self.err { if let Some(err) = self.err {
return e.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_stream( RequestSender::Rc(self.req.head, Some(self.extra_headers)).send_stream(
@ -211,8 +211,8 @@ impl FrozenSendBuilder {
/// Complete request construction and send an empty body. /// Complete request construction and send an empty body.
pub fn send(self) -> SendClientRequest { pub fn send(self) -> SendClientRequest {
if let Some(e) = self.err { if let Some(err) = self.err {
return e.into(); return err.into();
} }
RequestSender::Rc(self.req.head, Some(self.extra_headers)).send( RequestSender::Rc(self.req.head, Some(self.extra_headers)).send(

View File

@ -415,8 +415,8 @@ impl ClientRequest {
// allow unused mut when cookies feature is disabled // allow unused mut when cookies feature is disabled
fn prep_for_sending(#[allow(unused_mut)] mut self) -> Result<Self, PrepForSendingError> { fn prep_for_sending(#[allow(unused_mut)] mut self) -> Result<Self, PrepForSendingError> {
if let Some(e) = self.err { if let Some(err) = self.err {
return Err(e.into()); return Err(err.into());
} }
// validate uri // validate uri

View File

@ -17,7 +17,7 @@ use actix_http::{
use actix_http::{encoding::Decoder, header::ContentEncoding, Payload}; use actix_http::{encoding::Decoder, header::ContentEncoding, Payload};
use actix_rt::time::{sleep, Sleep}; use actix_rt::time::{sleep, Sleep};
use bytes::Bytes; use bytes::Bytes;
use derive_more::From; use derive_more::derive::From;
use futures_core::Stream; use futures_core::Stream;
use serde::Serialize; use serde::Serialize;
@ -54,8 +54,8 @@ impl From<PrepForSendingError> for FreezeRequestError {
impl From<PrepForSendingError> for SendRequestError { impl From<PrepForSendingError> for SendRequestError {
fn from(err: PrepForSendingError) -> SendRequestError { fn from(err: PrepForSendingError) -> SendRequestError {
match err { match err {
PrepForSendingError::Url(e) => SendRequestError::Url(e), PrepForSendingError::Url(err) => SendRequestError::Url(err),
PrepForSendingError::Http(e) => SendRequestError::Http(e), PrepForSendingError::Http(err) => SendRequestError::Http(err),
PrepForSendingError::Json(err) => { PrepForSendingError::Json(err) => {
SendRequestError::Custom(Box::new(err), Box::new("json serialization error")) SendRequestError::Custom(Box::new(err), Box::new("json serialization error"))
} }
@ -156,20 +156,20 @@ impl Future for SendClientRequest {
} }
impl From<SendRequestError> for SendClientRequest { impl From<SendRequestError> for SendClientRequest {
fn from(e: SendRequestError) -> Self { fn from(err: SendRequestError) -> Self {
SendClientRequest::Err(Some(e)) SendClientRequest::Err(Some(err))
} }
} }
impl From<HttpError> for SendClientRequest { impl From<HttpError> for SendClientRequest {
fn from(e: HttpError) -> Self { fn from(err: HttpError) -> Self {
SendClientRequest::Err(Some(e.into())) SendClientRequest::Err(Some(err.into()))
} }
} }
impl From<PrepForSendingError> for SendClientRequest { impl From<PrepForSendingError> for SendClientRequest {
fn from(e: PrepForSendingError) -> Self { fn from(err: PrepForSendingError) -> Self {
SendClientRequest::Err(Some(e.into())) SendClientRequest::Err(Some(err.into()))
} }
} }

View File

@ -253,12 +253,13 @@ impl WebsocketsRequest {
pub async fn connect( pub async fn connect(
mut self, mut self,
) -> Result<(ClientResponse, Framed<BoxedSocket, Codec>), WsClientError> { ) -> Result<(ClientResponse, Framed<BoxedSocket, Codec>), WsClientError> {
if let Some(e) = self.err.take() { if let Some(err) = self.err.take() {
return Err(e.into()); return Err(err.into());
} }
// validate uri // validate URI
let uri = &self.head.uri; let uri = &self.head.uri;
if uri.host().is_none() { if uri.host().is_none() {
return Err(InvalidUrl::MissingHost.into()); return Err(InvalidUrl::MissingHost.into());
} else if uri.scheme().is_none() { } else if uri.scheme().is_none() {
@ -273,9 +274,12 @@ impl WebsocketsRequest {
} }
if !self.head.headers.contains_key(header::HOST) { if !self.head.headers.contains_key(header::HOST) {
let hostname = uri.host().unwrap();
let port = uri.port();
self.head.headers.insert( self.head.headers.insert(
header::HOST, header::HOST,
HeaderValue::from_str(uri.host().unwrap()).unwrap(), HeaderValue::from_str(&Host { hostname, port }.to_string()).unwrap(),
); );
} }
@ -434,6 +438,25 @@ impl fmt::Debug for WebsocketsRequest {
} }
} }
/// Formatter for host (hostname+port) header values.
struct Host<'a> {
hostname: &'a str,
port: Option<http::uri::Port<&'a str>>,
}
impl<'a> fmt::Display for Host<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(self.hostname)?;
if let Some(port) = &self.port {
f.write_str(":")?;
f.write_str(port.as_str())?;
}
Ok(())
}
}
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;