mirror of
https://github.com/actix/actix-web.git
synced 2024-12-16 21:26:34 +00:00
Merge branch 'master' into feat/field-in-deserialize-error
This commit is contained in:
commit
b47f65063b
88 changed files with 1049 additions and 401 deletions
7
.clippy.toml
Normal file
7
.clippy.toml
Normal file
|
@ -0,0 +1,7 @@
|
||||||
|
disallowed-names = [
|
||||||
|
"e", # no single letter error bindings
|
||||||
|
]
|
||||||
|
disallowed-methods = [
|
||||||
|
"std::cell::RefCell::default()",
|
||||||
|
"std::rc::Rc::default()",
|
||||||
|
]
|
4
.github/workflows/ci-post-merge.yml
vendored
4
.github/workflows/ci-post-merge.yml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
4
.github/workflows/ci.yml
vendored
4
.github/workflows/ci.yml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
2
.github/workflows/coverage.yml
vendored
2
.github/workflows/coverage.yml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
4
.github/workflows/lint.yml
vendored
4
.github/workflows/lint.yml
vendored
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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)]
|
||||||
|
|
|
@ -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))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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!"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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!")));
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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));
|
||||||
|
|
||||||
|
|
|
@ -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]
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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:?}"),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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> {
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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`.
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 _};
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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)],
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 = [
|
||||||
|
|
|
@ -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)
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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" }
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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();
|
||||||
|
|
127
actix-web/examples/middleware_from_fn.rs
Normal file
127
actix-web/examples/middleware_from_fn.rs
Normal 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
|
||||||
|
}
|
|
@ -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)));
|
||||||
|
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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 {}
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
349
actix-web/src/middleware/from_fn.rs
Normal file
349
actix-web/src/middleware/from_fn.rs
Normal 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));
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 {
|
||||||
"-"
|
"-"
|
||||||
};
|
};
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"]"#);
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
121
actix-web/src/thin_data.rs
Normal 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);
|
||||||
|
}
|
||||||
|
}
|
|
@ -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())),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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.
|
||||||
|
|
||||||
|
|
|
@ -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"
|
||||||
|
|
|
@ -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 -->
|
||||||
|
|
|
@ -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>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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(
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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::*;
|
||||||
|
|
Loading…
Reference in a new issue