mirror of https://github.com/fafhrd91/actix-web
Merge branch 'master' into fix/app_data
This commit is contained in:
commit
511537e53f
|
@ -3,6 +3,7 @@ chk = "check --workspace --all-features --tests --examples --bins"
|
|||
lint = "clippy --workspace --tests --examples"
|
||||
ci-min = "hack check --workspace --no-default-features"
|
||||
ci-min-test = "hack check --workspace --no-default-features --tests --examples"
|
||||
ci-default = "hack check --workspace"
|
||||
ci-full = "check --workspace --bins --examples --tests"
|
||||
ci-test = "test --workspace --all-features --no-fail-fast"
|
||||
ci-default = "check --workspace --bins --tests --examples"
|
||||
ci-full = "check --workspace --all-features --bins --tests --examples"
|
||||
ci-test = "test --workspace --all-features --lib --tests --no-fail-fast -- --nocapture"
|
||||
ci-doctest = "hack test --workspace --all-features --doc --no-fail-fast -- --nocapture"
|
||||
|
|
|
@ -66,43 +66,33 @@ jobs:
|
|||
|
||||
- name: check minimal
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: hack
|
||||
args: check --workspace --no-default-features
|
||||
with: { command: ci-min }
|
||||
|
||||
- name: check minimal + tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: hack
|
||||
args: check --workspace --no-default-features --tests --examples
|
||||
with: { command: ci-min-test }
|
||||
|
||||
- name: check default
|
||||
uses: actions-rs/cargo@v1
|
||||
with: { command: ci-default }
|
||||
|
||||
- name: check full
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: check
|
||||
args: --workspace --bins --examples --tests
|
||||
with: { command: ci-full }
|
||||
|
||||
- name: tests
|
||||
uses: actions-rs/cargo@v1
|
||||
with:
|
||||
command: test
|
||||
args: --workspace --all-features --no-fail-fast -- --nocapture
|
||||
--skip=test_h2_content_length
|
||||
--skip=test_reading_deflate_encoding_large_random_rustls
|
||||
|
||||
- name: tests (actix-http)
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with:
|
||||
command: test
|
||||
args: --package=actix-http --no-default-features --features=rustls -- --nocapture
|
||||
command: ci-test
|
||||
args: --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
|
||||
- name: tests (awc)
|
||||
- name: doc tests
|
||||
# due to unknown issue with running doc tests on macOS
|
||||
if: matrix.target.os == 'ubuntu-latest'
|
||||
uses: actions-rs/cargo@v1
|
||||
timeout-minutes: 40
|
||||
with:
|
||||
command: test
|
||||
args: --package=awc --no-default-features --features=rustls -- --nocapture
|
||||
with: { command: ci-doctest }
|
||||
|
||||
- name: Generate coverage file
|
||||
if: >
|
||||
|
|
|
@ -36,4 +36,4 @@ jobs:
|
|||
uses: actions-rs/clippy-check@v1
|
||||
with:
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
args: --workspace --tests --all-features
|
||||
args: --workspace --all-features --tests
|
||||
|
|
16
CHANGES.md
16
CHANGES.md
|
@ -1,9 +1,20 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Changed
|
||||
* App data set on `Scope` instance can now be accessed in middlewares defined on the same scope. [#2267]
|
||||
### Added
|
||||
* Add `ServiceRequest::parts_mut`. [#2177]
|
||||
* Add extractors for `Uri` and `Method`. [#2263]
|
||||
* Add extractor for `ConnectionInfo` and `PeerAddr`. [#2263]
|
||||
|
||||
### Changed
|
||||
* Change compression algorithm features flags. [#2250]
|
||||
* Deprecate `App::data` and `App::data_factory`. [#2271]
|
||||
* `app_data` set on a `Scope` instance can now be accessed in middlewares defined on the same scope. This applies to `Resource` too. [#2267]
|
||||
|
||||
[#2177]: https://github.com/actix/actix-web/pull/2177
|
||||
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||
[#2271]: https://github.com/actix/actix-web/pull/2271
|
||||
[#2263]: https://github.com/actix/actix-web/pull/2263
|
||||
[#2267]: (https://github.com/actix/actix-web/pull/2267)
|
||||
|
||||
|
||||
|
@ -12,7 +23,6 @@
|
|||
* `HttpServer::worker_max_blocking_threads` for setting block thread pool. [#2200]
|
||||
|
||||
### Changed
|
||||
|
||||
* Adjusted default JSON payload limit to 2MB (from 32kb) and included size and limits in the `JsonPayloadError::Overflow` error variant. [#2162]
|
||||
[#2162]: (https://github.com/actix/actix-web/pull/2162)
|
||||
* `ServiceResponse::error_response` now uses body type of `Body`. [#2201]
|
||||
|
|
25
Cargo.toml
25
Cargo.toml
|
@ -17,7 +17,7 @@ edition = "2018"
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
# features that docs.rs will build with
|
||||
features = ["openssl", "rustls", "compress", "cookies", "secure-cookies"]
|
||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies", "secure-cookies"]
|
||||
|
||||
[lib]
|
||||
name = "actix_web"
|
||||
|
@ -39,10 +39,14 @@ members = [
|
|||
# resolver = "2"
|
||||
|
||||
[features]
|
||||
default = ["compress", "cookies"]
|
||||
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
|
||||
# content-encoding support
|
||||
compress = ["actix-http/compress"]
|
||||
# Brotli algorithm content-encoding support
|
||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||
# Gzip and deflate algorithms content-encoding support
|
||||
compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||
# Zstd algorithm content-encoding support
|
||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||
|
||||
# support for cookies
|
||||
cookies = ["cookie"]
|
||||
|
@ -56,6 +60,10 @@ openssl = ["actix-http/openssl", "actix-tls/accept", "actix-tls/openssl"]
|
|||
# rustls
|
||||
rustls = ["actix-http/rustls", "actix-tls/accept", "actix-tls/rustls"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-macros = "0.2.1"
|
||||
|
@ -71,6 +79,7 @@ actix-http = "3.0.0-beta.7"
|
|||
|
||||
ahash = "0.7"
|
||||
bytes = "1"
|
||||
cfg-if = "1"
|
||||
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
||||
derive_more = "0.99.5"
|
||||
either = "1.5.3"
|
||||
|
@ -94,7 +103,7 @@ time = { version = "0.2.23", default-features = false, features = ["std"] }
|
|||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.6", features = ["openssl"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
|
@ -126,15 +135,15 @@ awc = { path = "awc" }
|
|||
|
||||
[[test]]
|
||||
name = "test_server"
|
||||
required-features = ["compress", "cookies"]
|
||||
required-features = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
|
||||
[[example]]
|
||||
name = "basic"
|
||||
required-features = ["compress"]
|
||||
required-features = ["compress-gzip"]
|
||||
|
||||
[[example]]
|
||||
name = "uds"
|
||||
required-features = ["compress"]
|
||||
required-features = ["compress-gzip"]
|
||||
|
||||
[[example]]
|
||||
name = "on_connect"
|
||||
|
|
12
MIGRATION.md
12
MIGRATION.md
|
@ -10,6 +10,18 @@
|
|||
|
||||
Alternatively, explicitly require trailing slashes: `NormalizePath::new(TrailingSlash::Always)`.
|
||||
|
||||
* Feature flag `compress` has been split into its supported algorithm (brotli, gzip, zstd).
|
||||
By default all compression algorithms are enabled.
|
||||
To select algorithm you want to include with `middleware::Compress` use following flags:
|
||||
- `compress-brotli`
|
||||
- `compress-gzip`
|
||||
- `compress-zstd`
|
||||
If you have set in your `Cargo.toml` dedicated `actix-web` features and you still want
|
||||
to have compression enabled. Please change features selection like bellow:
|
||||
|
||||
Before: `"compress"`
|
||||
After: `"compress-brotli", "compress-gzip", "compress-zstd"`
|
||||
|
||||
|
||||
## 3.0.0
|
||||
|
||||
|
|
|
@ -25,7 +25,7 @@
|
|||
* Streaming and pipelining
|
||||
* Keep-alive and slow requests handling
|
||||
* Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||
* Transparent content compression/decompression (br, gzip, deflate)
|
||||
* Transparent content compression/decompression (br, gzip, deflate, zstd)
|
||||
* Powerful [request routing](https://actix.rs/docs/url-dispatch/)
|
||||
* Multipart streams
|
||||
* Static assets
|
||||
|
|
|
@ -8,11 +8,13 @@
|
|||
* For symbolic links, `Content-Disposition` header no longer shows the filename of the original file. [#2156]
|
||||
* `Files::redirect_to_slash_directory()` now works as expected when used with `Files::show_files_listing()`. [#2225]
|
||||
* `application/{javascript, json, wasm}` mime type now have `inline` disposition by default. [#2257]
|
||||
* `Files::show_files_listing()` can now be used with `Files::index_file()` to show files listing as a fallback when the index file is not found. [#2228]
|
||||
|
||||
[#2135]: https://github.com/actix/actix-web/pull/2135
|
||||
[#2156]: https://github.com/actix/actix-web/pull/2156
|
||||
[#2225]: https://github.com/actix/actix-web/pull/2225
|
||||
[#2257]: https://github.com/actix/actix-web/pull/2257
|
||||
[#2228]: https://github.com/actix/actix-web/pull/2228
|
||||
|
||||
|
||||
## 0.6.0-beta.4 - 2021-04-02
|
||||
|
|
|
@ -36,4 +36,4 @@ percent-encoding = "2.1"
|
|||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-web = "4.0.0-beta.7"
|
||||
actix-test = "0.1.0-beta.2"
|
||||
actix-test = "0.1.0-beta.3"
|
||||
|
|
|
@ -114,6 +114,9 @@ impl Files {
|
|||
/// Show files listing for directories.
|
||||
///
|
||||
/// By default show files listing is disabled.
|
||||
///
|
||||
/// When used with [`Files::index_file()`], files listing is shown as a fallback
|
||||
/// when the index file is not found.
|
||||
pub fn show_files_listing(mut self) -> Self {
|
||||
self.show_index = true;
|
||||
self
|
||||
|
@ -148,8 +151,11 @@ impl Files {
|
|||
|
||||
/// Set index file
|
||||
///
|
||||
/// Shows specific index file for directory "/" instead of
|
||||
/// Shows specific index file for directories instead of
|
||||
/// showing files listing.
|
||||
///
|
||||
/// If the index file is not found, files listing is shown as a fallback if
|
||||
/// [`Files::show_files_listing()`] is set.
|
||||
pub fn index_file<T: Into<String>>(mut self, index: T) -> Self {
|
||||
self.index = Some(index.into());
|
||||
self
|
||||
|
|
|
@ -872,4 +872,33 @@ mod tests {
|
|||
"inline; filename=\"symlink-test.png\""
|
||||
);
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_index_with_show_files_listing() {
|
||||
let service = Files::new(".", ".")
|
||||
.index_file("lib.rs")
|
||||
.show_files_listing()
|
||||
.new_service(())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
// Serve the index if exists
|
||||
let req = TestRequest::default().uri("/src").to_srv_request();
|
||||
let resp = test::call_service(&service, req).await;
|
||||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/x-rust"
|
||||
);
|
||||
|
||||
// Show files listing, otherwise.
|
||||
let req = TestRequest::default().uri("/tests").to_srv_request();
|
||||
let resp = test::call_service(&service, req).await;
|
||||
assert_eq!(
|
||||
resp.headers().get(header::CONTENT_TYPE).unwrap(),
|
||||
"text/html; charset=utf-8"
|
||||
);
|
||||
let bytes = test::read_body(resp).await;
|
||||
assert!(format!("{:?}", bytes).contains("/tests/test.png"));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -102,26 +102,20 @@ impl Service<ServiceRequest> for FilesService {
|
|||
)));
|
||||
}
|
||||
|
||||
if let Some(ref redir_index) = self.index {
|
||||
let path = path.join(redir_index);
|
||||
|
||||
match NamedFile::open(path) {
|
||||
Ok(mut named_file) => {
|
||||
if let Some(ref mime_override) = self.mime_override {
|
||||
let new_disposition =
|
||||
mime_override(&named_file.content_type.type_());
|
||||
named_file.content_disposition.disposition = new_disposition;
|
||||
}
|
||||
named_file.flags = self.file_flags;
|
||||
|
||||
let (req, _) = req.into_parts();
|
||||
let res = named_file.into_response(&req);
|
||||
Box::pin(ok(ServiceResponse::new(req, res)))
|
||||
}
|
||||
Err(err) => self.handle_err(err, req),
|
||||
let serve_named_file = |req: ServiceRequest, mut named_file: NamedFile| {
|
||||
if let Some(ref mime_override) = self.mime_override {
|
||||
let new_disposition = mime_override(&named_file.content_type.type_());
|
||||
named_file.content_disposition.disposition = new_disposition;
|
||||
}
|
||||
} else if self.show_index {
|
||||
let dir = Directory::new(self.directory.clone(), path);
|
||||
named_file.flags = self.file_flags;
|
||||
|
||||
let (req, _) = req.into_parts();
|
||||
let res = named_file.into_response(&req);
|
||||
Box::pin(ok(ServiceResponse::new(req, res)))
|
||||
};
|
||||
|
||||
let show_index = |req: ServiceRequest| {
|
||||
let dir = Directory::new(self.directory.clone(), path.clone());
|
||||
|
||||
let (req, _) = req.into_parts();
|
||||
let x = (self.renderer)(&dir, &req);
|
||||
|
@ -130,11 +124,19 @@ impl Service<ServiceRequest> for FilesService {
|
|||
Ok(resp) => ok(resp),
|
||||
Err(err) => ok(ServiceResponse::from_err(err, req)),
|
||||
})
|
||||
} else {
|
||||
Box::pin(ok(ServiceResponse::from_err(
|
||||
};
|
||||
|
||||
match self.index {
|
||||
Some(ref index) => match NamedFile::open(path.join(index)) {
|
||||
Ok(named_file) => serve_named_file(req, named_file),
|
||||
Err(_) if self.show_index => show_index(req),
|
||||
Err(err) => self.handle_err(err, req),
|
||||
},
|
||||
None if self.show_index => show_index(req),
|
||||
_ => Box::pin(ok(ServiceResponse::from_err(
|
||||
FilesError::IsDirectory,
|
||||
req.into_parts().0,
|
||||
)))
|
||||
))),
|
||||
}
|
||||
} else {
|
||||
match NamedFile::open(path) {
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Changed
|
||||
* Change compression algorithm features flags. [#2250]
|
||||
|
||||
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||
|
||||
|
||||
## 3.0.0-beta.7 - 2021-06-17
|
||||
|
|
|
@ -16,7 +16,7 @@ edition = "2018"
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
# features that docs.rs will build with
|
||||
features = ["openssl", "rustls", "compress"]
|
||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd"]
|
||||
|
||||
[lib]
|
||||
name = "actix_http"
|
||||
|
@ -32,11 +32,17 @@ openssl = ["actix-tls/openssl"]
|
|||
rustls = ["actix-tls/rustls"]
|
||||
|
||||
# enable compression support
|
||||
compress = ["flate2", "brotli2", "zstd"]
|
||||
compress-brotli = ["brotli2", "__compress"]
|
||||
compress-gzip = ["flate2", "__compress"]
|
||||
compress-zstd = ["zstd", "__compress"]
|
||||
|
||||
# trust-dns as client dns resolver
|
||||
trust-dns = ["trust-dns-resolver"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
[dependencies]
|
||||
actix-service = "2.0.0"
|
||||
actix-codec = "0.4.0"
|
||||
|
|
|
@ -8,10 +8,16 @@ use std::{
|
|||
};
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use brotli2::write::BrotliDecoder;
|
||||
use bytes::Bytes;
|
||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||
use futures_core::{ready, Stream};
|
||||
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
use brotli2::write::BrotliDecoder;
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
use flate2::write::{GzDecoder, ZlibDecoder};
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
use zstd::stream::write::Decoder as ZstdDecoder;
|
||||
|
||||
use crate::{
|
||||
|
@ -37,15 +43,19 @@ where
|
|||
#[inline]
|
||||
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
|
||||
let decoder = match encoding {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
|
||||
BrotliDecoder::new(Writer::new()),
|
||||
))),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||
ZlibDecoder::new(Writer::new()),
|
||||
))),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(
|
||||
GzDecoder::new(Writer::new()),
|
||||
))),
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
||||
ZstdDecoder::new(Writer::new()).expect(
|
||||
"Failed to create zstd decoder. This is a bug. \
|
||||
|
@ -148,17 +158,22 @@ where
|
|||
}
|
||||
|
||||
enum ContentDecoder {
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
Deflate(Box<ZlibDecoder<Writer>>),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
Gzip(Box<GzDecoder<Writer>>),
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
Br(Box<BrotliDecoder<Writer>>),
|
||||
// We need explicit 'static lifetime here because ZstdDecoder need lifetime
|
||||
// argument, and we use `spawn_blocking` in `Decoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
Zstd(Box<ZstdDecoder<'static, Writer>>),
|
||||
}
|
||||
|
||||
impl ContentDecoder {
|
||||
fn feed_eof(&mut self) -> io::Result<Option<Bytes>> {
|
||||
match self {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentDecoder::Br(ref mut decoder) => match decoder.flush() {
|
||||
Ok(()) => {
|
||||
let b = decoder.get_mut().take();
|
||||
|
@ -172,6 +187,7 @@ impl ContentDecoder {
|
|||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentDecoder::Gzip(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
|
@ -185,6 +201,7 @@ impl ContentDecoder {
|
|||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentDecoder::Deflate(ref mut decoder) => match decoder.try_finish() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
|
@ -197,6 +214,7 @@ impl ContentDecoder {
|
|||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentDecoder::Zstd(ref mut decoder) => match decoder.flush() {
|
||||
Ok(_) => {
|
||||
let b = decoder.get_mut().take();
|
||||
|
@ -213,6 +231,7 @@ impl ContentDecoder {
|
|||
|
||||
fn feed_data(&mut self, data: Bytes) -> io::Result<Option<Bytes>> {
|
||||
match self {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentDecoder::Br(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
|
@ -227,6 +246,7 @@ impl ContentDecoder {
|
|||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentDecoder::Gzip(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
|
@ -241,6 +261,7 @@ impl ContentDecoder {
|
|||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentDecoder::Deflate(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
|
@ -255,6 +276,7 @@ impl ContentDecoder {
|
|||
Err(e) => Err(e),
|
||||
},
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentDecoder::Zstd(ref mut decoder) => match decoder.write_all(&data) {
|
||||
Ok(_) => {
|
||||
decoder.flush()?;
|
||||
|
|
|
@ -9,12 +9,18 @@ use std::{
|
|||
};
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use brotli2::write::BrotliEncoder;
|
||||
use bytes::Bytes;
|
||||
use derive_more::Display;
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
use futures_core::ready;
|
||||
use pin_project::pin_project;
|
||||
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
use brotli2::write::BrotliEncoder;
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||
|
||||
use crate::{
|
||||
|
@ -233,28 +239,36 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
|||
}
|
||||
|
||||
enum ContentEncoder {
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
Deflate(ZlibEncoder<Writer>),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
Gzip(GzEncoder<Writer>),
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
Br(BrotliEncoder<Writer>),
|
||||
// We need explicit 'static lifetime here because ZstdEncoder need lifetime
|
||||
// argument, and we use `spawn_blocking` in `Encoder::poll_next` that require `FnOnce() -> R + Send + 'static`
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
Zstd(ZstdEncoder<'static, Writer>),
|
||||
}
|
||||
|
||||
impl ContentEncoder {
|
||||
fn encoder(encoding: ContentEncoding) -> Option<Self> {
|
||||
match encoding {
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Deflate => Some(ContentEncoder::Deflate(ZlibEncoder::new(
|
||||
Writer::new(),
|
||||
flate2::Compression::fast(),
|
||||
))),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Gzip => Some(ContentEncoder::Gzip(GzEncoder::new(
|
||||
Writer::new(),
|
||||
flate2::Compression::fast(),
|
||||
))),
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoding::Br => {
|
||||
Some(ContentEncoder::Br(BrotliEncoder::new(Writer::new(), 3)))
|
||||
}
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoding::Zstd => {
|
||||
let encoder = ZstdEncoder::new(Writer::new(), 3).ok()?;
|
||||
Some(ContentEncoder::Zstd(encoder))
|
||||
|
@ -266,27 +280,35 @@ impl ContentEncoder {
|
|||
#[inline]
|
||||
pub(crate) fn take(&mut self) -> Bytes {
|
||||
match *self {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => encoder.get_mut().take(),
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoder::Zstd(ref mut encoder) => encoder.get_mut().take(),
|
||||
}
|
||||
}
|
||||
|
||||
fn finish(self) -> Result<Bytes, io::Error> {
|
||||
match self {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoder::Br(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Gzip(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Deflate(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
},
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoder::Zstd(encoder) => match encoder.finish() {
|
||||
Ok(writer) => Ok(writer.buf.freeze()),
|
||||
Err(err) => Err(err),
|
||||
|
@ -296,6 +318,7 @@ impl ContentEncoder {
|
|||
|
||||
fn write(&mut self, data: &[u8]) -> Result<(), io::Error> {
|
||||
match *self {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoder::Br(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
|
@ -303,6 +326,7 @@ impl ContentEncoder {
|
|||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Gzip(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
|
@ -310,6 +334,7 @@ impl ContentEncoder {
|
|||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoder::Deflate(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
|
@ -317,6 +342,7 @@ impl ContentEncoder {
|
|||
Err(err)
|
||||
}
|
||||
},
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoder::Zstd(ref mut encoder) => match encoder.write_all(data) {
|
||||
Ok(_) => Ok(()),
|
||||
Err(err) => {
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
//! HTTP primitives for the Actix ecosystem.
|
||||
//!
|
||||
//! ## Crate Features
|
||||
//! | Feature | Functionality |
|
||||
//! | ---------------- | ----------------------------------------------------- |
|
||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||
//! | `rustls` | TLS support via [rustls]. |
|
||||
//! | `compress` | Payload compression support. (Deflate, Gzip & Brotli) |
|
||||
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
|
||||
//! | Feature | Functionality |
|
||||
//! | ------------------- | ------------------------------------------- |
|
||||
//! | `openssl` | TLS support via [OpenSSL]. |
|
||||
//! | `rustls` | TLS support via [rustls]. |
|
||||
//! | `compress-brotli` | Payload compression support: Brotli. |
|
||||
//! | `compress-gzip` | Payload compression support: Deflate, Gzip. |
|
||||
//! | `compress-zstd` | Payload compression support: Zstd. |
|
||||
//! | `trust-dns` | Use [trust-dns] as the client DNS resolver. |
|
||||
//!
|
||||
//! [OpenSSL]: https://crates.io/crates/openssl
|
||||
//! [rustls]: https://crates.io/crates/rustls
|
||||
|
@ -32,7 +34,8 @@ pub mod body;
|
|||
mod builder;
|
||||
pub mod client;
|
||||
mod config;
|
||||
#[cfg(feature = "compress")]
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub mod encoding;
|
||||
mod extensions;
|
||||
pub mod header;
|
||||
|
|
|
@ -3,6 +3,10 @@
|
|||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.1.0-beta.3 - 2021-06-20
|
||||
* No significant changes from `0.1.0-beta.2`.
|
||||
|
||||
|
||||
## 0.1.0-beta.2 - 2021-04-17
|
||||
* No significant changes from `0.1.0-beta.1`.
|
||||
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.0-beta.2"
|
||||
version = "0.1.0-beta.3"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
|
|
|
@ -1,6 +1,9 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
* Update `actix` to `0.12`. [#2277]
|
||||
|
||||
[#2277]: https://github.com/actix/actix-web/pull/2277
|
||||
|
||||
|
||||
## 4.0.0-beta.5 - 2021-06-17
|
||||
|
|
|
@ -16,7 +16,7 @@ name = "actix_web_actors"
|
|||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix = { version = "0.11.0-beta.3", default-features = false }
|
||||
actix = { version = "0.12.0", default-features = false }
|
||||
actix-codec = "0.4.0"
|
||||
actix-http = "3.0.0-beta.7"
|
||||
actix-web = { version = "4.0.0-beta.7", default-features = false }
|
||||
|
@ -29,7 +29,7 @@ tokio = { version = "1", features = ["sync"] }
|
|||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.2"
|
||||
actix-test = "0.1.0-beta.3"
|
||||
|
||||
awc = { version = "3.0.0-beta.6", default-features = false }
|
||||
env_logger = "0.8"
|
||||
|
|
|
@ -20,7 +20,7 @@ proc-macro2 = "1"
|
|||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.2"
|
||||
actix-test = "0.1.0-beta.3"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = "4.0.0-beta.7"
|
||||
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
### Changed
|
||||
* Change compression algorithm features flags. [#2250]
|
||||
|
||||
[#2250]: https://github.com/actix/actix-web/pull/2250
|
||||
|
||||
|
||||
## 3.0.0-beta.6 - 2021-06-17
|
||||
|
|
|
@ -24,10 +24,10 @@ path = "src/lib.rs"
|
|||
|
||||
[package.metadata.docs.rs]
|
||||
# features that docs.rs will build with
|
||||
features = ["openssl", "rustls", "compress", "cookies"]
|
||||
features = ["openssl", "rustls", "compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
|
||||
[features]
|
||||
default = ["compress", "cookies"]
|
||||
default = ["compress-brotli", "compress-gzip", "compress-zstd", "cookies"]
|
||||
|
||||
# openssl
|
||||
openssl = ["tls-openssl", "actix-http/openssl"]
|
||||
|
@ -35,8 +35,12 @@ openssl = ["tls-openssl", "actix-http/openssl"]
|
|||
# rustls
|
||||
rustls = ["tls-rustls", "actix-http/rustls"]
|
||||
|
||||
# content-encoding support
|
||||
compress = ["actix-http/compress"]
|
||||
# Brotli algorithm content-encoding support
|
||||
compress-brotli = ["actix-http/compress-brotli", "__compress"]
|
||||
# Gzip and deflate algorithms content-encoding support
|
||||
compress-gzip = ["actix-http/compress-gzip", "__compress"]
|
||||
# Zstd algorithm content-encoding support
|
||||
compress-zstd = ["actix-http/compress-zstd", "__compress"]
|
||||
|
||||
# cookie parsing and cookie jar
|
||||
cookies = ["cookie"]
|
||||
|
@ -44,6 +48,10 @@ cookies = ["cookie"]
|
|||
# trust-dns as dns resolver
|
||||
trust-dns = ["actix-http/trust-dns"]
|
||||
|
||||
# Internal (PRIVATE!) features used to aid testing and cheking feature status.
|
||||
# Don't rely on these whatsoever. They may disappear at anytime.
|
||||
__compress = []
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.0"
|
||||
actix-service = "2.0.0"
|
||||
|
@ -52,6 +60,7 @@ actix-rt = { version = "2.1", default-features = false }
|
|||
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
cfg-if = "1"
|
||||
cookie = { version = "0.15", features = ["percent-encode"], optional = true }
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
|
@ -74,7 +83,7 @@ actix-http-test = { version = "3.0.0-beta.4", features = ["openssl"] }
|
|||
actix-utils = "3.0.0"
|
||||
actix-server = "2.0.0-beta.3"
|
||||
actix-tls = { version = "3.0.0-beta.5", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.2", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.3", features = ["openssl", "rustls"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
env_logger = "0.8"
|
||||
|
|
|
@ -2,17 +2,19 @@ use std::error::Error as StdError;
|
|||
|
||||
#[actix_web::main]
|
||||
async fn main() -> Result<(), Box<dyn StdError>> {
|
||||
std::env::set_var("RUST_LOG", "actix_http=trace");
|
||||
std::env::set_var("RUST_LOG", "client=trace,awc=trace,actix_http=trace");
|
||||
env_logger::init();
|
||||
|
||||
let client = awc::Client::new();
|
||||
|
||||
// Create request builder, configure request and send
|
||||
let mut response = client
|
||||
let request = client
|
||||
.get("https://www.rust-lang.org/")
|
||||
.append_header(("User-Agent", "Actix-web"))
|
||||
.send()
|
||||
.await?;
|
||||
.append_header(("User-Agent", "Actix-web"));
|
||||
|
||||
println!("Request: {:?}", request);
|
||||
|
||||
let mut response = request.send().await?;
|
||||
|
||||
// server http response
|
||||
println!("Response: {:?}", response);
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
//! `awc` is a HTTP and WebSocket client library built on the Actix ecosystem.
|
||||
//!
|
||||
//! ## Making a GET request
|
||||
//!
|
||||
//! # Making a GET request
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||
|
@ -16,10 +15,8 @@
|
|||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## Making POST requests
|
||||
//!
|
||||
//! ### Raw body contents
|
||||
//!
|
||||
//! # Making POST requests
|
||||
//! ## Raw body contents
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||
|
@ -31,8 +28,7 @@
|
|||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### Forms
|
||||
//!
|
||||
//! ## Forms
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||
|
@ -46,8 +42,7 @@
|
|||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ### JSON
|
||||
//!
|
||||
//! ## JSON
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), awc::error::SendRequestError> {
|
||||
|
@ -64,8 +59,24 @@
|
|||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! ## WebSocket support
|
||||
//! # Response Compression
|
||||
//! All [official][iana-encodings] and common content encoding codecs are supported, optionally.
|
||||
//!
|
||||
//! The `Accept-Encoding` header will automatically be populated with enabled codecs and added to
|
||||
//! outgoing requests, allowing servers to select their `Content-Encoding` accordingly.
|
||||
//!
|
||||
//! Feature flags enable these codecs according to the table below. By default, all `compress-*`
|
||||
//! features are enabled.
|
||||
//!
|
||||
//! | Feature | Codecs |
|
||||
//! | ----------------- | ------------- |
|
||||
//! | `compress-brotli` | brotli |
|
||||
//! | `compress-gzip` | gzip, deflate |
|
||||
//! | `compress-zstd` | zstd |
|
||||
//!
|
||||
//! [iana-encodings]: https://www.iana.org/assignments/http-parameters/http-parameters.xhtml#content-coding
|
||||
//!
|
||||
//! # WebSocket support
|
||||
//! ```no_run
|
||||
//! # #[actix_rt::main]
|
||||
//! # async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
|
@ -128,6 +139,9 @@ pub use self::sender::SendClientRequest;
|
|||
|
||||
/// An asynchronous HTTP and WebSocket client.
|
||||
///
|
||||
/// You should take care to create, at most, one `Client` per thread. Otherwise, expect higher CPU
|
||||
/// and memory usage.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use awc::Client;
|
||||
|
@ -136,10 +150,10 @@ pub use self::sender::SendClientRequest;
|
|||
/// async fn main() {
|
||||
/// let mut client = Client::default();
|
||||
///
|
||||
/// let res = client.get("http://www.rust-lang.org") // <- Create request builder
|
||||
/// .insert_header(("User-Agent", "Actix-web"))
|
||||
/// .send() // <- Send HTTP request
|
||||
/// .await; // <- send request and wait for response
|
||||
/// let res = client.get("http://www.rust-lang.org")
|
||||
/// .insert_header(("User-Agent", "my-app/1.2"))
|
||||
/// .send()
|
||||
/// .await;
|
||||
///
|
||||
/// println!("Response: {:?}", res);
|
||||
/// }
|
||||
|
|
|
@ -8,7 +8,7 @@ use actix_http::{
|
|||
body::Body,
|
||||
http::{
|
||||
header::{self, IntoHeaderPair},
|
||||
uri, ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
|
||||
ConnectionType, Error as HttpError, HeaderMap, HeaderValue, Method, Uri, Version,
|
||||
},
|
||||
RequestHead,
|
||||
};
|
||||
|
@ -22,11 +22,6 @@ use crate::{
|
|||
ClientConfig,
|
||||
};
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
const HTTPS_ENCODING: &str = "br, gzip, deflate";
|
||||
#[cfg(not(feature = "compress"))]
|
||||
const HTTPS_ENCODING: &str = "br";
|
||||
|
||||
/// An HTTP Client request builder
|
||||
///
|
||||
/// This type can be used to construct an instance of `ClientRequest` through a
|
||||
|
@ -480,22 +475,37 @@ impl ClientRequest {
|
|||
|
||||
let mut slf = self;
|
||||
|
||||
// Set Accept-Encoding HTTP header depending on enabled feature.
|
||||
// If decompress is not ask, then we are not able to find which encoding is
|
||||
// supported, so we cannot guess Accept-Encoding HTTP header.
|
||||
if slf.response_decompress {
|
||||
let https = slf
|
||||
.head
|
||||
.uri
|
||||
.scheme()
|
||||
.map(|s| s == &uri::Scheme::HTTPS)
|
||||
.unwrap_or(true);
|
||||
// Set Accept-Encoding with compression algorithm awc is built with.
|
||||
#[cfg(feature = "__compress")]
|
||||
let accept_encoding = {
|
||||
let mut encoding = vec![];
|
||||
|
||||
if https {
|
||||
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, HTTPS_ENCODING));
|
||||
} else {
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
encoding.push("br");
|
||||
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
{
|
||||
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, "gzip, deflate"));
|
||||
encoding.push("gzip");
|
||||
encoding.push("deflate");
|
||||
}
|
||||
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
encoding.push("zstd");
|
||||
|
||||
assert!(!encoding.is_empty(), "encoding cannot be empty unless __compress feature has been explictily enabled.");
|
||||
encoding.join(", ")
|
||||
};
|
||||
|
||||
// Otherwise tell the server, we do not support any compression algorithm.
|
||||
// So we clearly indicate that we do want identity encoding.
|
||||
#[cfg(not(feature = "__compress"))]
|
||||
let accept_encoding = "identity";
|
||||
|
||||
slf = slf.insert_header_if_none((header::ACCEPT_ENCODING, accept_encoding));
|
||||
}
|
||||
|
||||
Ok(slf)
|
||||
|
|
|
@ -22,7 +22,7 @@ use derive_more::From;
|
|||
use futures_core::Stream;
|
||||
use serde::Serialize;
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
use actix_http::{encoding::Decoder, http::header::ContentEncoding, Payload, PayloadStream};
|
||||
|
||||
use crate::{
|
||||
|
@ -91,7 +91,7 @@ impl SendClientRequest {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
impl Future for SendClientRequest {
|
||||
type Output = Result<ClientResponse<Decoder<Payload<PayloadStream>>>, SendRequestError>;
|
||||
|
||||
|
@ -131,7 +131,7 @@ impl Future for SendClientRequest {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(not(feature = "compress"))]
|
||||
#[cfg(not(feature = "__compress"))]
|
||||
impl Future for SendClientRequest {
|
||||
type Output = Result<ClientResponse, SendRequestError>;
|
||||
|
||||
|
|
15
src/app.rs
15
src/app.rs
|
@ -98,13 +98,18 @@ where
|
|||
/// web::resource("/index.html").route(
|
||||
/// web::get().to(index)));
|
||||
/// ```
|
||||
#[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
|
||||
pub fn data<U: 'static>(self, data: U) -> Self {
|
||||
self.app_data(Data::new(data))
|
||||
}
|
||||
|
||||
/// Set application data factory. This function is
|
||||
/// similar to `.data()` but it accepts data factory. Data object get
|
||||
/// constructed asynchronously during application initialization.
|
||||
/// Add application data factory. This function is similar to `.data()` but it accepts a
|
||||
/// "data factory". Data values are constructed asynchronously during application
|
||||
/// initialization, before the server starts accepting requests.
|
||||
#[deprecated(
|
||||
since = "4.0.0",
|
||||
note = "Construct data value before starting server and use `.app_data(Data::new(val))` instead."
|
||||
)]
|
||||
pub fn data_factory<F, Out, D, E>(mut self, data: F) -> Self
|
||||
where
|
||||
F: Fn() -> Out + 'static,
|
||||
|
@ -518,6 +523,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::CREATED);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_data_factory() {
|
||||
let srv = init_service(
|
||||
|
@ -541,6 +548,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_data_factory_errors() {
|
||||
let srv = try_init_service(
|
||||
|
|
|
@ -365,6 +365,8 @@ mod tests {
|
|||
}
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_drop_data() {
|
||||
let data = Arc::new(AtomicBool::new(false));
|
||||
|
|
|
@ -124,6 +124,7 @@ impl AppConfig {
|
|||
AppConfig { secure, host, addr }
|
||||
}
|
||||
|
||||
/// Needed in actix-test crate.
|
||||
#[doc(hidden)]
|
||||
pub fn __priv_test_new(secure: bool, host: String, addr: SocketAddr) -> Self {
|
||||
AppConfig::new(secure, host, addr)
|
||||
|
|
|
@ -154,6 +154,8 @@ mod tests {
|
|||
web, App, HttpResponse,
|
||||
};
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_data_extractor() {
|
||||
let srv = init_service(App::new().data("TEST".to_string()).service(
|
||||
|
@ -221,6 +223,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_route_data_extractor() {
|
||||
let srv = init_service(
|
||||
|
@ -250,6 +254,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_override_data() {
|
||||
let srv =
|
||||
|
|
|
@ -1,12 +1,14 @@
|
|||
//! Request extractors
|
||||
|
||||
use std::{
|
||||
convert::Infallible,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_utils::future::{ready, Ready};
|
||||
use actix_http::http::{Method, Uri};
|
||||
use actix_utils::future::{ok, Ready};
|
||||
use futures_core::ready;
|
||||
|
||||
use crate::{dev::Payload, Error, HttpRequest};
|
||||
|
@ -216,14 +218,58 @@ where
|
|||
}
|
||||
}
|
||||
|
||||
/// Extract the request's URI.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{http::Uri, web, App, Responder};
|
||||
///
|
||||
/// async fn handler(uri: Uri) -> impl Responder {
|
||||
/// format!("Requested path: {}", uri.path())
|
||||
/// }
|
||||
///
|
||||
/// let app = App::new().default_service(web::to(handler));
|
||||
/// ```
|
||||
impl FromRequest for Uri {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(req.uri().clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Extract the request's method.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_web::{http::Method, web, App, Responder};
|
||||
///
|
||||
/// async fn handler(method: Method) -> impl Responder {
|
||||
/// format!("Request method: {}", method)
|
||||
/// }
|
||||
///
|
||||
/// let app = App::new().default_service(web::to(handler));
|
||||
/// ```
|
||||
impl FromRequest for Method {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(req.method().clone())
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
impl FromRequest for () {
|
||||
type Error = Error;
|
||||
type Future = Ready<Result<(), Error>>;
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<(), Infallible>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(_: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ready(Ok(()))
|
||||
ok(())
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -411,4 +457,18 @@ mod tests {
|
|||
.unwrap();
|
||||
assert!(r.is_err());
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_uri() {
|
||||
let req = TestRequest::default().uri("/foo/bar").to_http_request();
|
||||
let uri = Uri::extract(&req).await.unwrap();
|
||||
assert_eq!(uri.path(), "/foo/bar");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_method() {
|
||||
let req = TestRequest::default().method(Method::GET).to_http_request();
|
||||
let method = Method::extract(&req).await.unwrap();
|
||||
assert_eq!(method, Method::GET);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use std::{fmt, str};
|
||||
|
||||
pub use self::Encoding::{
|
||||
Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers,
|
||||
Brotli, Chunked, Compress, Deflate, EncodingExt, Gzip, Identity, Trailers, Zstd,
|
||||
};
|
||||
|
||||
/// A value to represent an encoding used in `Transfer-Encoding`
|
||||
|
@ -22,6 +22,8 @@ pub enum Encoding {
|
|||
Identity,
|
||||
/// The `trailers` encoding.
|
||||
Trailers,
|
||||
/// The `zstd` encoding.
|
||||
Zstd,
|
||||
/// Some other encoding that is less common, can be any String.
|
||||
EncodingExt(String),
|
||||
}
|
||||
|
@ -36,6 +38,7 @@ impl fmt::Display for Encoding {
|
|||
Compress => "compress",
|
||||
Identity => "identity",
|
||||
Trailers => "trailers",
|
||||
Zstd => "zstd",
|
||||
EncodingExt(ref s) => s.as_ref(),
|
||||
})
|
||||
}
|
||||
|
@ -52,6 +55,7 @@ impl str::FromStr for Encoding {
|
|||
"compress" => Ok(Compress),
|
||||
"identity" => Ok(Identity),
|
||||
"trailers" => Ok(Trailers),
|
||||
"zstd" => Ok(Zstd),
|
||||
_ => Ok(EncodingExt(s.to_owned())),
|
||||
}
|
||||
}
|
||||
|
|
111
src/info.rs
111
src/info.rs
|
@ -1,13 +1,36 @@
|
|||
use std::cell::Ref;
|
||||
use std::{cell::Ref, convert::Infallible, net::SocketAddr};
|
||||
|
||||
use crate::dev::{AppConfig, RequestHead};
|
||||
use crate::http::header::{self, HeaderName};
|
||||
use actix_utils::future::{err, ok, Ready};
|
||||
use derive_more::{Display, Error};
|
||||
|
||||
use crate::{
|
||||
dev::{AppConfig, Payload, RequestHead},
|
||||
http::header::{self, HeaderName},
|
||||
FromRequest, HttpRequest, ResponseError,
|
||||
};
|
||||
|
||||
const X_FORWARDED_FOR: &[u8] = b"x-forwarded-for";
|
||||
const X_FORWARDED_HOST: &[u8] = b"x-forwarded-host";
|
||||
const X_FORWARDED_PROTO: &[u8] = b"x-forwarded-proto";
|
||||
|
||||
/// `HttpRequest` connection information
|
||||
/// HTTP connection information.
|
||||
///
|
||||
/// `ConnectionInfo` implements `FromRequest` and can be extracted in handlers.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_web::{HttpResponse, Responder};
|
||||
/// use actix_web::dev::ConnectionInfo;
|
||||
///
|
||||
/// async fn handler(conn: ConnectionInfo) -> impl Responder {
|
||||
/// match conn.host() {
|
||||
/// "actix.rs" => HttpResponse::Ok().body("Welcome!"),
|
||||
/// "admin.actix.rs" => HttpResponse::Ok().body("Admin portal."),
|
||||
/// _ => HttpResponse::NotFound().finish()
|
||||
/// }
|
||||
/// }
|
||||
/// # let _svc = actix_web::web::to(handler);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
pub struct ConnectionInfo {
|
||||
scheme: String,
|
||||
|
@ -187,6 +210,65 @@ impl ConnectionInfo {
|
|||
}
|
||||
}
|
||||
|
||||
impl FromRequest for ConnectionInfo {
|
||||
type Error = Infallible;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
ok(req.connection_info().clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// Extractor for peer's socket address.
|
||||
///
|
||||
/// Also see [`HttpRequest::peer_addr`].
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_web::Responder;
|
||||
/// use actix_web::dev::PeerAddr;
|
||||
///
|
||||
/// async fn handler(peer_addr: PeerAddr) -> impl Responder {
|
||||
/// let socket_addr = peer_addr.0;
|
||||
/// socket_addr.to_string()
|
||||
/// }
|
||||
/// # let _svc = actix_web::web::to(handler);
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord, Display)]
|
||||
#[display(fmt = "{}", _0)]
|
||||
pub struct PeerAddr(pub SocketAddr);
|
||||
|
||||
impl PeerAddr {
|
||||
/// Unwrap into inner `SocketAddr` value.
|
||||
pub fn into_inner(self) -> SocketAddr {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display, Error)]
|
||||
#[non_exhaustive]
|
||||
#[display(fmt = "Missing peer address")]
|
||||
pub struct MissingPeerAddr;
|
||||
|
||||
impl ResponseError for MissingPeerAddr {}
|
||||
|
||||
impl FromRequest for PeerAddr {
|
||||
type Error = MissingPeerAddr;
|
||||
type Future = Ready<Result<Self, Self::Error>>;
|
||||
type Config = ();
|
||||
|
||||
fn from_request(req: &HttpRequest, _: &mut Payload) -> Self::Future {
|
||||
match req.peer_addr() {
|
||||
Some(addr) => ok(PeerAddr(addr)),
|
||||
None => {
|
||||
log::error!("Missing peer address.");
|
||||
err(MissingPeerAddr)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
@ -239,4 +321,25 @@ mod tests {
|
|||
let info = req.connection_info();
|
||||
assert_eq!(info.scheme(), "https");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_conn_info() {
|
||||
let req = TestRequest::default()
|
||||
.uri("http://actix.rs/")
|
||||
.to_http_request();
|
||||
let conn_info = ConnectionInfo::extract(&req).await.unwrap();
|
||||
assert_eq!(conn_info.scheme(), "http");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_peer_addr() {
|
||||
let addr = "127.0.0.1:8080".parse().unwrap();
|
||||
let req = TestRequest::default().peer_addr(addr).to_http_request();
|
||||
let peer_addr = PeerAddr::extract(&req).await.unwrap();
|
||||
assert_eq!(peer_addr, PeerAddr(addr));
|
||||
|
||||
let req = TestRequest::default().to_http_request();
|
||||
let res = PeerAddr::extract(&req).await;
|
||||
assert!(res.is_err());
|
||||
}
|
||||
}
|
||||
|
|
11
src/lib.rs
11
src/lib.rs
|
@ -47,7 +47,7 @@
|
|||
//! * Streaming and pipelining
|
||||
//! * Keep-alive and slow requests handling
|
||||
//! * Client/server [WebSockets](https://actix.rs/docs/websockets/) support
|
||||
//! * Transparent content compression/decompression (br, gzip, deflate)
|
||||
//! * Transparent content compression/decompression (br, gzip, deflate, zstd)
|
||||
//! * Powerful [request routing](https://actix.rs/docs/url-dispatch/)
|
||||
//! * Multipart streams
|
||||
//! * Static assets
|
||||
|
@ -57,8 +57,10 @@
|
|||
//! * Runs on stable Rust 1.46+
|
||||
//!
|
||||
//! # Crate Features
|
||||
//! * `compress` - content encoding compression support (enabled by default)
|
||||
//! * `cookies` - cookies support (enabled by default)
|
||||
//! * `compress-brotli` - brotli content encoding compression support (enabled by default)
|
||||
//! * `compress-gzip` - gzip and deflate content encoding compression support (enabled by default)
|
||||
//! * `compress-zstd` - zstd content encoding compression support (enabled by default)
|
||||
//! * `openssl` - HTTPS support via `openssl` crate, supports `HTTP/2`
|
||||
//! * `rustls` - HTTPS support via `rustls` crate, supports `HTTP/2`
|
||||
//! * `secure-cookies` - secure cookies support
|
||||
|
@ -129,7 +131,7 @@ pub mod dev {
|
|||
pub use crate::config::{AppConfig, AppService};
|
||||
#[doc(hidden)]
|
||||
pub use crate::handler::Handler;
|
||||
pub use crate::info::ConnectionInfo;
|
||||
pub use crate::info::{ConnectionInfo, PeerAddr};
|
||||
pub use crate::rmap::ResourceMap;
|
||||
pub use crate::service::{HttpServiceFactory, ServiceRequest, ServiceResponse, WebService};
|
||||
|
||||
|
@ -140,7 +142,8 @@ pub mod dev {
|
|||
pub use actix_http::body::{
|
||||
AnyBody, Body, BodySize, MessageBody, ResponseBody, SizedStream,
|
||||
};
|
||||
#[cfg(feature = "compress")]
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use actix_http::encoding::Decoder as Decompress;
|
||||
pub use actix_http::ResponseBuilder as BaseHttpResponseBuilder;
|
||||
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, ResponseHead};
|
||||
|
|
|
@ -144,7 +144,7 @@ mod tests {
|
|||
use crate::{web, App, HttpResponse};
|
||||
|
||||
#[actix_rt::test]
|
||||
#[cfg(all(feature = "cookies", feature = "compress"))]
|
||||
#[cfg(all(feature = "cookies", feature = "__compress"))]
|
||||
async fn test_scope_middleware() {
|
||||
use crate::middleware::Compress;
|
||||
|
||||
|
@ -167,7 +167,7 @@ mod tests {
|
|||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
#[cfg(all(feature = "cookies", feature = "compress"))]
|
||||
#[cfg(all(feature = "cookies", feature = "__compress"))]
|
||||
async fn test_resource_scope_middleware() {
|
||||
use crate::middleware::Compress;
|
||||
|
||||
|
|
|
@ -14,7 +14,8 @@ pub use self::err_handlers::{ErrorHandlerResponse, ErrorHandlers};
|
|||
pub use self::logger::Logger;
|
||||
pub use self::normalize::{NormalizePath, TrailingSlash};
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
mod compress;
|
||||
#[cfg(feature = "compress")]
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use self::compress::Compress;
|
||||
|
|
|
@ -60,18 +60,6 @@ impl HttpRequest {
|
|||
}),
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub fn __priv_test_new(
|
||||
path: Path<Url>,
|
||||
head: Message<RequestHead>,
|
||||
rmap: Rc<ResourceMap>,
|
||||
config: AppConfig,
|
||||
app_data: Rc<Extensions>,
|
||||
) -> HttpRequest {
|
||||
let app_state = AppInitServiceState::new(rmap, config);
|
||||
Self::new(path, head, app_state, app_data)
|
||||
}
|
||||
}
|
||||
|
||||
impl HttpRequest {
|
||||
|
@ -723,6 +711,8 @@ mod tests {
|
|||
assert_eq!(body, Bytes::from_static(b"1"));
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_extensions_dropped() {
|
||||
struct Tracker {
|
||||
|
|
|
@ -196,6 +196,7 @@ where
|
|||
/// ));
|
||||
/// }
|
||||
/// ```
|
||||
#[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
|
||||
pub fn data<U: 'static>(self, data: U) -> Self {
|
||||
self.app_data(Data::new(data))
|
||||
}
|
||||
|
@ -679,6 +680,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::NO_CONTENT);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_data() {
|
||||
let srv = init_service(
|
||||
|
@ -711,6 +714,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_data_default_service() {
|
||||
let srv = init_service(
|
||||
|
|
|
@ -49,7 +49,10 @@ impl HttpResponse<AnyBody> {
|
|||
/// Create an error response.
|
||||
#[inline]
|
||||
pub fn from_error(error: impl Into<Error>) -> Self {
|
||||
error.into().as_response_error().error_response()
|
||||
let error = error.into();
|
||||
let mut response = error.as_response_error().error_response();
|
||||
response.error = Some(error);
|
||||
response
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -146,6 +146,7 @@ where
|
|||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
#[deprecated(since = "4.0.0", note = "Use `.app_data(Data::new(val))` instead.")]
|
||||
pub fn data<U: 'static>(self, data: U) -> Self {
|
||||
self.app_data(Data::new(data))
|
||||
}
|
||||
|
@ -994,6 +995,8 @@ mod tests {
|
|||
);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_middleware_app_data() {
|
||||
let srv = init_service(
|
||||
|
@ -1032,6 +1035,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), StatusCode::OK);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_override_data_default_service() {
|
||||
let srv = init_service(App::new().data(1usize).service(
|
||||
|
|
|
@ -74,18 +74,18 @@ impl ServiceRequest {
|
|||
Self { req, payload }
|
||||
}
|
||||
|
||||
/// Construct service request.
|
||||
#[doc(hidden)]
|
||||
pub fn __priv_test_new(req: HttpRequest, payload: Payload) -> Self {
|
||||
Self::new(req, payload)
|
||||
}
|
||||
|
||||
/// Deconstruct request into parts
|
||||
#[inline]
|
||||
pub fn into_parts(self) -> (HttpRequest, Payload) {
|
||||
(self.req, self.payload)
|
||||
}
|
||||
|
||||
/// Get mutable access to inner `HttpRequest` and `Payload`
|
||||
#[inline]
|
||||
pub fn parts_mut(&mut self) -> (&mut HttpRequest, &mut Payload) {
|
||||
(&mut self.req, &mut self.payload)
|
||||
}
|
||||
|
||||
/// Construct request from parts.
|
||||
pub fn from_parts(req: HttpRequest, payload: Payload) -> Self {
|
||||
Self { req, payload }
|
||||
|
@ -655,6 +655,8 @@ mod tests {
|
|||
assert_eq!(resp.status(), http::StatusCode::NOT_FOUND);
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_service_data() {
|
||||
let srv =
|
||||
|
|
|
@ -839,6 +839,8 @@ mod tests {
|
|||
assert!(res.status().is_success());
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_server_data() {
|
||||
async fn handler(data: web::Data<usize>) -> impl Responder {
|
||||
|
|
|
@ -16,7 +16,7 @@ use futures_core::{future::LocalBoxFuture, ready};
|
|||
use futures_util::{FutureExt as _, StreamExt as _};
|
||||
use serde::{de::DeserializeOwned, Serialize};
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
use crate::dev::Decompress;
|
||||
use crate::{
|
||||
error::UrlencodedError, extract::FromRequest, http::header::CONTENT_LENGTH, web, Error,
|
||||
|
@ -80,6 +80,10 @@ use crate::{
|
|||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # Panics
|
||||
/// URL encoded forms consist of unordered `key=value` pairs, therefore they cannot be decoded into
|
||||
/// any type which depends upon data ordering (eg. tuples). Trying to do so will result in a panic.
|
||||
#[derive(PartialEq, Eq, PartialOrd, Ord, Debug)]
|
||||
pub struct Form<T>(pub T);
|
||||
|
||||
|
@ -255,9 +259,9 @@ impl Default for FormConfig {
|
|||
/// - content type is not `application/x-www-form-urlencoded`
|
||||
/// - content length is greater than [limit](UrlEncoded::limit())
|
||||
pub struct UrlEncoded<T> {
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
stream: Option<Decompress<Payload>>,
|
||||
#[cfg(not(feature = "compress"))]
|
||||
#[cfg(not(feature = "__compress"))]
|
||||
stream: Option<Payload>,
|
||||
|
||||
limit: usize,
|
||||
|
@ -293,10 +297,15 @@ impl<T> UrlEncoded<T> {
|
|||
}
|
||||
};
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
let payload = Decompress::from_headers(payload.take(), req.headers());
|
||||
#[cfg(not(feature = "compress"))]
|
||||
let payload = payload.take();
|
||||
let payload = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "__compress")] {
|
||||
Decompress::from_headers(payload.take(), req.headers())
|
||||
} else {
|
||||
payload.take()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
UrlEncoded {
|
||||
encoding,
|
||||
|
|
|
@ -16,7 +16,7 @@ use serde::{de::DeserializeOwned, Serialize};
|
|||
|
||||
use actix_http::Payload;
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
use crate::dev::Decompress;
|
||||
use crate::{
|
||||
error::{Error, JsonPayloadError},
|
||||
|
@ -300,9 +300,9 @@ pub enum JsonBody<T> {
|
|||
Body {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
payload: Decompress<Payload>,
|
||||
#[cfg(not(feature = "compress"))]
|
||||
#[cfg(not(feature = "__compress"))]
|
||||
payload: Payload,
|
||||
buf: BytesMut,
|
||||
_res: PhantomData<T>,
|
||||
|
@ -345,10 +345,15 @@ where
|
|||
// As the internal usage always call JsonBody::limit after JsonBody::new.
|
||||
// And limit check to return an error variant of JsonBody happens there.
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
let payload = Decompress::from_headers(payload.take(), req.headers());
|
||||
#[cfg(not(feature = "compress"))]
|
||||
let payload = payload.take();
|
||||
let payload = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "__compress")] {
|
||||
Decompress::from_headers(payload.take(), req.headers())
|
||||
} else {
|
||||
payload.take()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
JsonBody::Body {
|
||||
limit: DEFAULT_LIMIT,
|
||||
|
|
|
@ -282,9 +282,9 @@ impl Default for PayloadConfig {
|
|||
pub struct HttpMessageBody {
|
||||
limit: usize,
|
||||
length: Option<usize>,
|
||||
#[cfg(feature = "compress")]
|
||||
#[cfg(feature = "__compress")]
|
||||
stream: dev::Decompress<dev::Payload>,
|
||||
#[cfg(not(feature = "compress"))]
|
||||
#[cfg(not(feature = "__compress"))]
|
||||
stream: dev::Payload,
|
||||
buf: BytesMut,
|
||||
err: Option<PayloadError>,
|
||||
|
@ -312,10 +312,15 @@ impl HttpMessageBody {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "compress")]
|
||||
let stream = dev::Decompress::from_headers(payload.take(), req.headers());
|
||||
#[cfg(not(feature = "compress"))]
|
||||
let stream = payload.take();
|
||||
let stream = {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(feature = "__compress")] {
|
||||
dev::Decompress::from_headers(payload.take(), req.headers())
|
||||
} else {
|
||||
payload.take()
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
HttpMessageBody {
|
||||
stream,
|
||||
|
@ -393,6 +398,8 @@ mod tests {
|
|||
assert!(cfg.check_mimetype(&req).is_ok());
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_config_recall_locations() {
|
||||
async fn bytes_handler(_: Bytes) -> impl Responder {
|
||||
|
|
|
@ -0,0 +1,100 @@
|
|||
use actix_utils::future::{ok, Ready};
|
||||
use actix_web::dev::{Service, ServiceRequest, ServiceResponse, Transform};
|
||||
use actix_web::test::{call_service, init_service, TestRequest};
|
||||
use actix_web::{HttpResponse, ResponseError};
|
||||
use futures_util::lock::Mutex;
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct MyError;
|
||||
|
||||
impl ResponseError for MyError {}
|
||||
|
||||
impl std::fmt::Display for MyError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "A custom error")
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_web::get("/test")]
|
||||
async fn test() -> Result<actix_web::HttpResponse, actix_web::error::Error> {
|
||||
Err(MyError)?;
|
||||
Ok(HttpResponse::NoContent().finish())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct SpyMiddleware(Arc<Mutex<Option<bool>>>);
|
||||
|
||||
impl<S, B> Transform<S, ServiceRequest> for SpyMiddleware
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = actix_web::Error;
|
||||
type Transform = Middleware<S>;
|
||||
type InitError = ();
|
||||
type Future = Ready<Result<Self::Transform, Self::InitError>>;
|
||||
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ok(Middleware {
|
||||
was_error: self.0.clone(),
|
||||
service,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct Middleware<S> {
|
||||
was_error: Arc<Mutex<Option<bool>>>,
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S, B> Service<ServiceRequest> for Middleware<S>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = actix_web::Error>,
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = actix_web::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.service.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn call(&self, req: ServiceRequest) -> Self::Future {
|
||||
let lock = self.was_error.clone();
|
||||
let response_future = self.service.call(req);
|
||||
Box::pin(async move {
|
||||
let response = response_future.await;
|
||||
if let Ok(success) = &response {
|
||||
*lock.lock().await = Some(success.response().error().is_some());
|
||||
}
|
||||
response
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn error_cause_should_be_propagated_to_middlewares() {
|
||||
let lock = Arc::new(Mutex::new(None));
|
||||
let spy_middleware = SpyMiddleware(lock.clone());
|
||||
|
||||
let app = init_service(
|
||||
actix_web::App::new()
|
||||
.wrap(spy_middleware.clone())
|
||||
.service(test),
|
||||
)
|
||||
.await;
|
||||
|
||||
call_service(&app, TestRequest::with_uri("/test").to_request()).await;
|
||||
|
||||
let was_error_captured = lock.lock().await.unwrap();
|
||||
assert!(was_error_captured);
|
||||
}
|
|
@ -879,7 +879,7 @@ async fn test_brotli_encoding_large_openssl() {
|
|||
assert_eq!(bytes, Bytes::from(data));
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "rustls", feature = "openssl"))]
|
||||
#[cfg(feature = "rustls")]
|
||||
mod plus_rustls {
|
||||
use std::io::BufReader;
|
||||
|
||||
|
@ -1028,6 +1028,8 @@ async fn test_normalize() {
|
|||
assert!(response.status().is_success());
|
||||
}
|
||||
|
||||
// allow deprecated App::data
|
||||
#[allow(deprecated)]
|
||||
#[actix_rt::test]
|
||||
async fn test_data_drop() {
|
||||
use std::sync::{
|
||||
|
|
Loading…
Reference in New Issue