mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-19 12:25:37 +02:00
Compare commits
7 Commits
test-v0.1.
...
body-ergo-
Author | SHA1 | Date | |
---|---|---|---|
|
8d2abe4b35 | ||
|
f12f62ba73 | ||
|
fb5b4734a4 | ||
|
4d9dd30c72 | ||
|
d7a1b434cb | ||
|
d5ad250193 | ||
|
818c0f8cad |
30
CHANGES.md
30
CHANGES.md
@@ -1,34 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.0-beta.15 - 2021-12-17
|
||||
### Added
|
||||
* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510]
|
||||
* Implement `Debug` for `DefaultHeaders`. [#2510]
|
||||
|
||||
### Changed
|
||||
* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510]
|
||||
* Response service types in `ErrorHandlers` middleware now use `ServiceResponse<EitherBody<B>>` to allow changing the body type. [#2515]
|
||||
* Both variants in `ErrorHandlerResponse` now use `ServiceResponse<EitherBody<B>>`. [#2515]
|
||||
* Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518]
|
||||
* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518]
|
||||
* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518]
|
||||
* Relax body type and error bounds on test utilities.
|
||||
|
||||
### Removed
|
||||
* Top-level `EitherExtractError` export. [#2510]
|
||||
* Conversion implementations for `either` crate. [#2516]
|
||||
* `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518]
|
||||
|
||||
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||
[#2515]: https://github.com/actix/actix-web/pull/2515
|
||||
[#2516]: https://github.com/actix/actix-web/pull/2516
|
||||
[#2518]: https://github.com/actix/actix-web/pull/2518
|
||||
|
||||
|
||||
## 4.0.0-beta.14 - 2021-12-11
|
||||
### Added
|
||||
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
|
||||
* `AcceptEncoding` typed header. [#2482]
|
||||
@@ -37,7 +9,6 @@
|
||||
* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||
* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491]
|
||||
* `HttpRequest::{req_data,req_data_mut}`. [#2487]
|
||||
* `ServiceResponse::into_parts`. [#2499]
|
||||
|
||||
### Changed
|
||||
* Rename `Accept::{mime_precedence => ranked}`. [#2480]
|
||||
@@ -66,7 +37,6 @@
|
||||
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||
[#2492]: https://github.com/actix/actix-web/pull/2492
|
||||
[#2493]: https://github.com/actix/actix-web/pull/2493
|
||||
[#2499]: https://github.com/actix/actix-web/pull/2499
|
||||
|
||||
|
||||
## 4.0.0-beta.13 - 2021-11-30
|
||||
|
14
Cargo.toml
14
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.0.0-beta.15"
|
||||
version = "4.0.0-beta.13"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
@@ -77,15 +77,16 @@ actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
|
||||
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-router = "0.5.0-beta.3"
|
||||
actix-web-codegen = "0.5.0-beta.6"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-router = "0.5.0-beta.2"
|
||||
actix-web-codegen = "0.5.0-beta.5"
|
||||
|
||||
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"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
@@ -106,8 +107,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
||||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.14", features = ["openssl"] }
|
||||
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.11", features = ["openssl"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
@@ -117,6 +118,7 @@ futures-util = { version = "0.3.7", default-features = false, features = ["std"]
|
||||
rand = "0.8"
|
||||
rcgen = "0.8"
|
||||
rustls-pemfile = "0.2"
|
||||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
zstd = "0.9"
|
||||
|
@@ -6,10 +6,10 @@
|
||||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.0.0-beta.15)
|
||||
[](https://docs.rs/actix-web/4.0.0-beta.13)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.15)
|
||||
[](https://deps.rs/crate/actix-web/4.0.0-beta.13)
|
||||
<br />
|
||||
[](https://github.com/actix/actix-web/actions)
|
||||
[](https://codecov.io/gh/actix/actix-web)
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.6.0-beta.10 - 2021-12-11
|
||||
* No significant changes since `0.6.0-beta.9`.
|
||||
|
||||
|
||||
## 0.6.0-beta.9 - 2021-11-22
|
||||
* Add crate feature `experimental-io-uring`, enabling async file I/O to be utilized. This feature is only available on Linux OSes with recent kernel versions. This feature is semver-exempt. [#2408]
|
||||
* Add `NamedFile::open_async`. [#2408]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-files"
|
||||
version = "0.6.0-beta.10"
|
||||
version = "0.6.0-beta.9"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
@@ -22,17 +22,17 @@ path = "src/lib.rs"
|
||||
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
|
||||
|
||||
[dependencies]
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-service = "2"
|
||||
actix-utils = "3"
|
||||
actix-web = { version = "4.0.0-beta.15", default-features = false }
|
||||
|
||||
askama_escape = "0.10"
|
||||
bitflags = "1"
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
http-range = "0.1.4"
|
||||
derive_more = "0.99.5"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
mime_guess = "2.0.1"
|
||||
@@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.9"
|
||||
actix-web = "4.0.0-beta.15"
|
||||
actix-web = "4.0.0-beta.11"
|
||||
actix-test = "0.1.0-beta.7"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Static file serving for Actix Web
|
||||
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://docs.rs/actix-files/0.6.0-beta.10)
|
||||
[](https://docs.rs/actix-files/0.6.0-beta.9)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.10)
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.9)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -2,10 +2,14 @@ use std::{
|
||||
fmt,
|
||||
fs::Metadata,
|
||||
io,
|
||||
ops::{Deref, DerefMut},
|
||||
path::{Path, PathBuf},
|
||||
time::{SystemTime, UNIX_EPOCH},
|
||||
};
|
||||
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt;
|
||||
|
||||
use actix_service::{Service, ServiceFactory};
|
||||
use actix_web::{
|
||||
body::{self, BoxBody, SizedStream},
|
||||
@@ -23,7 +27,6 @@ use actix_web::{
|
||||
Error, HttpMessage, HttpRequest, HttpResponse, Responder,
|
||||
};
|
||||
use bitflags::bitflags;
|
||||
use derive_more::{Deref, DerefMut};
|
||||
use futures_core::future::LocalBoxFuture;
|
||||
use mime_guess::from_path;
|
||||
|
||||
@@ -68,11 +71,8 @@ impl Default for Flags {
|
||||
/// NamedFile::open_async("./static/index.html").await
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Deref, DerefMut)]
|
||||
pub struct NamedFile {
|
||||
path: PathBuf,
|
||||
#[deref]
|
||||
#[deref_mut]
|
||||
file: File,
|
||||
modified: Option<SystemTime>,
|
||||
pub(crate) md: Metadata,
|
||||
@@ -364,18 +364,14 @@ impl NamedFile {
|
||||
self
|
||||
}
|
||||
|
||||
/// Creates a etag in a format is similar to Apache's.
|
||||
pub(crate) fn etag(&self) -> Option<header::EntityTag> {
|
||||
// This etag format is similar to Apache's.
|
||||
self.modified.as_ref().map(|mtime| {
|
||||
let ino = {
|
||||
#[cfg(unix)]
|
||||
{
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::fs::MetadataExt as _;
|
||||
|
||||
self.md.ino()
|
||||
}
|
||||
|
||||
#[cfg(not(unix))]
|
||||
{
|
||||
0
|
||||
@@ -476,17 +472,17 @@ impl NamedFile {
|
||||
false
|
||||
};
|
||||
|
||||
let mut res = HttpResponse::build(self.status_code);
|
||||
let mut resp = HttpResponse::build(self.status_code);
|
||||
|
||||
if self.flags.contains(Flags::PREFER_UTF8) {
|
||||
let ct = equiv_utf8_text(self.content_type.clone());
|
||||
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
|
||||
} else {
|
||||
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||
resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
|
||||
}
|
||||
|
||||
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
|
||||
res.insert_header((
|
||||
resp.insert_header((
|
||||
header::CONTENT_DISPOSITION,
|
||||
self.content_disposition.to_string(),
|
||||
));
|
||||
@@ -494,18 +490,18 @@ impl NamedFile {
|
||||
|
||||
// default compressing
|
||||
if let Some(current_encoding) = self.encoding {
|
||||
res.encoding(current_encoding);
|
||||
resp.encoding(current_encoding);
|
||||
}
|
||||
|
||||
if let Some(lm) = last_modified {
|
||||
res.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
||||
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
|
||||
}
|
||||
|
||||
if let Some(etag) = etag {
|
||||
res.insert_header((header::ETAG, etag.to_string()));
|
||||
resp.insert_header((header::ETAG, etag.to_string()));
|
||||
}
|
||||
|
||||
res.insert_header((header::ACCEPT_RANGES, "bytes"));
|
||||
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
|
||||
|
||||
let mut length = self.md.len();
|
||||
let mut offset = 0;
|
||||
@@ -517,24 +513,24 @@ impl NamedFile {
|
||||
length = ranges[0].length;
|
||||
offset = ranges[0].start;
|
||||
|
||||
res.encoding(ContentEncoding::Identity);
|
||||
res.insert_header((
|
||||
resp.encoding(ContentEncoding::Identity);
|
||||
resp.insert_header((
|
||||
header::CONTENT_RANGE,
|
||||
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
|
||||
));
|
||||
} else {
|
||||
res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
||||
return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||
resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
|
||||
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
|
||||
};
|
||||
} else {
|
||||
return res.status(StatusCode::BAD_REQUEST).finish();
|
||||
return resp.status(StatusCode::BAD_REQUEST).finish();
|
||||
};
|
||||
};
|
||||
|
||||
if precondition_failed {
|
||||
return res.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
|
||||
} else if not_modified {
|
||||
return res
|
||||
return resp
|
||||
.status(StatusCode::NOT_MODIFIED)
|
||||
.body(body::None::new())
|
||||
.map_into_boxed_body();
|
||||
@@ -543,10 +539,10 @@ impl NamedFile {
|
||||
let reader = chunked::new_chunked_read(length, offset, self.file);
|
||||
|
||||
if offset != 0 || length != self.md.len() {
|
||||
res.status(StatusCode::PARTIAL_CONTENT);
|
||||
resp.status(StatusCode::PARTIAL_CONTENT);
|
||||
}
|
||||
|
||||
res.body(SizedStream::new(length, reader))
|
||||
resp.body(SizedStream::new(length, reader))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -590,6 +586,20 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
|
||||
}
|
||||
}
|
||||
|
||||
impl Deref for NamedFile {
|
||||
type Target = File;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl DerefMut for NamedFile {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.file
|
||||
}
|
||||
}
|
||||
|
||||
impl Responder for NamedFile {
|
||||
type Body = BoxBody;
|
||||
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.9 - 2021-12-11
|
||||
* No significant changes since `3.0.0-beta.8`.
|
||||
|
||||
|
||||
## 3.0.0-beta.8 - 2021-11-30
|
||||
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "3.0.0-beta.9"
|
||||
version = "3.0.0-beta.8"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Various helpers for Actix applications to use during testing"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@@ -35,7 +35,7 @@ actix-tls = "3.0.0-rc.1"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2.0.0-rc.1"
|
||||
awc = { version = "3.0.0-beta.14", default-features = false }
|
||||
awc = { version = "3.0.0-beta.11", default-features = false }
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
@@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tokio = { version = "1.2", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-beta.14"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.9)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.8)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br>
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.9)
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.8)
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -1,25 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.16 - 2021-12-17
|
||||
### Added
|
||||
* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522]
|
||||
|
||||
### Changed
|
||||
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
|
||||
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
|
||||
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
|
||||
|
||||
### Removed
|
||||
* `MessageBody::{is_complete_body,take_complete_body}`. [#2522]
|
||||
|
||||
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||
[#2522]: https://github.com/actix/actix-web/pull/2522
|
||||
|
||||
|
||||
## 3.0.0-beta.15 - 2021-12-11
|
||||
### Added
|
||||
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
||||
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
||||
@@ -37,8 +18,6 @@
|
||||
* `Request::take_conn_data()`. [#2491]
|
||||
* `Request::take_req_data()`. [#2487]
|
||||
* `impl Clone` for `RequestHead`. [#2487]
|
||||
* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497]
|
||||
* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
|
||||
|
||||
### Changed
|
||||
* Rename `body::BoxBody::{from_body => new}`. [#2468]
|
||||
@@ -66,8 +45,6 @@
|
||||
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||
[#2488]: https://github.com/actix/actix-web/pull/2488
|
||||
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||
[#2520]: https://github.com/actix/actix-web/pull/2520
|
||||
|
||||
|
||||
## 3.0.0-beta.14 - 2021-11-30
|
||||
@@ -278,7 +255,7 @@
|
||||
|
||||
## 3.0.0-beta.2 - 2021-02-10
|
||||
### Added
|
||||
* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
|
||||
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
|
||||
* `ResponseBuilder::append_header` method which allows using typed headers. [#1869]
|
||||
* `TestRequest::insert_header` method which allows using typed headers. [#1869]
|
||||
@@ -289,9 +266,9 @@
|
||||
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
|
||||
|
||||
### Changed
|
||||
* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
|
||||
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
|
||||
`mime` types. [#1894]
|
||||
* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
|
||||
`TryInto` trait. [#1894]
|
||||
* `Extensions::insert` returns Option of replaced item. [#1904]
|
||||
* Remove `HttpResponseBuilder::json2()`. [#1903]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.0-beta.16"
|
||||
version = "3.0.0-beta.14"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "HTTP primitives for the Actix ecosystem"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
@@ -45,7 +45,7 @@ __compress = []
|
||||
actix-service = "2.0.0"
|
||||
actix-codec = "0.4.1"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = { version = "2.2", default-features = false }
|
||||
actix-rt = "2.2"
|
||||
|
||||
ahash = "0.7"
|
||||
base64 = "0.13"
|
||||
@@ -55,8 +55,8 @@ bytestring = "1"
|
||||
derive_more = "0.99.5"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
h2 = "0.3.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||
h2 = "0.3.1"
|
||||
http = "0.2.5"
|
||||
httparse = "1.5.1"
|
||||
httpdate = "1.0.1"
|
||||
@@ -66,6 +66,7 @@ local-channel = "0.1"
|
||||
log = "0.4"
|
||||
mime = "0.3"
|
||||
percent-encoding = "2.1"
|
||||
pin-project = "1.0.0"
|
||||
pin-project-lite = "0.2"
|
||||
rand = "0.8"
|
||||
sha-1 = "0.9"
|
||||
@@ -80,15 +81,13 @@ flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.9", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
|
||||
actix-web = "4.0.0-beta.15"
|
||||
|
||||
actix-web = "4.0.0-beta.13"
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
rcgen = "0.8"
|
||||
regex = "1.3"
|
||||
rustls-pemfile = "0.2"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.0.0-beta.16)
|
||||
[](https://docs.rs/actix-http/3.0.0-beta.14)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.16)
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.14)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -27,7 +27,6 @@ where
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(stream: S) -> Self {
|
||||
BodyStream { stream }
|
||||
}
|
||||
@@ -40,7 +39,6 @@ where
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Stream
|
||||
}
|
||||
|
@@ -8,97 +8,48 @@ use std::{
|
||||
use bytes::Bytes;
|
||||
|
||||
use super::{BodySize, MessageBody, MessageBodyMapErr};
|
||||
use crate::body;
|
||||
use crate::Error;
|
||||
|
||||
/// A boxed message body with boxed errors.
|
||||
#[derive(Debug)]
|
||||
pub struct BoxBody(BoxBodyInner);
|
||||
|
||||
enum BoxBodyInner {
|
||||
None(body::None),
|
||||
Bytes(Bytes),
|
||||
Stream(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>),
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxBodyInner {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
match self {
|
||||
Self::None(arg0) => f.debug_tuple("None").field(arg0).finish(),
|
||||
Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(),
|
||||
Self::Stream(_) => f.debug_tuple("Stream").field(&"dyn MessageBody").finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
|
||||
|
||||
impl BoxBody {
|
||||
/// Same as `MessageBody::boxed`.
|
||||
///
|
||||
/// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to
|
||||
/// avoid double boxing.
|
||||
#[inline]
|
||||
/// Boxes a `MessageBody` and any errors it generates.
|
||||
pub fn new<B>(body: B) -> Self
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
match body.size() {
|
||||
BodySize::None => Self(BoxBodyInner::None(body::None)),
|
||||
_ => match body.try_into_bytes() {
|
||||
Ok(bytes) => Self(BoxBodyInner::Bytes(bytes)),
|
||||
Err(body) => {
|
||||
let body = MessageBodyMapErr::new(body, Into::into);
|
||||
Self(BoxBodyInner::Stream(Box::pin(body)))
|
||||
}
|
||||
},
|
||||
}
|
||||
let body = MessageBodyMapErr::new(body, Into::into);
|
||||
Self(Box::pin(body))
|
||||
}
|
||||
|
||||
/// Returns a mutable pinned reference to the inner message body type.
|
||||
#[inline]
|
||||
pub fn as_pin_mut(&mut self) -> Pin<&mut Self> {
|
||||
Pin::new(self)
|
||||
pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for BoxBody {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("BoxBody(dyn MessageBody)")
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for BoxBody {
|
||||
type Error = Box<dyn StdError>;
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
match &self.0 {
|
||||
BoxBodyInner::None(none) => none.size(),
|
||||
BoxBodyInner::Bytes(bytes) => bytes.size(),
|
||||
BoxBodyInner::Stream(stream) => stream.size(),
|
||||
}
|
||||
self.0.size()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match &mut self.0 {
|
||||
BoxBodyInner::None(body) => {
|
||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||
}
|
||||
BoxBodyInner::Bytes(body) => {
|
||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||
}
|
||||
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
match self.0 {
|
||||
BoxBodyInner::None(body) => Ok(body.try_into_bytes().unwrap()),
|
||||
BoxBodyInner::Bytes(body) => Ok(body.try_into_bytes().unwrap()),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn boxed(self) -> BoxBody {
|
||||
self
|
||||
self.0
|
||||
.as_mut()
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err))
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -23,7 +23,6 @@ pin_project! {
|
||||
|
||||
impl<L> EitherBody<L, BoxBody> {
|
||||
/// Creates new `EitherBody` using left variant and boxed right variant.
|
||||
#[inline]
|
||||
pub fn new(body: L) -> Self {
|
||||
Self::Left { body }
|
||||
}
|
||||
@@ -31,13 +30,11 @@ impl<L> EitherBody<L, BoxBody> {
|
||||
|
||||
impl<L, R> EitherBody<L, R> {
|
||||
/// Creates new `EitherBody` using left variant.
|
||||
#[inline]
|
||||
pub fn left(body: L) -> Self {
|
||||
Self::Left { body }
|
||||
}
|
||||
|
||||
/// Creates new `EitherBody` using right variant.
|
||||
#[inline]
|
||||
pub fn right(body: R) -> Self {
|
||||
Self::Right { body }
|
||||
}
|
||||
@@ -50,7 +47,6 @@ where
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
EitherBody::Left { body } => body.size(),
|
||||
@@ -58,7 +54,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
@@ -72,26 +67,6 @@ where
|
||||
.map_err(|err| Error::new_body().with_cause(err)),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
match self {
|
||||
EitherBody::Left { body } => body
|
||||
.try_into_bytes()
|
||||
.map_err(|body| EitherBody::Left { body }),
|
||||
EitherBody::Right { body } => body
|
||||
.try_into_bytes()
|
||||
.map_err(|body| EitherBody::Right { body }),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn boxed(self) -> BoxBody {
|
||||
match self {
|
||||
EitherBody::Left { body } => body.boxed(),
|
||||
EitherBody::Right { body } => body.boxed(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -12,56 +12,23 @@ use bytes::{Bytes, BytesMut};
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{BodySize, BoxBody};
|
||||
use super::BodySize;
|
||||
|
||||
/// An interface types that can converted to bytes and used as response bodies.
|
||||
// TODO: examples
|
||||
pub trait MessageBody {
|
||||
/// The type of error that will be returned if streaming body fails.
|
||||
///
|
||||
/// Since it is not appropriate to generate a response mid-stream, it only requires `Error` for
|
||||
/// internal use and logging.
|
||||
type Error: Into<Box<dyn StdError>>;
|
||||
// TODO: consider this bound to only fmt::Display since the error type is not really used
|
||||
// and there is an impl for Into<Box<StdError>> on String
|
||||
type Error: Into<Box<dyn StdError>> + 'static;
|
||||
|
||||
/// Body size hint.
|
||||
///
|
||||
/// If [`BodySize::None`] is returned, optimizations that skip reading the body are allowed.
|
||||
fn size(&self) -> BodySize;
|
||||
|
||||
/// Attempt to pull out the next chunk of body bytes.
|
||||
// TODO: expand documentation
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
||||
|
||||
/// Try to convert into the complete chunk of body bytes.
|
||||
///
|
||||
/// Implement this method if the entire body can be trivially extracted. This is useful for
|
||||
/// optimizations where `poll_next` calls can be avoided.
|
||||
///
|
||||
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling
|
||||
/// this method, it is recommended to check `size` first and return early.
|
||||
///
|
||||
/// # Errors
|
||||
/// The default implementation will error and return the original type back to the caller for
|
||||
/// further use.
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
Err(self)
|
||||
}
|
||||
|
||||
/// Converts this body into `BoxBody`.
|
||||
#[inline]
|
||||
fn boxed(self) -> BoxBody
|
||||
where
|
||||
Self: Sized + 'static,
|
||||
{
|
||||
BoxBody::new(self)
|
||||
}
|
||||
}
|
||||
|
||||
mod foreign_impls {
|
||||
@@ -70,10 +37,12 @@ mod foreign_impls {
|
||||
impl MessageBody for Infallible {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -97,16 +66,11 @@ mod foreign_impls {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(Bytes::new())
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Box<B>
|
||||
where
|
||||
B: MessageBody + Unpin + ?Sized,
|
||||
B: MessageBody + Unpin,
|
||||
{
|
||||
type Error = B::Error;
|
||||
|
||||
@@ -126,7 +90,7 @@ mod foreign_impls {
|
||||
|
||||
impl<B> MessageBody for Pin<Box<B>>
|
||||
where
|
||||
B: MessageBody + ?Sized,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Error = B::Error;
|
||||
|
||||
@@ -137,22 +101,20 @@ mod foreign_impls {
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
self.get_mut().as_mut().poll_next(cx)
|
||||
self.as_mut().poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static [u8] {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -160,25 +122,20 @@ mod foreign_impls {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(Bytes::from_static(mem::take(self.get_mut())))))
|
||||
let bytes = mem::take(self.get_mut());
|
||||
let bytes = Bytes::from_static(bytes);
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(Bytes::from_static(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Bytes {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -186,25 +143,19 @@ mod foreign_impls {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
|
||||
let bytes = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for BytesMut {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -212,25 +163,19 @@ mod foreign_impls {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
|
||||
let bytes = mem::take(self.get_mut()).freeze();
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(self.freeze())
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Vec<u8> {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -238,25 +183,19 @@ mod foreign_impls {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(mem::take(self.get_mut()).into())))
|
||||
let bytes = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(Bytes::from(bytes))))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(Bytes::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static str {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -269,22 +208,15 @@ mod foreign_impls {
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(Bytes::from_static(self.as_bytes()))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for String {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -296,22 +228,15 @@ mod foreign_impls {
|
||||
Poll::Ready(Some(Ok(Bytes::from(string))))
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(Bytes::from(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for bytestring::ByteString {
|
||||
type Error = Infallible;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.len() as u64)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
@@ -319,11 +244,6 @@ mod foreign_impls {
|
||||
let string = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(string.into_bytes())))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(self.into_bytes())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -352,11 +272,10 @@ impl<B, F, E> MessageBody for MessageBodyMapErr<B, F>
|
||||
where
|
||||
B: MessageBody,
|
||||
F: FnOnce(B::Error) -> E,
|
||||
E: Into<Box<dyn StdError>>,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
self.body.size()
|
||||
}
|
||||
@@ -377,12 +296,6 @@ where
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
let Self { body, mapper } = self;
|
||||
body.try_into_bytes().map_err(|body| Self { body, mapper })
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -392,7 +305,8 @@ mod tests {
|
||||
use bytes::{Bytes, BytesMut};
|
||||
|
||||
use super::*;
|
||||
use crate::body::{self, EitherBody};
|
||||
|
||||
// static_assertions::assert_obj_safe!(MessageBody<()>);
|
||||
|
||||
macro_rules! assert_poll_next {
|
||||
($pin:expr, $exp:expr) => {
|
||||
@@ -494,47 +408,6 @@ mod tests {
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn complete_body_combinators() {
|
||||
let body = Bytes::from_static(b"test");
|
||||
let body = BoxBody::new(body);
|
||||
let body = EitherBody::<_, ()>::left(body);
|
||||
let body = EitherBody::<(), _>::right(body);
|
||||
// Do not support try_into_bytes:
|
||||
// let body = Box::new(body);
|
||||
// let body = Box::pin(body);
|
||||
|
||||
assert_eq!(body.try_into_bytes().unwrap(), Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn complete_body_combinators_poll() {
|
||||
let body = Bytes::from_static(b"test");
|
||||
let body = BoxBody::new(body);
|
||||
let body = EitherBody::<_, ()>::left(body);
|
||||
let body = EitherBody::<(), _>::right(body);
|
||||
let mut body = body;
|
||||
|
||||
assert_eq!(body.size(), BodySize::Sized(4));
|
||||
assert_poll_next!(Pin::new(&mut body), Bytes::from("test"));
|
||||
assert_poll_next_none!(Pin::new(&mut body));
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn none_body_combinators() {
|
||||
fn none_body() -> BoxBody {
|
||||
let body = body::None;
|
||||
let body = BoxBody::new(body);
|
||||
let body = EitherBody::<_, ()>::left(body);
|
||||
let body = EitherBody::<(), _>::right(body);
|
||||
body.boxed()
|
||||
}
|
||||
|
||||
assert_eq!(none_body().size(), BodySize::None);
|
||||
assert_eq!(none_body().try_into_bytes().unwrap(), Bytes::new());
|
||||
assert_poll_next_none!(Pin::new(&mut none_body()));
|
||||
}
|
||||
|
||||
// down-casting used to be done with a method on MessageBody trait
|
||||
// test is kept to demonstrate equivalence of Any trait
|
||||
#[actix_rt::test]
|
||||
|
@@ -1,5 +1,6 @@
|
||||
//! Traits and structures to aid consuming and writing HTTP payloads.
|
||||
|
||||
// mod any;
|
||||
mod body_stream;
|
||||
mod boxed;
|
||||
mod either;
|
||||
@@ -9,6 +10,7 @@ mod size;
|
||||
mod sized_stream;
|
||||
mod utils;
|
||||
|
||||
// pub use self::any::AnyBody;
|
||||
pub use self::body_stream::BodyStream;
|
||||
pub use self::boxed::BoxBody;
|
||||
pub use self::either::EitherBody;
|
||||
|
@@ -40,9 +40,4 @@ impl MessageBody for None {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Poll::Ready(Option::None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self> {
|
||||
Ok(Bytes::new())
|
||||
}
|
||||
}
|
||||
|
@@ -1,11 +1,9 @@
|
||||
/// Body size hint.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum BodySize {
|
||||
/// Implicitly empty body.
|
||||
/// Absence of body can be assumed from method or status code.
|
||||
///
|
||||
/// Will omit the Content-Length header. Used for responses to certain methods (e.g., `HEAD`) or
|
||||
/// with particular status codes (e.g., 204 No Content). Consumers that read this as a body size
|
||||
/// hint are allowed to make optimizations that skip reading or writing the payload.
|
||||
/// Will skip writing Content-Length header.
|
||||
None,
|
||||
|
||||
/// Known size body.
|
||||
@@ -20,9 +18,6 @@ pub enum BodySize {
|
||||
}
|
||||
|
||||
impl BodySize {
|
||||
/// Equivalent to `BodySize::Sized(0)`;
|
||||
pub const ZERO: Self = Self::Sized(0);
|
||||
|
||||
/// Returns true if size hint indicates omitted or empty body.
|
||||
///
|
||||
/// Streams will return false because it cannot be known without reading the stream.
|
||||
|
@@ -27,7 +27,6 @@ where
|
||||
S: Stream<Item = Result<Bytes, E>>,
|
||||
E: Into<Box<dyn StdError>> + 'static,
|
||||
{
|
||||
#[inline]
|
||||
pub fn new(size: u64, stream: S) -> Self {
|
||||
SizedStream { size, stream }
|
||||
}
|
||||
@@ -42,7 +41,6 @@ where
|
||||
{
|
||||
type Error = E;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
BodySize::Sized(self.size as u64)
|
||||
}
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! Stream encoders.
|
||||
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
future::Future,
|
||||
io::{self, Write as _},
|
||||
pin::Pin,
|
||||
@@ -10,7 +9,6 @@ use std::{
|
||||
|
||||
use actix_rt::task::{spawn_blocking, JoinHandle};
|
||||
use bytes::Bytes;
|
||||
use derive_more::Display;
|
||||
use futures_core::ready;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
@@ -23,9 +21,9 @@ use flate2::write::{GzEncoder, ZlibEncoder};
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
use zstd::stream::write::Encoder as ZstdEncoder;
|
||||
|
||||
use super::Writer;
|
||||
use super::{EncoderError, Writer};
|
||||
use crate::{
|
||||
body::{self, BodySize, MessageBody},
|
||||
body::{BodySize, MessageBody},
|
||||
error::BlockingError,
|
||||
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
|
||||
ResponseHead, StatusCode,
|
||||
@@ -46,15 +44,22 @@ pin_project! {
|
||||
impl<B: MessageBody> Encoder<B> {
|
||||
fn none() -> Self {
|
||||
Encoder {
|
||||
body: EncoderBody::None {
|
||||
body: body::None::new(),
|
||||
},
|
||||
body: EncoderBody::None,
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn not_acceptable(body: Bytes) -> Self {
|
||||
Encoder {
|
||||
body: EncoderBody::Bytes { body },
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self {
|
||||
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
||||
@@ -62,23 +67,25 @@ impl<B: MessageBody> Encoder<B> {
|
||||
|| encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto);
|
||||
|
||||
// no need to compress an empty body
|
||||
if matches!(body.size(), BodySize::None) {
|
||||
return Self::none();
|
||||
match body.size() {
|
||||
// no need to compress an empty body
|
||||
BodySize::None => return Self::none(),
|
||||
|
||||
// we cannot assume that Sized is not a stream
|
||||
BodySize::Sized(_) | BodySize::Stream => {}
|
||||
}
|
||||
|
||||
let body = match body.try_into_bytes() {
|
||||
Ok(body) => EncoderBody::Full { body },
|
||||
Err(body) => EncoderBody::Stream { body },
|
||||
};
|
||||
// TODO potentially some optimisation for single-chunk responses here by trying to read the
|
||||
// payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
|
||||
|
||||
if can_encode {
|
||||
// Modify response body only if encoder is set
|
||||
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking(false);
|
||||
|
||||
return Encoder {
|
||||
body,
|
||||
body: EncoderBody::Stream { body },
|
||||
encoder: Some(enc),
|
||||
fut: None,
|
||||
eof: false,
|
||||
@@ -87,7 +94,7 @@ impl<B: MessageBody> Encoder<B> {
|
||||
}
|
||||
|
||||
Encoder {
|
||||
body,
|
||||
body: EncoderBody::Stream { body },
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: false,
|
||||
@@ -98,8 +105,8 @@ impl<B: MessageBody> Encoder<B> {
|
||||
pin_project! {
|
||||
#[project = EncoderBodyProj]
|
||||
enum EncoderBody<B> {
|
||||
None { body: body::None },
|
||||
Full { body: Bytes },
|
||||
None,
|
||||
Bytes { body: Bytes },
|
||||
Stream { #[pin] body: B },
|
||||
}
|
||||
}
|
||||
@@ -110,11 +117,10 @@ where
|
||||
{
|
||||
type Error = EncoderError;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
EncoderBody::None { body } => body.size(),
|
||||
EncoderBody::Full { body } => body.size(),
|
||||
EncoderBody::None => BodySize::None,
|
||||
EncoderBody::Bytes { body } => body.size(),
|
||||
EncoderBody::Stream { body } => body.size(),
|
||||
}
|
||||
}
|
||||
@@ -124,10 +130,8 @@ where
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
EncoderBodyProj::None { body } => {
|
||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||
}
|
||||
EncoderBodyProj::Full { body } => {
|
||||
EncoderBodyProj::None => Poll::Ready(None),
|
||||
EncoderBodyProj::Bytes { body } => {
|
||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||
}
|
||||
EncoderBodyProj::Stream { body } => body
|
||||
@@ -135,18 +139,6 @@ where
|
||||
.map_err(|err| EncoderError::Body(err.into())),
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(self) -> Result<Bytes, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
match self {
|
||||
EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()),
|
||||
EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Encoder<B>
|
||||
@@ -155,12 +147,11 @@ where
|
||||
{
|
||||
type Error = EncoderError;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
if self.encoder.is_some() {
|
||||
BodySize::Stream
|
||||
} else {
|
||||
if self.encoder.is_none() {
|
||||
self.body.size()
|
||||
} else {
|
||||
BodySize::Stream
|
||||
}
|
||||
}
|
||||
|
||||
@@ -231,24 +222,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn try_into_bytes(mut self) -> Result<Bytes, Self>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
if self.encoder.is_some() {
|
||||
Err(self)
|
||||
} else {
|
||||
match self.body.try_into_bytes() {
|
||||
Ok(body) => Ok(body),
|
||||
Err(body) => {
|
||||
self.body = body;
|
||||
Err(self)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
@@ -256,8 +229,6 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
header::CONTENT_ENCODING,
|
||||
HeaderValue::from_static(encoding.as_str()),
|
||||
);
|
||||
|
||||
head.no_chunking(false);
|
||||
}
|
||||
|
||||
enum ContentEncoder {
|
||||
@@ -391,32 +362,3 @@ impl ContentEncoder {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
#[non_exhaustive]
|
||||
pub enum EncoderError {
|
||||
#[display(fmt = "body")]
|
||||
Body(Box<dyn StdError>),
|
||||
|
||||
#[display(fmt = "blocking")]
|
||||
Blocking(BlockingError),
|
||||
|
||||
#[display(fmt = "io")]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl StdError for EncoderError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
EncoderError::Body(err) => Some(&**err),
|
||||
EncoderError::Blocking(err) => Some(err),
|
||||
EncoderError::Io(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncoderError> for crate::Error {
|
||||
fn from(err: EncoderError) -> Self {
|
||||
crate::Error::new_encoder().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
@@ -1,22 +1,60 @@
|
||||
//! Content-Encoding support.
|
||||
|
||||
use std::io;
|
||||
use std::{error::Error as StdError, io};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
use derive_more::Display;
|
||||
|
||||
use crate::error::BlockingError;
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
mod decoder;
|
||||
#[cfg(feature = "__compress")]
|
||||
mod encoder;
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use self::decoder::Decoder;
|
||||
#[cfg(feature = "__compress")]
|
||||
pub use self::encoder::Encoder;
|
||||
|
||||
#[derive(Debug, Display)]
|
||||
#[non_exhaustive]
|
||||
pub enum EncoderError {
|
||||
#[display(fmt = "body")]
|
||||
Body(Box<dyn StdError>),
|
||||
|
||||
#[display(fmt = "blocking")]
|
||||
Blocking(BlockingError),
|
||||
|
||||
#[display(fmt = "io")]
|
||||
Io(io::Error),
|
||||
}
|
||||
|
||||
impl StdError for EncoderError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
EncoderError::Body(err) => Some(&**err),
|
||||
EncoderError::Blocking(err) => Some(err),
|
||||
EncoderError::Io(err) => Some(err),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<EncoderError> for crate::Error {
|
||||
fn from(err: EncoderError) -> Self {
|
||||
crate::Error::new_encoder().with_cause(err)
|
||||
}
|
||||
}
|
||||
|
||||
/// Special-purpose writer for streaming (de-)compression.
|
||||
///
|
||||
/// Pre-allocates 8KiB of capacity.
|
||||
#[cfg(feature = "__compress")]
|
||||
pub(self) struct Writer {
|
||||
buf: BytesMut,
|
||||
}
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
impl Writer {
|
||||
fn new() -> Writer {
|
||||
Writer {
|
||||
@@ -29,6 +67,7 @@ impl Writer {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
impl io::Write for Writer {
|
||||
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||
self.buf.extend_from_slice(buf);
|
||||
|
@@ -9,6 +9,8 @@ use crate::{body::BoxBody, ws, Response};
|
||||
|
||||
pub use http::Error as HttpError;
|
||||
|
||||
pub use crate::encoding::EncoderError;
|
||||
|
||||
pub struct Error {
|
||||
inner: Box<ErrorInner>,
|
||||
}
|
||||
@@ -332,28 +334,31 @@ impl From<PayloadError> for Error {
|
||||
}
|
||||
|
||||
/// A set of errors that can occur during dispatching HTTP requests.
|
||||
#[derive(Debug, Display, From)]
|
||||
#[derive(Debug, Display, Error, From)]
|
||||
#[non_exhaustive]
|
||||
pub enum DispatchError {
|
||||
/// Service error.
|
||||
/// Service error
|
||||
// FIXME: display and error type
|
||||
#[display(fmt = "Service Error")]
|
||||
Service(Response<BoxBody>),
|
||||
Service(#[error(not(source))] Response<BoxBody>),
|
||||
|
||||
/// Body streaming error.
|
||||
#[display(fmt = "Body error: {}", _0)]
|
||||
Body(Box<dyn StdError>),
|
||||
/// Body error
|
||||
// FIXME: display and error type
|
||||
#[display(fmt = "Body Error")]
|
||||
Body(#[error(not(source))] Box<dyn StdError>),
|
||||
|
||||
/// Upgrade service error.
|
||||
/// Upgrade service error
|
||||
Upgrade,
|
||||
|
||||
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||
#[display(fmt = "IO error: {}", _0)]
|
||||
Io(io::Error),
|
||||
|
||||
/// Request parse error.
|
||||
#[display(fmt = "Request parse error: {}", _0)]
|
||||
/// Http request parse error.
|
||||
#[display(fmt = "Parse error: {}", _0)]
|
||||
Parse(ParseError),
|
||||
|
||||
/// HTTP/2 error.
|
||||
/// Http/2 error
|
||||
#[display(fmt = "{}", _0)]
|
||||
H2(h2::Error),
|
||||
|
||||
@@ -365,23 +370,21 @@ pub enum DispatchError {
|
||||
#[display(fmt = "Connection shutdown timeout")]
|
||||
DisconnectTimeout,
|
||||
|
||||
/// Internal error.
|
||||
/// Payload is not consumed
|
||||
#[display(fmt = "Task is completed but request's payload is not consumed")]
|
||||
PayloadIsNotConsumed,
|
||||
|
||||
/// Malformed request
|
||||
#[display(fmt = "Malformed request")]
|
||||
MalformedRequest,
|
||||
|
||||
/// Internal error
|
||||
#[display(fmt = "Internal error")]
|
||||
InternalError,
|
||||
}
|
||||
|
||||
impl StdError for DispatchError {
|
||||
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||
match self {
|
||||
// TODO: error source extraction?
|
||||
DispatchError::Service(_res) => None,
|
||||
DispatchError::Body(err) => Some(&**err),
|
||||
DispatchError::Io(err) => Some(err),
|
||||
DispatchError::Parse(err) => Some(err),
|
||||
DispatchError::H2(err) => Some(err),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
/// Unknown error
|
||||
#[display(fmt = "Unknown error")]
|
||||
Unknown,
|
||||
}
|
||||
|
||||
/// A set of error that can occur during parsing content type.
|
||||
|
@@ -15,14 +15,14 @@ use bitflags::bitflags;
|
||||
use bytes::{Buf, BytesMut};
|
||||
use futures_core::ready;
|
||||
use log::{error, trace};
|
||||
use pin_project_lite::pin_project;
|
||||
use pin_project::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
error::{DispatchError, ParseError, PayloadError},
|
||||
service::HttpFlow,
|
||||
Error, Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
};
|
||||
|
||||
use super::{
|
||||
@@ -46,111 +46,79 @@ bitflags! {
|
||||
}
|
||||
}
|
||||
|
||||
// there's 2 versions of Dispatcher state because of:
|
||||
// https://github.com/taiki-e/pin-project-lite/issues/3
|
||||
//
|
||||
// tl;dr: pin-project-lite doesn't play well with other attribute macros
|
||||
#[pin_project]
|
||||
/// Dispatcher for HTTP/1.1 protocol
|
||||
pub struct Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
|
||||
#[cfg(not(test))]
|
||||
pin_project! {
|
||||
/// Dispatcher for HTTP/1.1 protocol
|
||||
pub struct Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
B: MessageBody,
|
||||
|
||||
B: MessageBody,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
inner: DispatcherState<T, S, B, X, U>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
inner: DispatcherState<T, S, B, X, U>,
|
||||
}
|
||||
#[cfg(test)]
|
||||
poll_count: u64,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pin_project! {
|
||||
/// Dispatcher for HTTP/1.1 protocol
|
||||
pub struct Dispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
#[pin_project(project = DispatcherStateProj)]
|
||||
enum DispatcherState<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B: MessageBody,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
#[pin]
|
||||
inner: DispatcherState<T, S, B, X, U>,
|
||||
|
||||
// used in tests
|
||||
poll_count: u64,
|
||||
}
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
|
||||
Upgrade(#[pin] U::Future),
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = DispatcherStateProj]
|
||||
enum DispatcherState<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
#[pin_project(project = InnerDispatcherProj)]
|
||||
struct InnerDispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
|
||||
B: MessageBody,
|
||||
B: MessageBody,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
Normal { #[pin] inner: InnerDispatcher<T, S, B, X, U> },
|
||||
Upgrade { #[pin] fut: U::Future },
|
||||
}
|
||||
}
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
flags: Flags,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
conn_data: Option<Rc<Extensions>>,
|
||||
error: Option<DispatchError>,
|
||||
|
||||
pin_project! {
|
||||
#[project = InnerDispatcherProj]
|
||||
struct InnerDispatcher<T, S, B, X, U>
|
||||
where
|
||||
S: Service<Request>,
|
||||
S::Error: Into<Response<BoxBody>>,
|
||||
#[pin]
|
||||
state: State<S, B, X>,
|
||||
payload: Option<PayloadSender>,
|
||||
messages: VecDeque<DispatcherMessage>,
|
||||
|
||||
B: MessageBody,
|
||||
ka_expire: Instant,
|
||||
#[pin]
|
||||
ka_timer: Option<Sleep>,
|
||||
|
||||
X: Service<Request, Response = Request>,
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
|
||||
U: Service<(Request, Framed<T, Codec>), Response = ()>,
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
flags: Flags,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
conn_data: Option<Rc<Extensions>>,
|
||||
error: Option<DispatchError>,
|
||||
|
||||
#[pin]
|
||||
state: State<S, B, X>,
|
||||
payload: Option<PayloadSender>,
|
||||
messages: VecDeque<DispatcherMessage>,
|
||||
|
||||
ka_expire: Instant,
|
||||
#[pin]
|
||||
ka_timer: Option<Sleep>,
|
||||
|
||||
io: Option<T>,
|
||||
read_buf: BytesMut,
|
||||
write_buf: BytesMut,
|
||||
codec: Codec,
|
||||
}
|
||||
io: Option<T>,
|
||||
read_buf: BytesMut,
|
||||
write_buf: BytesMut,
|
||||
codec: Codec,
|
||||
}
|
||||
|
||||
enum DispatcherMessage {
|
||||
@@ -159,21 +127,19 @@ enum DispatcherMessage {
|
||||
Error(Response<()>),
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
enum State<S, B, X>
|
||||
where
|
||||
S: Service<Request>,
|
||||
X: Service<Request, Response = Request>,
|
||||
#[pin_project(project = StateProj)]
|
||||
enum State<S, B, X>
|
||||
where
|
||||
S: Service<Request>,
|
||||
X: Service<Request, Response = Request>,
|
||||
|
||||
B: MessageBody,
|
||||
{
|
||||
None,
|
||||
ExpectCall { #[pin] fut: X::Future },
|
||||
ServiceCall { #[pin] fut: S::Future },
|
||||
SendPayload { #[pin] body: B },
|
||||
SendErrorPayload { #[pin] body: BoxBody },
|
||||
}
|
||||
B: MessageBody,
|
||||
{
|
||||
None,
|
||||
ExpectCall(#[pin] X::Future),
|
||||
ServiceCall(#[pin] S::Future),
|
||||
SendPayload(#[pin] B),
|
||||
SendErrorPayload(#[pin] BoxBody),
|
||||
}
|
||||
|
||||
impl<S, B, X> State<S, B, X>
|
||||
@@ -232,27 +198,25 @@ where
|
||||
};
|
||||
|
||||
Dispatcher {
|
||||
inner: DispatcherState::Normal {
|
||||
inner: InnerDispatcher {
|
||||
flow,
|
||||
flags,
|
||||
peer_addr,
|
||||
conn_data: conn_data.0.map(Rc::new),
|
||||
error: None,
|
||||
inner: DispatcherState::Normal(InnerDispatcher {
|
||||
flow,
|
||||
flags,
|
||||
peer_addr,
|
||||
conn_data: conn_data.0.map(Rc::new),
|
||||
error: None,
|
||||
|
||||
state: State::None,
|
||||
payload: None,
|
||||
messages: VecDeque::new(),
|
||||
state: State::None,
|
||||
payload: None,
|
||||
messages: VecDeque::new(),
|
||||
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
|
||||
io: Some(io),
|
||||
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
codec: Codec::new(config),
|
||||
},
|
||||
},
|
||||
io: Some(io),
|
||||
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
codec: Codec::new(config),
|
||||
}),
|
||||
|
||||
#[cfg(test)]
|
||||
poll_count: 0,
|
||||
@@ -352,7 +316,7 @@ where
|
||||
let size = self.as_mut().send_response_inner(message, &body)?;
|
||||
let state = match size {
|
||||
BodySize::None | BodySize::Sized(0) => State::None,
|
||||
_ => State::SendPayload { body },
|
||||
_ => State::SendPayload(body),
|
||||
};
|
||||
self.project().state.set(state);
|
||||
Ok(())
|
||||
@@ -366,7 +330,7 @@ where
|
||||
let size = self.as_mut().send_response_inner(message, &body)?;
|
||||
let state = match size {
|
||||
BodySize::None | BodySize::Sized(0) => State::None,
|
||||
_ => State::SendErrorPayload { body },
|
||||
_ => State::SendErrorPayload(body),
|
||||
};
|
||||
self.project().state.set(state);
|
||||
Ok(())
|
||||
@@ -392,12 +356,12 @@ where
|
||||
// Handle `EXPECT: 100-Continue` header
|
||||
if req.head().expect() {
|
||||
// set InnerDispatcher state and continue loop to poll it.
|
||||
let fut = this.flow.expect.call(req);
|
||||
this.state.set(State::ExpectCall { fut });
|
||||
let task = this.flow.expect.call(req);
|
||||
this.state.set(State::ExpectCall(task));
|
||||
} else {
|
||||
// the same as expect call.
|
||||
let fut = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall { fut });
|
||||
let task = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall(task));
|
||||
};
|
||||
}
|
||||
|
||||
@@ -417,7 +381,7 @@ where
|
||||
// all messages are dealt with.
|
||||
None => return Ok(PollResponse::DoNothing),
|
||||
},
|
||||
StateProj::ServiceCall { fut } => match fut.poll(cx) {
|
||||
StateProj::ServiceCall(fut) => match fut.poll(cx) {
|
||||
// service call resolved. send response.
|
||||
Poll::Ready(Ok(res)) => {
|
||||
let (res, body) = res.into().replace_body(());
|
||||
@@ -443,11 +407,11 @@ where
|
||||
}
|
||||
},
|
||||
|
||||
StateProj::SendPayload { mut body } => {
|
||||
StateProj::SendPayload(mut stream) => {
|
||||
// keep populate writer buffer until buffer size limit hit,
|
||||
// get blocked or finished.
|
||||
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||
match body.as_mut().poll_next(cx) {
|
||||
match stream.as_mut().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(item))) => {
|
||||
this.codec
|
||||
.encode(Message::Chunk(Some(item)), this.write_buf)?;
|
||||
@@ -473,13 +437,13 @@ where
|
||||
return Ok(PollResponse::DrainWriteBuf);
|
||||
}
|
||||
|
||||
StateProj::SendErrorPayload { mut body } => {
|
||||
StateProj::SendErrorPayload(mut stream) => {
|
||||
// TODO: de-dupe impl with SendPayload
|
||||
|
||||
// keep populate writer buffer until buffer size limit hit,
|
||||
// get blocked or finished.
|
||||
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||
match body.as_mut().poll_next(cx) {
|
||||
match stream.as_mut().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(item))) => {
|
||||
this.codec
|
||||
.encode(Message::Chunk(Some(item)), this.write_buf)?;
|
||||
@@ -494,9 +458,7 @@ where
|
||||
}
|
||||
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
return Err(DispatchError::Body(
|
||||
Error::new_body().with_cause(err).into(),
|
||||
))
|
||||
return Err(DispatchError::Service(err.into()))
|
||||
}
|
||||
|
||||
Poll::Pending => return Ok(PollResponse::DoNothing),
|
||||
@@ -507,14 +469,14 @@ where
|
||||
return Ok(PollResponse::DrainWriteBuf);
|
||||
}
|
||||
|
||||
StateProj::ExpectCall { fut } => match fut.poll(cx) {
|
||||
StateProj::ExpectCall(fut) => match fut.poll(cx) {
|
||||
// expect resolved. write continue to buffer and set InnerDispatcher state
|
||||
// to service call.
|
||||
Poll::Ready(Ok(req)) => {
|
||||
this.write_buf
|
||||
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
|
||||
let fut = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall { fut });
|
||||
this.state.set(State::ServiceCall(fut));
|
||||
}
|
||||
|
||||
// send expect error as response
|
||||
@@ -540,25 +502,25 @@ where
|
||||
let mut this = self.as_mut().project();
|
||||
if req.head().expect() {
|
||||
// set dispatcher state so the future is pinned.
|
||||
let fut = this.flow.expect.call(req);
|
||||
this.state.set(State::ExpectCall { fut });
|
||||
let task = this.flow.expect.call(req);
|
||||
this.state.set(State::ExpectCall(task));
|
||||
} else {
|
||||
// the same as above.
|
||||
let fut = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall { fut });
|
||||
let task = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall(task));
|
||||
};
|
||||
|
||||
// eagerly poll the future for once(or twice if expect is resolved immediately).
|
||||
loop {
|
||||
match self.as_mut().project().state.project() {
|
||||
StateProj::ExpectCall { fut } => {
|
||||
StateProj::ExpectCall(fut) => {
|
||||
match fut.poll(cx) {
|
||||
// expect is resolved. continue loop and poll the service call branch.
|
||||
Poll::Ready(Ok(req)) => {
|
||||
self.as_mut().send_continue();
|
||||
let mut this = self.as_mut().project();
|
||||
let fut = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall { fut });
|
||||
let task = this.flow.service.call(req);
|
||||
this.state.set(State::ServiceCall(task));
|
||||
continue;
|
||||
}
|
||||
// future is pending. return Ok(()) to notify that a new state is
|
||||
@@ -574,7 +536,7 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
StateProj::ServiceCall { fut } => {
|
||||
StateProj::ServiceCall(fut) => {
|
||||
// return no matter the service call future's result.
|
||||
return match fut.poll(cx) {
|
||||
// future is resolved. send response and return a result. On success
|
||||
@@ -939,7 +901,7 @@ where
|
||||
}
|
||||
|
||||
match this.inner.project() {
|
||||
DispatcherStateProj::Normal { mut inner } => {
|
||||
DispatcherStateProj::Normal(mut inner) => {
|
||||
inner.as_mut().poll_keepalive(cx)?;
|
||||
|
||||
if inner.flags.contains(Flags::SHUTDOWN) {
|
||||
@@ -979,7 +941,7 @@ where
|
||||
self.as_mut()
|
||||
.project()
|
||||
.inner
|
||||
.set(DispatcherState::Upgrade { fut: upgrade });
|
||||
.set(DispatcherState::Upgrade(upgrade));
|
||||
return self.poll(cx);
|
||||
}
|
||||
};
|
||||
@@ -1031,8 +993,8 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| {
|
||||
error!("Upgrade handler error: {}", err);
|
||||
DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
|
||||
error!("Upgrade handler error: {}", e);
|
||||
DispatchError::Upgrade
|
||||
}),
|
||||
}
|
||||
@@ -1126,7 +1088,7 @@ mod tests {
|
||||
Poll::Ready(res) => assert!(res.is_err()),
|
||||
}
|
||||
|
||||
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
|
||||
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
|
||||
assert_eq!(
|
||||
&inner.project().io.take().unwrap().write_buf[..26],
|
||||
@@ -1161,7 +1123,7 @@ mod tests {
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
||||
|
||||
match h1.as_mut().poll(cx) {
|
||||
Poll::Pending => panic!("first poll should not be pending"),
|
||||
@@ -1171,7 +1133,7 @@ mod tests {
|
||||
// polls: initial => shutdown
|
||||
assert_eq!(h1.poll_count, 2);
|
||||
|
||||
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
|
||||
let res = &mut inner.project().io.take().unwrap().write_buf[..];
|
||||
stabilize_date_header(res);
|
||||
|
||||
@@ -1215,7 +1177,7 @@ mod tests {
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
||||
|
||||
match h1.as_mut().poll(cx) {
|
||||
Poll::Pending => panic!("first poll should not be pending"),
|
||||
@@ -1225,7 +1187,7 @@ mod tests {
|
||||
// polls: initial => shutdown
|
||||
assert_eq!(h1.poll_count, 1);
|
||||
|
||||
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
|
||||
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
|
||||
let res = &mut inner.project().io.take().unwrap().write_buf[..];
|
||||
stabilize_date_header(res);
|
||||
|
||||
@@ -1275,13 +1237,13 @@ mod tests {
|
||||
actix_rt::pin!(h1);
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_pending());
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
||||
|
||||
// polls: manual
|
||||
assert_eq!(h1.poll_count, 1);
|
||||
eprintln!("poll count: {}", h1.poll_count);
|
||||
|
||||
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
||||
let io = inner.io.as_ref().unwrap();
|
||||
let res = &io.write_buf()[..];
|
||||
assert_eq!(
|
||||
@@ -1296,7 +1258,7 @@ mod tests {
|
||||
// polls: manual manual shutdown
|
||||
assert_eq!(h1.poll_count, 3);
|
||||
|
||||
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
||||
let io = inner.io.as_ref().unwrap();
|
||||
let mut res = (&io.write_buf()[..]).to_owned();
|
||||
stabilize_date_header(&mut res);
|
||||
@@ -1347,12 +1309,12 @@ mod tests {
|
||||
actix_rt::pin!(h1);
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_ready());
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
|
||||
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
|
||||
|
||||
// polls: manual shutdown
|
||||
assert_eq!(h1.poll_count, 2);
|
||||
|
||||
if let DispatcherState::Normal { ref inner } = h1.inner {
|
||||
if let DispatcherState::Normal(ref inner) = h1.inner {
|
||||
let io = inner.io.as_ref().unwrap();
|
||||
let mut res = (&io.write_buf()[..]).to_owned();
|
||||
stabilize_date_header(&mut res);
|
||||
@@ -1424,7 +1386,7 @@ mod tests {
|
||||
actix_rt::pin!(h1);
|
||||
|
||||
assert!(h1.as_mut().poll(cx).is_ready());
|
||||
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
|
||||
assert!(matches!(&h1.inner, DispatcherState::Upgrade(_)));
|
||||
|
||||
// polls: manual shutdown
|
||||
assert_eq!(h1.poll_count, 2);
|
||||
|
@@ -356,9 +356,9 @@ where
|
||||
type Future = Dispatcher<T, S, B, X, U>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self._poll_ready(cx).map_err(|err| {
|
||||
log::error!("HTTP/1 service readiness error: {:?}", err);
|
||||
DispatchError::Service(err)
|
||||
self._poll_ready(cx).map_err(|e| {
|
||||
log::error!("HTTP/1 service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -19,13 +19,13 @@ use h2::{
|
||||
server::{Connection, SendResponse},
|
||||
Ping, PingPong,
|
||||
};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
||||
use log::{error, trace};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
|
||||
service::HttpFlow,
|
||||
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
||||
};
|
||||
@@ -217,28 +217,25 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// poll response body and send chunks to client
|
||||
// poll response body and send chunks to client.
|
||||
actix_rt::pin!(body);
|
||||
|
||||
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
|
||||
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;
|
||||
|
||||
'send: loop {
|
||||
let chunk_size = cmp::min(chunk.len(), CHUNK_SIZE);
|
||||
|
||||
// reserve enough space and wait for stream ready.
|
||||
stream.reserve_capacity(chunk_size);
|
||||
stream.reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE));
|
||||
|
||||
match poll_fn(|cx| stream.poll_capacity(cx)).await {
|
||||
// No capacity left. drop body and return.
|
||||
None => return Ok(()),
|
||||
Some(res) => {
|
||||
// Split chuck to writeable size and send to client.
|
||||
let cap = res.map_err(DispatchError::SendData)?;
|
||||
|
||||
Some(Err(err)) => return Err(DispatchError::SendData(err)),
|
||||
|
||||
Some(Ok(cap)) => {
|
||||
// split chunk to writeable size and send to client
|
||||
let len = chunk.len();
|
||||
let bytes = chunk.split_to(cmp::min(len, cap));
|
||||
let bytes = chunk.split_to(cmp::min(cap, len));
|
||||
|
||||
stream
|
||||
.send_data(bytes, false)
|
||||
|
@@ -270,10 +270,10 @@ where
|
||||
type Future = H2ServiceHandlerResponse<T, S, B>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.flow.service.poll_ready(cx).map_err(|err| {
|
||||
let err = err.into();
|
||||
error!("Service readiness error: {:?}", err);
|
||||
DispatchError::Service(err)
|
||||
self.flow.service.poll_ready(cx).map_err(|e| {
|
||||
let e = e.into();
|
||||
error!("Service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})
|
||||
}
|
||||
|
||||
@@ -297,6 +297,7 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
S::Future: 'static,
|
||||
{
|
||||
Incoming(Dispatcher<T, S, B, (), ()>),
|
||||
Handshake(
|
||||
Option<Rc<HttpFlow<S, (), ()>>>,
|
||||
Option<ServiceConfig>,
|
||||
@@ -304,7 +305,6 @@ where
|
||||
OnConnectData,
|
||||
HandshakeWithTimeout<T>,
|
||||
),
|
||||
Established(Dispatcher<T, S, B, (), ()>),
|
||||
}
|
||||
|
||||
pub struct H2ServiceHandlerResponse<T, S, B>
|
||||
@@ -332,6 +332,7 @@ where
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.state {
|
||||
State::Incoming(ref mut disp) => Pin::new(disp).poll(cx),
|
||||
State::Handshake(
|
||||
ref mut srv,
|
||||
ref mut config,
|
||||
@@ -342,7 +343,7 @@ where
|
||||
Ok((conn, timer)) => {
|
||||
let on_connect_data = mem::take(conn_data);
|
||||
|
||||
self.state = State::Established(Dispatcher::new(
|
||||
self.state = State::Incoming(Dispatcher::new(
|
||||
conn,
|
||||
srv.take().unwrap(),
|
||||
config.take().unwrap(),
|
||||
@@ -359,8 +360,6 @@ where
|
||||
Poll::Ready(Err(err))
|
||||
}
|
||||
},
|
||||
|
||||
State::Established(ref mut disp) => Pin::new(disp).poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -16,7 +16,6 @@ pub trait Sealed {
|
||||
}
|
||||
|
||||
impl Sealed for HeaderName {
|
||||
#[inline]
|
||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||
Ok(Cow::Borrowed(self))
|
||||
}
|
||||
@@ -24,7 +23,6 @@ impl Sealed for HeaderName {
|
||||
impl AsHeaderName for HeaderName {}
|
||||
|
||||
impl Sealed for &HeaderName {
|
||||
#[inline]
|
||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||
Ok(Cow::Borrowed(*self))
|
||||
}
|
||||
@@ -32,7 +30,6 @@ impl Sealed for &HeaderName {
|
||||
impl AsHeaderName for &HeaderName {}
|
||||
|
||||
impl Sealed for &str {
|
||||
#[inline]
|
||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||
HeaderName::from_str(self).map(Cow::Owned)
|
||||
}
|
||||
@@ -40,7 +37,6 @@ impl Sealed for &str {
|
||||
impl AsHeaderName for &str {}
|
||||
|
||||
impl Sealed for String {
|
||||
#[inline]
|
||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||
HeaderName::from_str(self).map(Cow::Owned)
|
||||
}
|
||||
@@ -48,7 +44,6 @@ impl Sealed for String {
|
||||
impl AsHeaderName for String {}
|
||||
|
||||
impl Sealed for &String {
|
||||
#[inline]
|
||||
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
|
||||
HeaderName::from_str(self).map(Cow::Owned)
|
||||
}
|
||||
|
@@ -1,20 +1,22 @@
|
||||
//! [`TryIntoHeaderPair`] trait and implementations.
|
||||
//! [`IntoHeaderPair`] trait and implementations.
|
||||
|
||||
use std::convert::TryFrom as _;
|
||||
|
||||
use super::{
|
||||
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
|
||||
use http::{
|
||||
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
|
||||
Error as HttpError, HeaderValue,
|
||||
};
|
||||
use crate::error::HttpError;
|
||||
|
||||
/// An interface for types that can be converted into a [`HeaderName`] + [`HeaderValue`] pair for
|
||||
use super::{Header, IntoHeaderValue};
|
||||
|
||||
/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for
|
||||
/// insertion into a [`HeaderMap`].
|
||||
///
|
||||
/// [`HeaderMap`]: super::HeaderMap
|
||||
pub trait TryIntoHeaderPair: Sized {
|
||||
pub trait IntoHeaderPair: Sized {
|
||||
type Error: Into<HttpError>;
|
||||
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -32,14 +34,14 @@ impl From<InvalidHeaderPart> for HttpError {
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TryIntoHeaderPair for (HeaderName, V)
|
||||
impl<V> IntoHeaderPair for (HeaderName, V)
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
V::Error: Into<InvalidHeaderValue>,
|
||||
{
|
||||
type Error = InvalidHeaderPart;
|
||||
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
let (name, value) = self;
|
||||
let value = value
|
||||
.try_into_value()
|
||||
@@ -48,14 +50,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TryIntoHeaderPair for (&HeaderName, V)
|
||||
impl<V> IntoHeaderPair for (&HeaderName, V)
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
V::Error: Into<InvalidHeaderValue>,
|
||||
{
|
||||
type Error = InvalidHeaderPart;
|
||||
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
let (name, value) = self;
|
||||
let value = value
|
||||
.try_into_value()
|
||||
@@ -64,14 +66,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TryIntoHeaderPair for (&[u8], V)
|
||||
impl<V> IntoHeaderPair for (&[u8], V)
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
V::Error: Into<InvalidHeaderValue>,
|
||||
{
|
||||
type Error = InvalidHeaderPart;
|
||||
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
let (name, value) = self;
|
||||
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||
let value = value
|
||||
@@ -81,14 +83,14 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TryIntoHeaderPair for (&str, V)
|
||||
impl<V> IntoHeaderPair for (&str, V)
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
V::Error: Into<InvalidHeaderValue>,
|
||||
{
|
||||
type Error = InvalidHeaderPart;
|
||||
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
let (name, value) = self;
|
||||
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
|
||||
let value = value
|
||||
@@ -98,25 +100,23 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
impl<V> TryIntoHeaderPair for (String, V)
|
||||
impl<V> IntoHeaderPair for (String, V)
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
V::Error: Into<InvalidHeaderValue>,
|
||||
{
|
||||
type Error = InvalidHeaderPart;
|
||||
|
||||
#[inline]
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
let (name, value) = self;
|
||||
(name.as_str(), value).try_into_pair()
|
||||
(name.as_str(), value).try_into_header_pair()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Header> TryIntoHeaderPair for T {
|
||||
type Error = <T as TryIntoHeaderValue>::Error;
|
||||
impl<T: Header> IntoHeaderPair for T {
|
||||
type Error = <T as IntoHeaderValue>::Error;
|
||||
|
||||
#[inline]
|
||||
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
|
||||
Ok((T::name(), self.try_into_value()?))
|
||||
}
|
||||
}
|
||||
|
@@ -1,4 +1,4 @@
|
||||
//! [`TryIntoHeaderValue`] trait and implementations.
|
||||
//! [`IntoHeaderValue`] trait and implementations.
|
||||
|
||||
use std::convert::TryFrom as _;
|
||||
|
||||
@@ -7,7 +7,7 @@ use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
|
||||
use mime::Mime;
|
||||
|
||||
/// An interface for types that can be converted into a [`HeaderValue`].
|
||||
pub trait TryIntoHeaderValue: Sized {
|
||||
pub trait IntoHeaderValue: Sized {
|
||||
/// The type returned in the event of a conversion error.
|
||||
type Error: Into<HttpError>;
|
||||
|
||||
@@ -15,7 +15,7 @@ pub trait TryIntoHeaderValue: Sized {
|
||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for HeaderValue {
|
||||
impl IntoHeaderValue for HeaderValue {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -24,7 +24,7 @@ impl TryIntoHeaderValue for HeaderValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for &HeaderValue {
|
||||
impl IntoHeaderValue for &HeaderValue {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -33,7 +33,7 @@ impl TryIntoHeaderValue for &HeaderValue {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for &str {
|
||||
impl IntoHeaderValue for &str {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -42,7 +42,7 @@ impl TryIntoHeaderValue for &str {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for &[u8] {
|
||||
impl IntoHeaderValue for &[u8] {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -51,7 +51,7 @@ impl TryIntoHeaderValue for &[u8] {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for Bytes {
|
||||
impl IntoHeaderValue for Bytes {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -60,7 +60,7 @@ impl TryIntoHeaderValue for Bytes {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for Vec<u8> {
|
||||
impl IntoHeaderValue for Vec<u8> {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -69,7 +69,7 @@ impl TryIntoHeaderValue for Vec<u8> {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for String {
|
||||
impl IntoHeaderValue for String {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -78,7 +78,7 @@ impl TryIntoHeaderValue for String {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for usize {
|
||||
impl IntoHeaderValue for usize {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -87,7 +87,7 @@ impl TryIntoHeaderValue for usize {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for i64 {
|
||||
impl IntoHeaderValue for i64 {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -96,7 +96,7 @@ impl TryIntoHeaderValue for i64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for u64 {
|
||||
impl IntoHeaderValue for u64 {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -105,7 +105,7 @@ impl TryIntoHeaderValue for u64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for i32 {
|
||||
impl IntoHeaderValue for i32 {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -114,7 +114,7 @@ impl TryIntoHeaderValue for i32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for u32 {
|
||||
impl IntoHeaderValue for u32 {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -123,7 +123,7 @@ impl TryIntoHeaderValue for u32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for Mime {
|
||||
impl IntoHeaderValue for Mime {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
|
@@ -333,7 +333,7 @@ impl HeaderMap {
|
||||
}
|
||||
}
|
||||
|
||||
/// Inserts (overrides) a name-value pair in the map.
|
||||
/// Inserts a name-value pair into the map.
|
||||
///
|
||||
/// If the map already contained this key, the new value is associated with the key and all
|
||||
/// previous values are removed and returned as a `Removed` iterator. The key is not updated;
|
||||
@@ -372,7 +372,7 @@ impl HeaderMap {
|
||||
Removed::new(value)
|
||||
}
|
||||
|
||||
/// Appends a name-value pair to the map.
|
||||
/// Inserts a name-value pair into the map.
|
||||
///
|
||||
/// If the map already contained this key, the new value is added to the list of values
|
||||
/// currently associated with the key. The key is not updated; this matters for types that can
|
||||
|
@@ -37,8 +37,8 @@ mod shared;
|
||||
mod utils;
|
||||
|
||||
pub use self::as_name::AsHeaderName;
|
||||
pub use self::into_pair::TryIntoHeaderPair;
|
||||
pub use self::into_value::TryIntoHeaderValue;
|
||||
pub use self::into_pair::IntoHeaderPair;
|
||||
pub use self::into_value::IntoHeaderValue;
|
||||
pub use self::map::HeaderMap;
|
||||
pub use self::shared::{
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||
@@ -49,7 +49,7 @@ pub use self::utils::{
|
||||
};
|
||||
|
||||
/// An interface for types that already represent a valid header.
|
||||
pub trait Header: TryIntoHeaderValue {
|
||||
pub trait Header: IntoHeaderValue {
|
||||
/// Returns the name of the header field
|
||||
fn name() -> HeaderName;
|
||||
|
||||
|
@@ -5,7 +5,7 @@ use http::header::InvalidHeaderValue;
|
||||
|
||||
use crate::{
|
||||
error::ParseError,
|
||||
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue},
|
||||
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue},
|
||||
HttpMessage,
|
||||
};
|
||||
|
||||
@@ -96,7 +96,7 @@ impl TryFrom<&str> for ContentEncoding {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for ContentEncoding {
|
||||
impl IntoHeaderValue for ContentEncoding {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {
|
||||
|
@@ -4,8 +4,7 @@ use bytes::BytesMut;
|
||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||
|
||||
use crate::{
|
||||
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue,
|
||||
helpers::MutWriter,
|
||||
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, helpers::MutWriter,
|
||||
};
|
||||
|
||||
/// A timestamp with HTTP-style formatting and parsing.
|
||||
@@ -30,7 +29,7 @@ impl fmt::Display for HttpDate {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for HttpDate {
|
||||
impl IntoHeaderValue for HttpDate {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||
|
@@ -32,7 +32,6 @@ pub mod body;
|
||||
mod builder;
|
||||
mod config;
|
||||
|
||||
#[cfg(feature = "__compress")]
|
||||
pub mod encoding;
|
||||
mod extensions;
|
||||
pub mod header;
|
||||
|
@@ -7,10 +7,10 @@ use h2::RecvStream;
|
||||
|
||||
use crate::error::PayloadError;
|
||||
|
||||
/// A boxed payload.
|
||||
/// Type represent boxed payload
|
||||
pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
|
||||
|
||||
/// A streaming payload.
|
||||
/// Type represent streaming payload
|
||||
pub enum Payload<S = PayloadStream> {
|
||||
None,
|
||||
H1(crate::h1::Payload),
|
||||
|
@@ -2,7 +2,7 @@
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefMut},
|
||||
fmt, str,
|
||||
fmt, mem, str,
|
||||
};
|
||||
|
||||
use bytes::{Bytes, BytesMut};
|
||||
@@ -11,7 +11,7 @@ use bytestring::ByteString;
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
extensions::Extensions,
|
||||
header::{self, HeaderMap, TryIntoHeaderValue},
|
||||
header::{self, HeaderMap, IntoHeaderValue},
|
||||
message::{BoxedResponseHead, ResponseHead},
|
||||
Error, ResponseBuilder, StatusCode,
|
||||
};
|
||||
@@ -170,7 +170,7 @@ impl<B> Response<B> {
|
||||
/// Returns split head and body.
|
||||
///
|
||||
/// # Implementation Notes
|
||||
/// Due to internal performance optimizations, the first element of the returned tuple is a
|
||||
/// Due to internal performance optimisations, the first element of the returned tuple is a
|
||||
/// `Response` as well but only contains the head of the response this was called on.
|
||||
pub fn into_parts(self) -> (Response<()>, B) {
|
||||
self.replace_body(())
|
||||
@@ -194,7 +194,7 @@ impl<B> Response<B> {
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
self.map_body(|_, body| body.boxed())
|
||||
self.map_body(|_, body| BoxBody::new(body))
|
||||
}
|
||||
|
||||
/// Returns body, consuming this response.
|
||||
@@ -203,6 +203,12 @@ impl<B> Response<B> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: Default> Response<B> {
|
||||
pub fn take_body(&mut self) -> B {
|
||||
mem::take(&mut self.body)
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for Response<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
|
@@ -8,7 +8,7 @@ use std::{
|
||||
use crate::{
|
||||
body::{EitherBody, MessageBody},
|
||||
error::{Error, HttpError},
|
||||
header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
|
||||
header::{self, IntoHeaderPair, IntoHeaderValue},
|
||||
message::{BoxedResponseHead, ConnectionType, ResponseHead},
|
||||
Extensions, Response, StatusCode,
|
||||
};
|
||||
@@ -90,9 +90,12 @@ impl ResponseBuilder {
|
||||
/// assert!(res.headers().contains_key("content-type"));
|
||||
/// assert!(res.headers().contains_key("x-test"));
|
||||
/// ```
|
||||
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
if let Some(parts) = self.inner() {
|
||||
match header.try_into_pair() {
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => {
|
||||
parts.headers.insert(key, value);
|
||||
}
|
||||
@@ -118,9 +121,12 @@ impl ResponseBuilder {
|
||||
/// assert_eq!(res.headers().get_all("content-type").count(), 1);
|
||||
/// assert_eq!(res.headers().get_all("x-test").count(), 2);
|
||||
/// ```
|
||||
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
if let Some(parts) = self.inner() {
|
||||
match header.try_into_pair() {
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => parts.headers.append(key, value),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
@@ -151,7 +157,7 @@ impl ResponseBuilder {
|
||||
#[inline]
|
||||
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = self.inner() {
|
||||
parts.set_connection_type(ConnectionType::Upgrade);
|
||||
@@ -189,7 +195,7 @@ impl ResponseBuilder {
|
||||
#[inline]
|
||||
pub fn content_type<V>(&mut self, value: V) -> &mut Self
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
if let Some(parts) = self.inner() {
|
||||
match value.try_into_value() {
|
||||
|
@@ -493,9 +493,9 @@ where
|
||||
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
|
||||
|
||||
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self._poll_ready(cx).map_err(|err| {
|
||||
log::error!("HTTP service readiness error: {:?}", err);
|
||||
DispatchError::Service(err)
|
||||
self._poll_ready(cx).map_err(|e| {
|
||||
log::error!("HTTP service readiness error: {:?}", e);
|
||||
DispatchError::Service(e)
|
||||
})
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,7 @@ use bytes::{Bytes, BytesMut};
|
||||
use http::{Method, Uri, Version};
|
||||
|
||||
use crate::{
|
||||
header::{HeaderMap, TryIntoHeaderPair},
|
||||
header::{HeaderMap, IntoHeaderPair},
|
||||
payload::Payload,
|
||||
Request,
|
||||
};
|
||||
@@ -92,8 +92,11 @@ impl TestRequest {
|
||||
}
|
||||
|
||||
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||
match header.try_into_pair() {
|
||||
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => {
|
||||
parts(&mut self.0).headers.insert(key, value);
|
||||
}
|
||||
@@ -106,8 +109,11 @@ impl TestRequest {
|
||||
}
|
||||
|
||||
/// Append a header, keeping any that were set with an equivalent field name.
|
||||
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
|
||||
match header.try_into_pair() {
|
||||
pub fn append_header<H>(&mut self, header: H) -> &mut Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => {
|
||||
parts(&mut self.0).headers.append(key, value);
|
||||
}
|
||||
@@ -264,7 +270,7 @@ impl TestSeqBuffer {
|
||||
|
||||
/// Create new empty `TestBuffer` instance.
|
||||
pub fn empty() -> Self {
|
||||
Self::new(BytesMut::new())
|
||||
Self::new("")
|
||||
}
|
||||
|
||||
pub fn read_buf(&self) -> Ref<'_, BytesMut> {
|
||||
|
@@ -101,7 +101,7 @@ async fn test_h2_1() -> io::Result<()> {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_h2_body() -> io::Result<()> {
|
||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB
|
||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|mut req: Request<_>| async move {
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.4.0-beta.10 - 2021-12-11
|
||||
* No significant changes since `0.4.0-beta.9`.
|
||||
|
||||
|
||||
## 0.4.0-beta.9 - 2021-12-01
|
||||
* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.4.0-beta.10"
|
||||
version = "0.4.0-beta.9"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart form support for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@@ -14,8 +14,8 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.15", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
@@ -28,7 +28,7 @@ twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.10)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.9)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.10)
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.9)
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -1,9 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.5.0-beta.3 - 2021-12-17
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-router"
|
||||
version = "0.5.0-beta.3"
|
||||
version = "0.5.0-beta.2"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",
|
||||
|
@@ -7,20 +7,144 @@
|
||||
|
||||
mod de;
|
||||
mod path;
|
||||
mod pattern;
|
||||
mod resource;
|
||||
mod resource_path;
|
||||
mod router;
|
||||
|
||||
pub use self::de::PathDeserializer;
|
||||
pub use self::path::Path;
|
||||
pub use self::resource::ResourceDef;
|
||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
||||
|
||||
// TODO: this trait is necessary, document it
|
||||
// see impl Resource for ServiceRequest
|
||||
pub trait Resource<T: ResourcePath> {
|
||||
fn resource_path(&mut self) -> &mut Path<T>;
|
||||
}
|
||||
|
||||
pub trait ResourcePath {
|
||||
fn path(&self) -> &str;
|
||||
}
|
||||
|
||||
impl ResourcePath for String {
|
||||
fn path(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ResourcePath for &'a str {
|
||||
fn path(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourcePath for bytestring::ByteString {
|
||||
fn path(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
/// One or many patterns.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Patterns {
|
||||
Single(String),
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
impl Patterns {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Patterns::Single(_) => false,
|
||||
Patterns::List(pats) => pats.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for type that could be converted to one or more path pattern.
|
||||
pub trait IntoPatterns {
|
||||
fn patterns(&self) -> Patterns;
|
||||
}
|
||||
|
||||
impl IntoPatterns for String {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPatterns for &'a String {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single((*self).clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoPatterns for &'a str {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single((*self).to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for bytestring::ByteString {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for Patterns {
|
||||
fn patterns(&self) -> Patterns {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
|
||||
fn patterns(&self) -> Patterns {
|
||||
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
|
||||
|
||||
match patterns.size_hint() {
|
||||
(1, _) => Patterns::Single(patterns.next().unwrap()),
|
||||
_ => Patterns::List(patterns.collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! array_patterns_single (($tp:ty) => {
|
||||
impl IntoPatterns for [$tp; 1] {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self[0].to_owned())
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
|
||||
// for each array length specified in $num
|
||||
$(
|
||||
impl IntoPatterns for [$tp; $num] {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::List(self.iter().map($str_fn).collect())
|
||||
}
|
||||
}
|
||||
)+
|
||||
});
|
||||
|
||||
array_patterns_single!(&str);
|
||||
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||
|
||||
array_patterns_single!(String);
|
||||
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod url;
|
||||
|
||||
pub use self::de::PathDeserializer;
|
||||
pub use self::path::Path;
|
||||
pub use self::pattern::{IntoPatterns, Patterns};
|
||||
pub use self::resource::ResourceDef;
|
||||
pub use self::resource_path::{Resource, ResourcePath};
|
||||
pub use self::router::{ResourceInfo, Router, RouterBuilder};
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
pub use self::url::{Quoter, Url};
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
mod http_impls {
|
||||
use http::Uri;
|
||||
|
||||
use super::ResourcePath;
|
||||
|
||||
impl ResourcePath for Uri {
|
||||
fn path(&self) -> &str {
|
||||
self.path()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -1,92 +0,0 @@
|
||||
/// One or many patterns.
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
|
||||
pub enum Patterns {
|
||||
Single(String),
|
||||
List(Vec<String>),
|
||||
}
|
||||
|
||||
impl Patterns {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
match self {
|
||||
Patterns::Single(_) => false,
|
||||
Patterns::List(pats) => pats.is_empty(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper trait for type that could be converted to one or more path patterns.
|
||||
pub trait IntoPatterns {
|
||||
fn patterns(&self) -> Patterns;
|
||||
}
|
||||
|
||||
impl IntoPatterns for String {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for &String {
|
||||
fn patterns(&self) -> Patterns {
|
||||
(*self).patterns()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for str {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self.to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for &str {
|
||||
fn patterns(&self) -> Patterns {
|
||||
(*self).patterns()
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for bytestring::ByteString {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoPatterns for Patterns {
|
||||
fn patterns(&self) -> Patterns {
|
||||
self.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
|
||||
fn patterns(&self) -> Patterns {
|
||||
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
|
||||
|
||||
match patterns.size_hint() {
|
||||
(1, _) => Patterns::Single(patterns.next().unwrap()),
|
||||
_ => Patterns::List(patterns.collect()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! array_patterns_single (($tp:ty) => {
|
||||
impl IntoPatterns for [$tp; 1] {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::Single(self[0].to_owned())
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
|
||||
// for each array length specified in space-separated $num
|
||||
$(
|
||||
impl IntoPatterns for [$tp; $num] {
|
||||
fn patterns(&self) -> Patterns {
|
||||
Patterns::List(self.iter().map($str_fn).collect())
|
||||
}
|
||||
}
|
||||
)+
|
||||
});
|
||||
|
||||
array_patterns_single!(&str);
|
||||
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
||||
|
||||
array_patterns_single!(String);
|
||||
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
|
@@ -1,36 +0,0 @@
|
||||
use crate::Path;
|
||||
|
||||
// TODO: this trait is necessary, document it
|
||||
// see impl Resource for ServiceRequest
|
||||
pub trait Resource<T: ResourcePath> {
|
||||
fn resource_path(&mut self) -> &mut Path<T>;
|
||||
}
|
||||
|
||||
pub trait ResourcePath {
|
||||
fn path(&self) -> &str;
|
||||
}
|
||||
|
||||
impl ResourcePath for String {
|
||||
fn path(&self) -> &str {
|
||||
self.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ResourcePath for &'a str {
|
||||
fn path(&self) -> &str {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourcePath for bytestring::ByteString {
|
||||
fn path(&self) -> &str {
|
||||
&*self
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http")]
|
||||
impl ResourcePath for http::Uri {
|
||||
fn path(&self) -> &str {
|
||||
self.path()
|
||||
}
|
||||
}
|
@@ -2,28 +2,22 @@ use crate::ResourcePath;
|
||||
|
||||
#[allow(dead_code)]
|
||||
const GEN_DELIMS: &[u8] = b":/?#[]@";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
|
||||
|
||||
#[allow(dead_code)]
|
||||
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
1234567890
|
||||
-._~";
|
||||
|
||||
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
|
||||
ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
1234567890
|
||||
-._~
|
||||
!$'()*,";
|
||||
|
||||
const QS: &[u8] = b"+&=;b";
|
||||
|
||||
#[inline]
|
||||
@@ -40,20 +34,19 @@ thread_local! {
|
||||
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Default, Clone, Debug)]
|
||||
pub struct Url {
|
||||
uri: http::Uri,
|
||||
path: Option<String>,
|
||||
}
|
||||
|
||||
impl Url {
|
||||
#[inline]
|
||||
pub fn new(uri: http::Uri) -> Url {
|
||||
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
|
||||
|
||||
Url { uri, path }
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
|
||||
Url {
|
||||
path: quoter.requote(uri.path().as_bytes()),
|
||||
@@ -61,16 +54,15 @@ impl Url {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn uri(&self) -> &http::Uri {
|
||||
&self.uri
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn path(&self) -> &str {
|
||||
match self.path {
|
||||
Some(ref path) => path,
|
||||
_ => self.uri.path(),
|
||||
if let Some(ref s) = self.path {
|
||||
s
|
||||
} else {
|
||||
self.uri.path()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -94,7 +86,6 @@ impl ResourcePath for Url {
|
||||
}
|
||||
}
|
||||
|
||||
/// A quoter
|
||||
pub struct Quoter {
|
||||
safe_table: [u8; 16],
|
||||
protected_table: [u8; 16],
|
||||
@@ -102,7 +93,7 @@ pub struct Quoter {
|
||||
|
||||
impl Quoter {
|
||||
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
|
||||
let mut quoter = Quoter {
|
||||
let mut q = Quoter {
|
||||
safe_table: [0; 16],
|
||||
protected_table: [0; 16],
|
||||
};
|
||||
@@ -110,24 +101,24 @@ impl Quoter {
|
||||
// prepare safe table
|
||||
for i in 0..128 {
|
||||
if ALLOWED.contains(&i) {
|
||||
set_bit(&mut quoter.safe_table, i);
|
||||
set_bit(&mut q.safe_table, i);
|
||||
}
|
||||
if QS.contains(&i) {
|
||||
set_bit(&mut quoter.safe_table, i);
|
||||
set_bit(&mut q.safe_table, i);
|
||||
}
|
||||
}
|
||||
|
||||
for ch in safe {
|
||||
set_bit(&mut quoter.safe_table, *ch)
|
||||
set_bit(&mut q.safe_table, *ch)
|
||||
}
|
||||
|
||||
// prepare protected table
|
||||
for ch in protected {
|
||||
set_bit(&mut quoter.safe_table, *ch);
|
||||
set_bit(&mut quoter.protected_table, *ch);
|
||||
set_bit(&mut q.safe_table, *ch);
|
||||
set_bit(&mut q.protected_table, *ch);
|
||||
}
|
||||
|
||||
quoter
|
||||
q
|
||||
}
|
||||
|
||||
pub fn requote(&self, val: &[u8]) -> Option<String> {
|
||||
@@ -224,7 +215,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_url() {
|
||||
fn test_parse_url() {
|
||||
let re = "/user/{id}/test";
|
||||
|
||||
let path = match_url(re, "/user/2345/test");
|
||||
@@ -240,24 +231,24 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn protected_chars() {
|
||||
fn test_protected_chars() {
|
||||
let encoded = percent_encode(PROTECTED);
|
||||
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
||||
assert_eq!(path.get("id").unwrap(), &encoded);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn non_protected_ascii() {
|
||||
let non_protected_ascii = ('\u{0}'..='\u{7F}')
|
||||
fn test_non_protecteed_ascii() {
|
||||
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
|
||||
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
|
||||
.collect::<String>();
|
||||
let encoded = percent_encode(non_protected_ascii.as_bytes());
|
||||
let encoded = percent_encode(nonprotected_ascii.as_bytes());
|
||||
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
|
||||
assert_eq!(path.get("id").unwrap(), &non_protected_ascii);
|
||||
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn valid_utf8_multibyte() {
|
||||
fn test_valid_utf8_multibyte() {
|
||||
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
|
||||
let encoded = percent_encode(test.as_bytes());
|
||||
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
|
||||
@@ -265,7 +256,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn invalid_utf8() {
|
||||
fn test_invalid_utf8() {
|
||||
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
|
||||
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
|
||||
let path = Path::new(Url::new(uri));
|
||||
@@ -275,7 +266,7 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn hex_encoding() {
|
||||
fn test_from_hex() {
|
||||
let hex = b"0123456789abcdefABCDEF";
|
||||
|
||||
for i in 0..256 {
|
||||
|
@@ -3,17 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.1.0-beta.9 - 2021-12-17
|
||||
* Re-export `actix_http::body::to_bytes`. [#2518]
|
||||
* Update `actix_web::test` re-exports. [#2518]
|
||||
|
||||
[#2518]: https://github.com/actix/actix-web/pull/2518
|
||||
|
||||
|
||||
## 0.1.0-beta.8 - 2021-12-11
|
||||
* No significant changes since `0.1.0-beta.7`.
|
||||
|
||||
|
||||
## 0.1.0-beta.7 - 2021-11-22
|
||||
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.0-beta.9"
|
||||
version = "0.1.0-beta.7"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-http-test = "3.0.0-beta.9"
|
||||
actix-rt = "2.1"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-http-test = "3.0.0-beta.7"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.0.0-beta.14", default-features = false, features = ["cookies"] }
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
actix-rt = "2.1"
|
||||
awc = { version = "3.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||
|
@@ -37,14 +37,9 @@ extern crate tls_rustls as rustls;
|
||||
use std::{fmt, net, thread, time::Duration};
|
||||
|
||||
use actix_codec::{AsyncRead, AsyncWrite, Framed};
|
||||
pub use actix_http::{body::to_bytes, test::TestBuffer};
|
||||
pub use actix_http::test::TestBuffer;
|
||||
use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
|
||||
pub use actix_http_test::unused_addr;
|
||||
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
|
||||
pub use actix_web::test::{
|
||||
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
|
||||
read_body, read_body_json, simple_service, TestRequest,
|
||||
};
|
||||
use actix_web::{
|
||||
body::MessageBody,
|
||||
dev::{AppConfig, Server, ServerHandle, Service},
|
||||
@@ -53,6 +48,12 @@ use actix_web::{
|
||||
};
|
||||
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
|
||||
use futures_core::Stream;
|
||||
|
||||
pub use actix_http_test::unused_addr;
|
||||
pub use actix_web::test::{
|
||||
call_service, default_service, init_service, load_stream, ok_service, read_body,
|
||||
read_body_json, read_response, read_response_json, TestRequest,
|
||||
};
|
||||
use tokio::sync::mpsc;
|
||||
|
||||
/// Start default [`TestServer`].
|
||||
@@ -162,9 +163,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -178,9 +181,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -194,9 +199,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -213,9 +220,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -229,9 +238,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -245,9 +256,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -264,9 +277,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -280,9 +295,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
@@ -296,9 +313,11 @@ where
|
||||
local_addr,
|
||||
);
|
||||
|
||||
let fac = factory()
|
||||
.into_factory()
|
||||
.map_err(|err| err.into().error_response());
|
||||
let fac = factory().into_factory().map_err(|err| {
|
||||
let res: actix_http::Response<_> =
|
||||
err.into().error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
});
|
||||
|
||||
HttpService::build()
|
||||
.client_timeout(timeout)
|
||||
|
@@ -1,9 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.0-beta.8 - 2021-12-11
|
||||
* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
|
||||
* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web-actors"
|
||||
version = "4.0.0-beta.8"
|
||||
version = "4.0.0-beta.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix actors support for Actix Web"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
@@ -16,8 +16,8 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
actix = { version = "0.12.0", default-features = false }
|
||||
actix-codec = "0.4.1"
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-web = { version = "4.0.0-beta.15", default-features = false }
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
@@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.9"
|
||||
awc = { version = "3.0.0-beta.14", default-features = false }
|
||||
actix-test = "0.1.0-beta.7"
|
||||
|
||||
awc = { version = "3.0.0-beta.11", default-features = false }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Actix actors support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.8)
|
||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.7)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8)
|
||||
[](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.5.0-beta.6 - 2021-12-11
|
||||
* No significant changes since `0.5.0-beta.5`.
|
||||
|
||||
|
||||
## 0.5.0-beta.5 - 2021-10-20
|
||||
* Improve error recovery potential when macro input is invalid. [#2410]
|
||||
* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web-codegen"
|
||||
version = "0.5.0-beta.6"
|
||||
version = "0.5.0-beta.5"
|
||||
description = "Routing and runtime macros for Actix Web"
|
||||
homepage = "https://actix.rs"
|
||||
repository = "https://github.com/actix/actix-web.git"
|
||||
@@ -18,14 +18,14 @@ proc-macro = true
|
||||
quote = "1"
|
||||
syn = { version = "1", features = ["full", "parsing"] }
|
||||
proc-macro2 = "1"
|
||||
actix-router = "0.5.0-beta.3"
|
||||
actix-router = "0.5.0-beta.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-macros = "0.2.3"
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.9"
|
||||
actix-macros = "0.2.3"
|
||||
actix-test = "0.1.0-beta.7"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = "4.0.0-beta.15"
|
||||
actix-web = "4.0.0-beta.11"
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
trybuild = "1"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Routing and runtime macros for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://docs.rs/actix-web-codegen/0.5.0-beta.6)
|
||||
[](https://docs.rs/actix-web-codegen/0.5.0-beta.5)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6)
|
||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5)
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -3,16 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.14 - 2021-12-17
|
||||
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
|
||||
|
||||
[#2510]: https://github.com/actix/actix-web/pull/2510
|
||||
|
||||
|
||||
## 3.0.0-beta.13 - 2021-12-11
|
||||
* No significant changes since `3.0.0-beta.12`.
|
||||
|
||||
|
||||
## 3.0.0-beta.12 - 2021-11-30
|
||||
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||
|
||||
@@ -66,7 +56,7 @@
|
||||
* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
|
||||
* Fix http/https encoding when enabling `compress` feature. [#2116]
|
||||
* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
|
||||
methods now take `TryIntoHeaderPair` tuples. [#2094]
|
||||
methods now take `IntoHeaderPair` tuples. [#2094]
|
||||
|
||||
[#2081]: https://github.com/actix/actix-web/pull/2081
|
||||
[#2094]: https://github.com/actix/actix-web/pull/2094
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "3.0.0-beta.14"
|
||||
version = "3.0.0-beta.12"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
@@ -60,7 +60,7 @@ dangerous-h2c = []
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-service = "2.0.0"
|
||||
actix-http = "3.0.0-beta.16"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
|
||||
actix-utils = "3.0.0"
|
||||
@@ -70,9 +70,9 @@ base64 = "0.13"
|
||||
bytes = "1"
|
||||
cfg-if = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||
h2 = "0.3.9"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
h2 = "0.3"
|
||||
http = "0.2.5"
|
||||
itoa = "0.4"
|
||||
log =" 0.4"
|
||||
@@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
|
||||
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "3.0.0-beta.16", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
|
||||
actix-web = { version = "4.0.0-beta.11", features = ["openssl"] }
|
||||
actix-http = { version = "3.0.0-beta.14", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.15", features = ["openssl"] }
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
env_logger = "0.9"
|
||||
|
@@ -3,9 +3,9 @@
|
||||
> Async HTTP and WebSocket client library.
|
||||
|
||||
[](https://crates.io/crates/awc)
|
||||
[](https://docs.rs/awc/3.0.0-beta.14)
|
||||
[](https://docs.rs/awc/3.0.0-beta.12)
|
||||

|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.14)
|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.12)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
@@ -45,7 +45,9 @@ impl AnyBody {
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
{
|
||||
Self::Body { body: body.boxed() }
|
||||
Self::Body {
|
||||
body: BoxBody::new(body),
|
||||
}
|
||||
}
|
||||
|
||||
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.
|
||||
|
@@ -2,7 +2,7 @@ use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration};
|
||||
|
||||
use actix_http::{
|
||||
error::HttpError,
|
||||
header::{self, HeaderMap, HeaderName, TryIntoHeaderPair},
|
||||
header::{self, HeaderMap, HeaderName},
|
||||
Uri,
|
||||
};
|
||||
use actix_rt::net::{ActixStream, TcpStream};
|
||||
@@ -21,11 +21,11 @@ use crate::{
|
||||
/// This type can be used to construct an instance of `Client` through a
|
||||
/// builder-like pattern.
|
||||
pub struct ClientBuilder<S = (), M = ()> {
|
||||
default_headers: bool,
|
||||
max_http_version: Option<http::Version>,
|
||||
stream_window_size: Option<u32>,
|
||||
conn_window_size: Option<u32>,
|
||||
fundamental_headers: bool,
|
||||
default_headers: HeaderMap,
|
||||
headers: HeaderMap,
|
||||
timeout: Option<Duration>,
|
||||
connector: Connector<S>,
|
||||
middleware: M,
|
||||
@@ -44,15 +44,15 @@ impl ClientBuilder {
|
||||
(),
|
||||
> {
|
||||
ClientBuilder {
|
||||
middleware: (),
|
||||
default_headers: true,
|
||||
headers: HeaderMap::new(),
|
||||
timeout: Some(Duration::from_secs(5)),
|
||||
local_address: None,
|
||||
connector: Connector::new(),
|
||||
max_http_version: None,
|
||||
stream_window_size: None,
|
||||
conn_window_size: None,
|
||||
fundamental_headers: true,
|
||||
default_headers: HeaderMap::new(),
|
||||
timeout: Some(Duration::from_secs(5)),
|
||||
connector: Connector::new(),
|
||||
middleware: (),
|
||||
local_address: None,
|
||||
max_redirects: 10,
|
||||
}
|
||||
}
|
||||
@@ -78,8 +78,8 @@ where
|
||||
{
|
||||
ClientBuilder {
|
||||
middleware: self.middleware,
|
||||
fundamental_headers: self.fundamental_headers,
|
||||
default_headers: self.default_headers,
|
||||
headers: self.headers,
|
||||
timeout: self.timeout,
|
||||
local_address: self.local_address,
|
||||
connector,
|
||||
@@ -153,46 +153,30 @@ where
|
||||
self
|
||||
}
|
||||
|
||||
/// Do not add fundamental default request headers.
|
||||
///
|
||||
/// Do not add default request headers.
|
||||
/// By default `Date` and `User-Agent` headers are set.
|
||||
pub fn no_default_headers(mut self) -> Self {
|
||||
self.fundamental_headers = false;
|
||||
self.default_headers = false;
|
||||
self
|
||||
}
|
||||
|
||||
/// Add default header.
|
||||
///
|
||||
/// Headers added by this method get added to every request unless overriden by .
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics if header name or value is invalid.
|
||||
pub fn add_default_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
match header.try_into_pair() {
|
||||
Ok((key, value)) => self.default_headers.append(key, value),
|
||||
Err(err) => panic!("Header error: {:?}", err.into()),
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "3.0.0", note = "Prefer `add_default_header((key, value))`.")]
|
||||
/// Add default header. Headers added by this method
|
||||
/// get added to every request.
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: fmt::Debug + Into<HttpError>,
|
||||
V: header::TryIntoHeaderValue,
|
||||
V: header::IntoHeaderValue,
|
||||
V::Error: fmt::Debug,
|
||||
{
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match value.try_into_value() {
|
||||
Ok(value) => {
|
||||
self.default_headers.append(key, value);
|
||||
self.headers.append(key, value);
|
||||
}
|
||||
Err(err) => log::error!("Header value error: {:?}", err),
|
||||
Err(e) => log::error!("Header value error: {:?}", e),
|
||||
},
|
||||
Err(err) => log::error!("Header name error: {:?}", err),
|
||||
Err(e) => log::error!("Header name error: {:?}", e),
|
||||
}
|
||||
self
|
||||
}
|
||||
@@ -206,10 +190,10 @@ where
|
||||
Some(password) => format!("{}:{}", username, password),
|
||||
None => format!("{}:", username),
|
||||
};
|
||||
self.add_default_header((
|
||||
self.header(
|
||||
header::AUTHORIZATION,
|
||||
format!("Basic {}", base64::encode(&auth)),
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
/// Set client wide HTTP bearer authentication header
|
||||
@@ -217,12 +201,13 @@ where
|
||||
where
|
||||
T: fmt::Display,
|
||||
{
|
||||
self.add_default_header((header::AUTHORIZATION, format!("Bearer {}", token)))
|
||||
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
|
||||
}
|
||||
|
||||
/// Registers middleware, in the form of a middleware component (type), that runs during inbound
|
||||
/// and/or outbound processing in the request life-cycle (request -> response),
|
||||
/// modifying request/response as necessary, across all requests managed by the `Client`.
|
||||
/// Registers middleware, in the form of a middleware component (type),
|
||||
/// that runs during inbound and/or outbound processing in the request
|
||||
/// life-cycle (request -> response), modifying request/response as
|
||||
/// necessary, across all requests managed by the Client.
|
||||
pub fn wrap<S1, M1>(
|
||||
self,
|
||||
mw: M1,
|
||||
@@ -233,11 +218,11 @@ where
|
||||
{
|
||||
ClientBuilder {
|
||||
middleware: NestTransform::new(self.middleware, mw),
|
||||
fundamental_headers: self.fundamental_headers,
|
||||
default_headers: self.default_headers,
|
||||
max_http_version: self.max_http_version,
|
||||
stream_window_size: self.stream_window_size,
|
||||
conn_window_size: self.conn_window_size,
|
||||
default_headers: self.default_headers,
|
||||
headers: self.headers,
|
||||
timeout: self.timeout,
|
||||
connector: self.connector,
|
||||
local_address: self.local_address,
|
||||
@@ -252,10 +237,10 @@ where
|
||||
M::Transform:
|
||||
Service<ConnectRequest, Response = ConnectResponse, Error = SendRequestError>,
|
||||
{
|
||||
let max_redirects = self.max_redirects;
|
||||
let redirect_time = self.max_redirects;
|
||||
|
||||
if max_redirects > 0 {
|
||||
self.wrap(Redirect::new().max_redirect_times(max_redirects))
|
||||
if redirect_time > 0 {
|
||||
self.wrap(Redirect::new().max_redirect_times(redirect_time))
|
||||
._finish()
|
||||
} else {
|
||||
self._finish()
|
||||
@@ -287,7 +272,7 @@ where
|
||||
let connector = boxed::rc_service(self.middleware.new_transform(connector));
|
||||
|
||||
Client(ClientConfig {
|
||||
default_headers: Rc::new(self.default_headers),
|
||||
headers: Rc::new(self.headers),
|
||||
timeout: self.timeout,
|
||||
connector,
|
||||
})
|
||||
@@ -303,7 +288,7 @@ mod tests {
|
||||
let client = ClientBuilder::new().basic_auth("username", Some("password"));
|
||||
assert_eq!(
|
||||
client
|
||||
.default_headers
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
@@ -314,7 +299,7 @@ mod tests {
|
||||
let client = ClientBuilder::new().basic_auth("username", None);
|
||||
assert_eq!(
|
||||
client
|
||||
.default_headers
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
@@ -328,7 +313,7 @@ mod tests {
|
||||
let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n");
|
||||
assert_eq!(
|
||||
client
|
||||
.default_headers
|
||||
.headers
|
||||
.get(header::AUTHORIZATION)
|
||||
.unwrap()
|
||||
.to_str()
|
||||
|
@@ -9,7 +9,7 @@ use actix_http::{
|
||||
body::{BodySize, MessageBody},
|
||||
error::PayloadError,
|
||||
h1,
|
||||
header::{HeaderMap, TryIntoHeaderValue, EXPECT, HOST},
|
||||
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
|
||||
Payload, RequestHeadType, ResponseHead, StatusCode,
|
||||
};
|
||||
use actix_utils::future::poll_fn;
|
||||
|
@@ -6,7 +6,7 @@ use serde::Serialize;
|
||||
|
||||
use actix_http::{
|
||||
error::HttpError,
|
||||
header::{HeaderMap, HeaderName, TryIntoHeaderValue},
|
||||
header::{HeaderMap, HeaderName, IntoHeaderValue},
|
||||
Method, RequestHead, Uri,
|
||||
};
|
||||
|
||||
@@ -114,7 +114,7 @@ impl FrozenClientRequest {
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
self.extra_headers(HeaderMap::new())
|
||||
.extra_header(key, value)
|
||||
@@ -142,7 +142,7 @@ impl FrozenSendBuilder {
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match value.try_into_value() {
|
||||
|
@@ -168,7 +168,7 @@ pub struct Client(ClientConfig);
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct ClientConfig {
|
||||
pub(crate) connector: BoxConnectorService,
|
||||
pub(crate) default_headers: Rc<HeaderMap>,
|
||||
pub(crate) headers: Rc<HeaderMap>,
|
||||
pub(crate) timeout: Option<Duration>,
|
||||
}
|
||||
|
||||
@@ -204,9 +204,7 @@ impl Client {
|
||||
{
|
||||
let mut req = ClientRequest::new(method, url, self.0.clone());
|
||||
|
||||
for header in self.0.default_headers.iter() {
|
||||
// header map is empty
|
||||
// TODO: probably append instead
|
||||
for header in self.0.headers.iter() {
|
||||
req = req.insert_header_if_none(header);
|
||||
}
|
||||
req
|
||||
@@ -299,7 +297,7 @@ impl Client {
|
||||
<Uri as TryFrom<U>>::Error: Into<HttpError>,
|
||||
{
|
||||
let mut req = ws::WebsocketsRequest::new(url, self.0.clone());
|
||||
for (key, value) in self.0.default_headers.iter() {
|
||||
for (key, value) in self.0.headers.iter() {
|
||||
req.head.headers.insert(key.clone(), value.clone());
|
||||
}
|
||||
req
|
||||
@@ -310,6 +308,6 @@ impl Client {
|
||||
/// Returns Some(&mut HeaderMap) when Client object is unique
|
||||
/// (No other clone of client exists at the same time).
|
||||
pub fn headers(&mut self) -> Option<&mut HeaderMap> {
|
||||
Rc::get_mut(&mut self.0.default_headers)
|
||||
Rc::get_mut(&mut self.0.headers)
|
||||
}
|
||||
}
|
||||
|
@@ -442,15 +442,13 @@ mod tests {
|
||||
});
|
||||
|
||||
let client = ClientBuilder::new()
|
||||
.add_default_header(("custom", "value"))
|
||||
.header("custom", "value")
|
||||
.disable_redirects()
|
||||
.finish();
|
||||
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||
assert_eq!(res.status().as_u16(), 302);
|
||||
|
||||
let client = ClientBuilder::new()
|
||||
.add_default_header(("custom", "value"))
|
||||
.finish();
|
||||
let client = ClientBuilder::new().header("custom", "value").finish();
|
||||
let res = client.get(srv.url("/")).send().await.unwrap();
|
||||
assert_eq!(res.status().as_u16(), 200);
|
||||
|
||||
@@ -522,7 +520,7 @@ mod tests {
|
||||
|
||||
// send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header
|
||||
let client = ClientBuilder::new()
|
||||
.add_default_header((header::AUTHORIZATION, "auth_key_value"))
|
||||
.header(header::AUTHORIZATION, "auth_key_value")
|
||||
.finish();
|
||||
let res = client.get(srv1.url("/")).send().await.unwrap();
|
||||
assert_eq!(res.status().as_u16(), 200);
|
||||
|
@@ -6,7 +6,7 @@ use serde::Serialize;
|
||||
|
||||
use actix_http::{
|
||||
error::HttpError,
|
||||
header::{self, HeaderMap, HeaderValue, TryIntoHeaderPair},
|
||||
header::{self, HeaderMap, HeaderValue, IntoHeaderPair},
|
||||
ConnectionType, Method, RequestHead, Uri, Version,
|
||||
};
|
||||
|
||||
@@ -147,8 +147,11 @@ impl ClientRequest {
|
||||
}
|
||||
|
||||
/// Insert a header, replacing any that were set with an equivalent field name.
|
||||
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
match header.try_into_pair() {
|
||||
pub fn insert_header<H>(mut self, header: H) -> Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => {
|
||||
self.head.headers.insert(key, value);
|
||||
}
|
||||
@@ -159,8 +162,11 @@ impl ClientRequest {
|
||||
}
|
||||
|
||||
/// Insert a header only if it is not yet set.
|
||||
pub fn insert_header_if_none(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
match header.try_into_pair() {
|
||||
pub fn insert_header_if_none<H>(mut self, header: H) -> Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => {
|
||||
if !self.head.headers.contains_key(&key) {
|
||||
self.head.headers.insert(key, value);
|
||||
@@ -186,8 +192,11 @@ impl ClientRequest {
|
||||
/// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
match header.try_into_pair() {
|
||||
pub fn append_header<H>(mut self, header: H) -> Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
match header.try_into_header_pair() {
|
||||
Ok((key, value)) => self.head.headers.append(key, value),
|
||||
Err(e) => self.err = Some(e.into()),
|
||||
};
|
||||
@@ -579,7 +588,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_client_header() {
|
||||
let req = Client::builder()
|
||||
.add_default_header((header::CONTENT_TYPE, "111"))
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
.get("/");
|
||||
|
||||
@@ -597,7 +606,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_client_header_override() {
|
||||
let req = Client::builder()
|
||||
.add_default_header((header::CONTENT_TYPE, "111"))
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
.get("/")
|
||||
.insert_header((header::CONTENT_TYPE, "222"));
|
||||
|
@@ -10,7 +10,7 @@ use std::{
|
||||
use actix_http::{
|
||||
body::BodyStream,
|
||||
error::HttpError,
|
||||
header::{self, HeaderMap, HeaderName, TryIntoHeaderValue},
|
||||
header::{self, HeaderMap, HeaderName, IntoHeaderValue},
|
||||
RequestHead, RequestHeadType,
|
||||
};
|
||||
use actix_rt::time::{sleep, Sleep};
|
||||
@@ -298,7 +298,7 @@ impl RequestSender {
|
||||
|
||||
fn set_header_if_none<V>(&mut self, key: HeaderName, value: V) -> Result<(), HttpError>
|
||||
where
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
match self {
|
||||
RequestSender::Owned(head) => {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
//! Test helpers for actix http client to use during testing.
|
||||
|
||||
use actix_http::{h1, header::TryIntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
|
||||
use actix_http::{h1, header::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
|
||||
use bytes::Bytes;
|
||||
|
||||
#[cfg(feature = "cookies")]
|
||||
@@ -28,7 +28,10 @@ impl Default for TestResponse {
|
||||
|
||||
impl TestResponse {
|
||||
/// Create TestResponse and set header
|
||||
pub fn with_header(header: impl TryIntoHeaderPair) -> Self {
|
||||
pub fn with_header<H>(header: H) -> Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
Self::default().insert_header(header)
|
||||
}
|
||||
|
||||
@@ -39,8 +42,11 @@ impl TestResponse {
|
||||
}
|
||||
|
||||
/// Insert a header
|
||||
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
if let Ok((key, value)) = header.try_into_pair() {
|
||||
pub fn insert_header<H>(mut self, header: H) -> Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
if let Ok((key, value)) = header.try_into_header_pair() {
|
||||
self.head.headers.insert(key, value);
|
||||
return self;
|
||||
}
|
||||
@@ -48,8 +54,11 @@ impl TestResponse {
|
||||
}
|
||||
|
||||
/// Append a header
|
||||
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
if let Ok((key, value)) = header.try_into_pair() {
|
||||
pub fn append_header<H>(mut self, header: H) -> Self
|
||||
where
|
||||
H: IntoHeaderPair,
|
||||
{
|
||||
if let Ok((key, value)) = header.try_into_header_pair() {
|
||||
self.head.headers.append(key, value);
|
||||
return self;
|
||||
}
|
||||
|
@@ -39,7 +39,7 @@ use crate::{
|
||||
connect::{BoxedSocket, ConnectRequest},
|
||||
error::{HttpError, InvalidUrl, SendRequestError, WsClientError},
|
||||
http::{
|
||||
header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, AUTHORIZATION},
|
||||
header::{self, HeaderName, HeaderValue, IntoHeaderValue, AUTHORIZATION},
|
||||
ConnectionType, Method, StatusCode, Uri, Version,
|
||||
},
|
||||
response::ClientResponse,
|
||||
@@ -171,7 +171,7 @@ impl WebsocketsRequest {
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match value.try_into_value() {
|
||||
@@ -190,7 +190,7 @@ impl WebsocketsRequest {
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match value.try_into_value() {
|
||||
@@ -209,7 +209,7 @@ impl WebsocketsRequest {
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||
V: TryIntoHeaderValue,
|
||||
V: IntoHeaderValue,
|
||||
{
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => {
|
||||
@@ -445,7 +445,7 @@ mod tests {
|
||||
#[actix_rt::test]
|
||||
async fn test_header_override() {
|
||||
let req = Client::builder()
|
||||
.add_default_header((header::CONTENT_TYPE, "111"))
|
||||
.header(header::CONTENT_TYPE, "111")
|
||||
.finish()
|
||||
.ws("/")
|
||||
.set_header(header::CONTENT_TYPE, "222");
|
||||
|
@@ -22,14 +22,14 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
.service(no_params)
|
||||
.service(
|
||||
web::resource("/resource2/index.html")
|
||||
.wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3")))
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
|
||||
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
|
||||
.route(web::get().to(index_async)),
|
||||
)
|
||||
|
@@ -26,14 +26,14 @@ async fn main() -> std::io::Result<()> {
|
||||
|
||||
HttpServer::new(|| {
|
||||
App::new()
|
||||
.wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
.wrap(middleware::Compress::default())
|
||||
.wrap(middleware::Logger::default())
|
||||
.service(index)
|
||||
.service(no_params)
|
||||
.service(
|
||||
web::resource("/resource2/index.html")
|
||||
.wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3")))
|
||||
.wrap(middleware::DefaultHeaders::new().header("X-Version-R2", "0.3"))
|
||||
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
|
||||
.route(web::get().to(index_async)),
|
||||
)
|
||||
|
31
scripts/bump
31
scripts/bump
@@ -41,8 +41,6 @@ cat "$CHANGELOG_FILE" |
|
||||
# if word count of changelog chunk is 0 then insert filler changelog chunk
|
||||
if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then
|
||||
echo "* No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE"
|
||||
echo >>"$CHANGE_CHUNK_FILE"
|
||||
echo >>"$CHANGE_CHUNK_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "${2-}" ]; then
|
||||
@@ -84,33 +82,8 @@ rm -f $README_FILE.bak
|
||||
echo "manifest, changelog, and readme updated"
|
||||
echo
|
||||
echo "check other references:"
|
||||
rg --glob='**/Cargo.toml' "\
|
||||
${PACKAGE_NAME} ?= ?\"[^\"]+\"\
|
||||
|${PACKAGE_NAME} ?=.*version ?= ?\"([^\"]+)\"\
|
||||
|package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\"([^\"]+)\"\
|
||||
|version ?= ?\"([^\"]+)\".*package ?= ?\"${PACKAGE_NAME}\"" || true
|
||||
|
||||
echo
|
||||
read -p "Update all references: (y/N) " UPDATE_REFERENCES
|
||||
UPDATE_REFERENCES="${UPDATE_REFERENCES:-n}"
|
||||
|
||||
if [ "$UPDATE_REFERENCES" = 'y' ] || [ "$UPDATE_REFERENCES" = 'Y' ]; then
|
||||
|
||||
for f in $(fd Cargo.toml); do
|
||||
sed -i.bak -E \
|
||||
"s/^(${PACKAGE_NAME} ?= ?\")[^\"]+(\")$/\1${NEW_VERSION}\2/g" $f
|
||||
sed -i.bak -E \
|
||||
"s/^(${PACKAGE_NAME} ?=.*version ?= ?\")[^\"]+(\".*)$/\1${NEW_VERSION}\2/g" $f
|
||||
sed -i.bak -E \
|
||||
"s/^(.*package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\")[^\"]+(\".*)$/\1${NEW_VERSION}\2/g" $f
|
||||
sed -i.bak -E \
|
||||
"s/^(.*version ?= ?\")[^\"]+(\".*package ?= ?\"${PACKAGE_NAME}\".*)$/\1${NEW_VERSION}\2/g" $f
|
||||
|
||||
# remove backup file
|
||||
rm -f $f.bak
|
||||
done
|
||||
|
||||
fi
|
||||
rg "$PACKAGE_NAME =" || true
|
||||
rg "package = \"$PACKAGE_NAME\"" || true
|
||||
|
||||
if [ $MACOS ]; then
|
||||
printf "prepare $PACKAGE_NAME release $NEW_VERSION" | pbcopy
|
||||
|
@@ -4,25 +4,15 @@
|
||||
|
||||
set -x
|
||||
|
||||
EXIT=0
|
||||
cargo test --lib --tests -p=actix-router --all-features
|
||||
cargo test --lib --tests -p=actix-http --all-features
|
||||
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
cargo test --lib --tests -p=actix-web-codegen --all-features
|
||||
cargo test --lib --tests -p=awc --all-features
|
||||
cargo test --lib --tests -p=actix-http-test --all-features
|
||||
cargo test --lib --tests -p=actix-test --all-features
|
||||
cargo test --lib --tests -p=actix-files
|
||||
cargo test --lib --tests -p=actix-multipart --all-features
|
||||
cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
|
||||
save_exit_code() {
|
||||
eval $@
|
||||
local CMD_EXIT=$?
|
||||
[ "$CMD_EXIT" = "0" ] || EXIT=$CMD_EXIT
|
||||
}
|
||||
|
||||
save_exit_code cargo test --lib --tests -p=actix-router --all-features
|
||||
save_exit_code cargo test --lib --tests -p=actix-http --all-features
|
||||
save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
|
||||
save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features
|
||||
save_exit_code cargo test --lib --tests -p=awc --all-features
|
||||
save_exit_code cargo test --lib --tests -p=actix-http-test --all-features
|
||||
save_exit_code cargo test --lib --tests -p=actix-test --all-features
|
||||
save_exit_code cargo test --lib --tests -p=actix-files
|
||||
save_exit_code cargo test --lib --tests -p=actix-multipart --all-features
|
||||
save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features
|
||||
|
||||
save_exit_code cargo test --workspace --doc
|
||||
|
||||
exit $EXIT
|
||||
cargo test --workspace --doc
|
||||
|
@@ -1,41 +0,0 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
bold="\033[1m"
|
||||
reset="\033[0m"
|
||||
|
||||
unreleased_for() {
|
||||
DIR=$1
|
||||
|
||||
CARGO_MANIFEST=$DIR/Cargo.toml
|
||||
CHANGELOG_FILE=$DIR/CHANGES.md
|
||||
|
||||
# get current version
|
||||
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
|
||||
CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")"
|
||||
|
||||
CHANGE_CHUNK_FILE="$(mktemp)"
|
||||
|
||||
# get changelog chunk and save to temp file
|
||||
cat "$CHANGELOG_FILE" |
|
||||
# skip up to unreleased heading
|
||||
sed '1,/Unreleased/ d' |
|
||||
# take up to previous version heading
|
||||
sed "/$CURRENT_VERSION/ q" |
|
||||
# drop last line
|
||||
sed '$d' \
|
||||
>"$CHANGE_CHUNK_FILE"
|
||||
|
||||
# if word count of changelog chunk is 0 then exit
|
||||
if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then
|
||||
return 0;
|
||||
fi
|
||||
|
||||
echo "${bold}# ${PACKAGE_NAME}${reset} since ${bold}v$CURRENT_VERSION${reset}"
|
||||
cat "$CHANGE_CHUNK_FILE"
|
||||
}
|
||||
|
||||
for f in $(fd --absolute-path CHANGES.md); do
|
||||
unreleased_for $(dirname $f)
|
||||
done
|
146
src/any_body.rs
Normal file
146
src/any_body.rs
Normal file
@@ -0,0 +1,146 @@
|
||||
use std::{
|
||||
error::Error as StdError,
|
||||
mem,
|
||||
pin::Pin,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::body::{BodySize, BoxBody, MessageBody};
|
||||
use bytes::Bytes;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::Error;
|
||||
|
||||
pin_project! {
|
||||
#[derive(Debug)]
|
||||
#[project = AnyBodyProj]
|
||||
pub enum AnyBody<B = BoxBody> {
|
||||
None,
|
||||
Full { body: Bytes },
|
||||
Stream { #[pin] body: B },
|
||||
Boxed { body: BoxBody },
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: MessageBody + 'static> AnyBody<B> {
|
||||
pub fn into_body<B1>(self) -> AnyBody<B1> {
|
||||
match self {
|
||||
AnyBody::None => AnyBody::None,
|
||||
AnyBody::Full { body } => AnyBody::Full { body },
|
||||
AnyBody::Stream { body } => AnyBody::Boxed {
|
||||
body: BoxBody::new(body),
|
||||
},
|
||||
AnyBody::Boxed { body } => AnyBody::Boxed { body },
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Default for AnyBody<B> {
|
||||
fn default() -> Self {
|
||||
Self::Full { body: Bytes::new() }
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for AnyBody<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
B::Error: 'static,
|
||||
{
|
||||
type Error = Box<dyn StdError>;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
Self::None => BodySize::None,
|
||||
Self::Full { body } => body.size(),
|
||||
Self::Stream { body } => body.size(),
|
||||
Self::Boxed { body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
AnyBodyProj::None => Poll::Ready(None),
|
||||
AnyBodyProj::Full { body } => {
|
||||
let bytes = mem::take(body);
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
AnyBodyProj::Stream { body } => body.poll_next(cx).map_err(|err| err.into()),
|
||||
AnyBodyProj::Boxed { body } => body.as_pin_mut().poll_next(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = EitherAnyBodyProj]
|
||||
#[derive(Debug)]
|
||||
pub enum EitherAnyBody<L, R = BoxBody> {
|
||||
/// A body of type `L`.
|
||||
Left { #[pin] body: AnyBody<L> },
|
||||
|
||||
/// A body of type `R`.
|
||||
Right { #[pin] body: AnyBody<R> },
|
||||
}
|
||||
}
|
||||
|
||||
// impl<L> EitherAnyBody<L, BoxBody> {
|
||||
// /// Creates new `EitherBody` using left variant and boxed right variant.
|
||||
// pub fn new(body: L) -> Self {
|
||||
// Self::Left {
|
||||
// body: AnyBody::Stream { body },
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// impl<L, R> EitherAnyBody<L, R> {
|
||||
// /// Creates new `EitherBody` using left variant.
|
||||
// pub fn left(body: L) -> Self {
|
||||
// Self::Left {
|
||||
// body: AnyBody::Stream { body },
|
||||
// }
|
||||
// }
|
||||
|
||||
// /// Creates new `EitherBody` using right variant.
|
||||
// pub fn right(body: R) -> Self {
|
||||
// Self::Right {
|
||||
// body: AnyBody::Stream { body },
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
impl<L, R> MessageBody for EitherAnyBody<L, R>
|
||||
where
|
||||
L: MessageBody + 'static,
|
||||
R: MessageBody + 'static,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
Self::Left { body } => body.size(),
|
||||
Self::Right { body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
EitherAnyBodyProj::Left { body } => body.poll_next(cx).map_err(Error::from),
|
||||
EitherAnyBodyProj::Right { body } => body.poll_next(cx).map_err(Error::from),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use static_assertions::assert_eq_size;
|
||||
|
||||
use super::*;
|
||||
|
||||
assert_eq_size!(AnyBody<()>, [u8; 40]);
|
||||
assert_eq_size!(AnyBody<u64>, [u8; 40]); // how is this the same size as ()
|
||||
}
|
25
src/app.rs
25
src/app.rs
@@ -602,7 +602,7 @@ mod tests {
|
||||
App::new()
|
||||
.wrap(
|
||||
DefaultHeaders::new()
|
||||
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
||||
)
|
||||
.route("/test", web::get().to(HttpResponse::Ok)),
|
||||
)
|
||||
@@ -623,7 +623,7 @@ mod tests {
|
||||
.route("/test", web::get().to(HttpResponse::Ok))
|
||||
.wrap(
|
||||
DefaultHeaders::new()
|
||||
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
|
||||
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
|
||||
),
|
||||
)
|
||||
.await;
|
||||
@@ -706,25 +706,4 @@ mod tests {
|
||||
let body = read_body(resp).await;
|
||||
assert_eq!(body, Bytes::from_static(b"https://youtube.com/watch/12345"));
|
||||
}
|
||||
|
||||
/// compile-only test for returning app type from function
|
||||
pub fn foreign_app_type() -> App<
|
||||
impl ServiceFactory<
|
||||
ServiceRequest,
|
||||
Response = ServiceResponse<impl MessageBody>,
|
||||
Config = (),
|
||||
InitError = (),
|
||||
Error = Error,
|
||||
>,
|
||||
> {
|
||||
App::new()
|
||||
// logger can be removed without affecting the return type
|
||||
.wrap(crate::middleware::Logger::default())
|
||||
.route("/", web::to(|| async { "hello" }))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn return_foreign_app_type() {
|
||||
let _app = foreign_app_type();
|
||||
}
|
||||
}
|
||||
|
@@ -22,7 +22,6 @@ use crate::{
|
||||
type Guards = Vec<Box<dyn Guard>>;
|
||||
|
||||
/// Service factory to convert `Request` to a `ServiceRequest<S>`.
|
||||
///
|
||||
/// It also executes data factories.
|
||||
pub struct AppInit<T, B>
|
||||
where
|
||||
|
46
src/dev.rs
46
src/dev.rs
@@ -102,49 +102,3 @@ impl<B> BodyEncoding for crate::HttpResponse<B> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: remove this if it doesn't appear to be needed
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug)]
|
||||
pub(crate) enum AnyBody {
|
||||
None,
|
||||
Full { body: crate::web::Bytes },
|
||||
Boxed { body: actix_http::body::BoxBody },
|
||||
}
|
||||
|
||||
impl crate::body::MessageBody for AnyBody {
|
||||
type Error = crate::BoxError;
|
||||
|
||||
/// Body size hint.
|
||||
fn size(&self) -> crate::body::BodySize {
|
||||
match self {
|
||||
AnyBody::None => crate::body::BodySize::None,
|
||||
AnyBody::Full { body } => body.size(),
|
||||
AnyBody::Boxed { body } => body.size(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Attempt to pull out the next chunk of body bytes.
|
||||
fn poll_next(
|
||||
self: std::pin::Pin<&mut Self>,
|
||||
cx: &mut std::task::Context<'_>,
|
||||
) -> std::task::Poll<Option<Result<crate::web::Bytes, Self::Error>>> {
|
||||
match self.get_mut() {
|
||||
AnyBody::None => std::task::Poll::Ready(None),
|
||||
AnyBody::Full { body } => {
|
||||
let bytes = std::mem::take(body);
|
||||
std::task::Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
AnyBody::Boxed { body } => body.as_pin_mut().poll_next(cx),
|
||||
}
|
||||
}
|
||||
|
||||
fn try_into_bytes(self) -> Result<crate::web::Bytes, Self> {
|
||||
match self {
|
||||
AnyBody::None => Ok(crate::web::Bytes::new()),
|
||||
AnyBody::Full { body } => Ok(body),
|
||||
_ => Err(self),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -2,7 +2,7 @@ use std::{error::Error as StdError, fmt};
|
||||
|
||||
use actix_http::{body::BoxBody, Response};
|
||||
|
||||
use crate::{HttpResponse, ResponseError};
|
||||
use crate::{any_body::AnyBody, HttpResponse, ResponseError};
|
||||
|
||||
/// General purpose actix web error.
|
||||
///
|
||||
@@ -69,8 +69,15 @@ impl<T: ResponseError + 'static> From<T> for Error {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for Response<BoxBody> {
|
||||
fn from(err: Error) -> Response<BoxBody> {
|
||||
impl From<Error> for Response<AnyBody<BoxBody>> {
|
||||
fn from(err: Error) -> Self {
|
||||
err.error_response().into()
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Error> for actix_http::Response<BoxBody> {
|
||||
fn from(err: Error) -> Self {
|
||||
let res: actix_http::Response<_> = err.error_response().into();
|
||||
res.map_into_boxed_body()
|
||||
}
|
||||
}
|
||||
|
@@ -2,12 +2,12 @@ use std::{cell::RefCell, fmt, io::Write as _};
|
||||
|
||||
use actix_http::{
|
||||
body::BoxBody,
|
||||
header::{self, TryIntoHeaderValue as _},
|
||||
header::{self, IntoHeaderValue as _},
|
||||
StatusCode,
|
||||
};
|
||||
use bytes::{BufMut as _, BytesMut};
|
||||
|
||||
use crate::{Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
use crate::{any_body::AnyBody, Error, HttpRequest, HttpResponse, Responder, ResponseError};
|
||||
|
||||
/// Wraps errors to alter the generated response status code.
|
||||
///
|
||||
@@ -91,7 +91,9 @@ where
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
|
||||
res.set_body(BoxBody::new(buf.into_inner()))
|
||||
res.set_body(AnyBody::Full {
|
||||
body: buf.into_inner().freeze(),
|
||||
})
|
||||
}
|
||||
|
||||
InternalErrorType::Response(ref resp) => {
|
||||
|
@@ -1,5 +1,4 @@
|
||||
//! Error and Result module
|
||||
|
||||
// This is meant to be a glob import of the whole error module except for `Error`. Rustdoc can't yet
|
||||
// correctly resolve the conflicting `Error` type defined in this module, so these re-exports are
|
||||
// expanded manually.
|
||||
|
@@ -8,12 +8,13 @@ use std::{
|
||||
|
||||
use actix_http::{
|
||||
body::BoxBody,
|
||||
header::{self, TryIntoHeaderValue},
|
||||
header::{self, IntoHeaderValue},
|
||||
Response, StatusCode,
|
||||
};
|
||||
use bytes::BytesMut;
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
error::{downcast_dyn, downcast_get_type_id},
|
||||
helpers, HttpResponse,
|
||||
};
|
||||
@@ -33,7 +34,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||
///
|
||||
/// By default, the generated response uses a 500 Internal Server Error status code, a
|
||||
/// `Content-Type` of `text/plain`, and the body is set to `Self`'s `Display` impl.
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
let mut res = HttpResponse::new(self.status_code());
|
||||
|
||||
let mut buf = BytesMut::new();
|
||||
@@ -42,7 +43,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||
let mime = mime::TEXT_PLAIN_UTF_8.try_into_value().unwrap();
|
||||
res.headers_mut().insert(header::CONTENT_TYPE, mime);
|
||||
|
||||
res.set_body(BoxBody::new(buf))
|
||||
res.set_body(AnyBody::Full { body: buf.freeze() })
|
||||
}
|
||||
|
||||
downcast_get_type_id!();
|
||||
@@ -50,7 +51,7 @@ pub trait ResponseError: fmt::Debug + fmt::Display {
|
||||
|
||||
downcast_dyn!(ResponseError);
|
||||
|
||||
impl ResponseError for Box<dyn StdError + 'static> {}
|
||||
impl ResponseError for Box<dyn StdError> {}
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
impl ResponseError for actix_tls::accept::openssl::reexports::Error {}
|
||||
@@ -128,7 +129,15 @@ impl ResponseError for actix_http::error::ContentTypeError {
|
||||
|
||||
impl ResponseError for actix_http::ws::HandshakeError {
|
||||
fn error_response(&self) -> HttpResponse<BoxBody> {
|
||||
Response::from(self).map_into_boxed_body().into()
|
||||
Response::from(self)
|
||||
.map_body(|_, body| AnyBody::Boxed { body })
|
||||
.into()
|
||||
}
|
||||
}
|
||||
|
||||
impl ResponseError for actix_http::error::EncoderError {
|
||||
fn error_response(&self) -> HttpResponse {
|
||||
todo!("")
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
|
||||
use regex::Regex;
|
||||
use std::fmt::{self, Write};
|
||||
|
||||
use super::{ExtendedValue, Header, TryIntoHeaderValue, Writer};
|
||||
use super::{ExtendedValue, Header, IntoHeaderValue, Writer};
|
||||
use crate::http::header;
|
||||
|
||||
/// Split at the index of the first `needle` if it exists or at the end.
|
||||
@@ -454,7 +454,7 @@ impl ContentDisposition {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for ContentDisposition {
|
||||
impl IntoHeaderValue for ContentDisposition {
|
||||
type Error = header::InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<header::HeaderValue, Self::Error> {
|
||||
|
@@ -3,7 +3,7 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer, CONTENT_RANGE};
|
||||
use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE};
|
||||
use crate::error::ParseError;
|
||||
|
||||
crate::http::header::common_header! {
|
||||
@@ -196,7 +196,7 @@ impl Display for ContentRangeSpec {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for ContentRangeSpec {
|
||||
impl IntoHeaderValue for ContentRangeSpec {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||
|
@@ -3,7 +3,7 @@ use std::{
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
|
||||
use super::{HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
|
||||
|
||||
/// check that each char in the slice is either:
|
||||
/// 1. `%x21`, or
|
||||
@@ -159,7 +159,7 @@ impl FromStr for EntityTag {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for EntityTag {
|
||||
impl IntoHeaderValue for EntityTag {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||
|
@@ -1,8 +1,8 @@
|
||||
use std::fmt::{self, Display, Write};
|
||||
|
||||
use super::{
|
||||
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, InvalidHeaderValue,
|
||||
TryIntoHeaderValue, Writer,
|
||||
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, IntoHeaderValue,
|
||||
InvalidHeaderValue, Writer,
|
||||
};
|
||||
use crate::error::ParseError;
|
||||
use crate::http::header;
|
||||
@@ -96,7 +96,7 @@ impl Display for IfRange {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for IfRange {
|
||||
impl IntoHeaderValue for IfRange {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||
|
@@ -125,7 +125,7 @@ macro_rules! common_header {
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::http::header::TryIntoHeaderValue for $id {
|
||||
impl $crate::http::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::http::header::InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -172,7 +172,7 @@ macro_rules! common_header {
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::http::header::TryIntoHeaderValue for $id {
|
||||
impl $crate::http::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::http::header::InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -211,7 +211,7 @@ macro_rules! common_header {
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::http::header::TryIntoHeaderValue for $id {
|
||||
impl $crate::http::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::http::header::InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
@@ -266,7 +266,7 @@ macro_rules! common_header {
|
||||
}
|
||||
}
|
||||
|
||||
impl $crate::http::header::TryIntoHeaderValue for $id {
|
||||
impl $crate::http::header::IntoHeaderValue for $id {
|
||||
type Error = $crate::http::header::InvalidHeaderValue;
|
||||
|
||||
#[inline]
|
||||
|
@@ -6,7 +6,7 @@ use std::{
|
||||
|
||||
use actix_http::{error::ParseError, header, HttpMessage};
|
||||
|
||||
use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
|
||||
use super::{Header, HeaderName, HeaderValue, IntoHeaderValue, InvalidHeaderValue, Writer};
|
||||
|
||||
/// `Range` header, defined
|
||||
/// in [RFC 7233 §3.1](https://datatracker.ietf.org/doc/html/rfc7233#section-3.1)
|
||||
@@ -274,7 +274,7 @@ impl Header for Range {
|
||||
}
|
||||
}
|
||||
|
||||
impl TryIntoHeaderValue for Range {
|
||||
impl IntoHeaderValue for Range {
|
||||
type Error = InvalidHeaderValue;
|
||||
|
||||
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {
|
||||
|
@@ -70,6 +70,7 @@
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
mod any_body;
|
||||
mod app;
|
||||
mod app_service;
|
||||
mod config;
|
||||
@@ -86,6 +87,7 @@ pub mod middleware;
|
||||
mod request;
|
||||
mod request_data;
|
||||
mod resource;
|
||||
mod responder;
|
||||
mod response;
|
||||
mod rmap;
|
||||
mod route;
|
||||
@@ -108,10 +110,12 @@ pub use crate::error::{Error, ResponseError, Result};
|
||||
pub use crate::extract::FromRequest;
|
||||
pub use crate::request::HttpRequest;
|
||||
pub use crate::resource::Resource;
|
||||
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
|
||||
pub use crate::responder::Responder;
|
||||
pub use crate::response::{HttpResponse, HttpResponseBuilder};
|
||||
pub use crate::route::Route;
|
||||
pub use crate::scope::Scope;
|
||||
pub use crate::server::HttpServer;
|
||||
pub use crate::types::Either;
|
||||
// TODO: is exposing the error directly really needed
|
||||
pub use crate::types::{Either, EitherExtractError};
|
||||
|
||||
pub(crate) type BoxError = Box<dyn std::error::Error>;
|
||||
|
@@ -6,15 +6,12 @@ use std::{
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use actix_http::body::MessageBody;
|
||||
use actix_service::{Service, Transform};
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
dev::{Service, Transform},
|
||||
error::Error,
|
||||
service::ServiceResponse,
|
||||
};
|
||||
use crate::{error::Error, service::ServiceResponse};
|
||||
|
||||
/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap),
|
||||
/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition).
|
||||
@@ -55,7 +52,7 @@ where
|
||||
T::Response: MapServiceResponseBody,
|
||||
T::Error: Into<Error>,
|
||||
{
|
||||
type Response = ServiceResponse<BoxBody>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Transform = CompatMiddleware<T::Transform>;
|
||||
type InitError = T::InitError;
|
||||
@@ -80,7 +77,7 @@ where
|
||||
S::Response: MapServiceResponseBody,
|
||||
S::Error: Into<Error>,
|
||||
{
|
||||
type Response = ServiceResponse<BoxBody>;
|
||||
type Response = ServiceResponse;
|
||||
type Error = Error;
|
||||
type Future = CompatMiddlewareFuture<S::Future>;
|
||||
|
||||
@@ -105,7 +102,7 @@ where
|
||||
T: MapServiceResponseBody,
|
||||
E: Into<Error>,
|
||||
{
|
||||
type Output = Result<ServiceResponse<BoxBody>, Error>;
|
||||
type Output = Result<ServiceResponse, Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let res = match ready!(self.project().fut.poll(cx)) {
|
||||
@@ -119,15 +116,14 @@ where
|
||||
|
||||
/// Convert `ServiceResponse`'s `ResponseBody<B>` generic type to `ResponseBody<Body>`.
|
||||
pub trait MapServiceResponseBody {
|
||||
fn map_body(self) -> ServiceResponse<BoxBody>;
|
||||
fn map_body(self) -> ServiceResponse;
|
||||
}
|
||||
|
||||
impl<B> MapServiceResponseBody for ServiceResponse<B>
|
||||
where
|
||||
B: MessageBody + 'static,
|
||||
B: MessageBody + Unpin + 'static,
|
||||
{
|
||||
#[inline]
|
||||
fn map_body(self) -> ServiceResponse<BoxBody> {
|
||||
fn map_body(self) -> ServiceResponse {
|
||||
self.map_into_boxed_body()
|
||||
}
|
||||
}
|
||||
|
@@ -10,7 +10,7 @@ use std::{
|
||||
};
|
||||
|
||||
use actix_http::{
|
||||
body::{EitherBody, MessageBody},
|
||||
body::MessageBody,
|
||||
encoding::Encoder,
|
||||
header::{ContentEncoding, ACCEPT_ENCODING},
|
||||
StatusCode,
|
||||
@@ -22,6 +22,7 @@ use once_cell::sync::Lazy;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
dev::BodyEncoding,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error, HttpResponse,
|
||||
@@ -61,7 +62,7 @@ where
|
||||
B: MessageBody,
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
type Response = ServiceResponse<EitherBody<Encoder<B>>>;
|
||||
type Response = ServiceResponse<Encoder<AnyBody<B>>>;
|
||||
type Error = Error;
|
||||
type Transform = CompressMiddleware<S>;
|
||||
type InitError = ();
|
||||
@@ -111,7 +112,7 @@ where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
{
|
||||
type Response = ServiceResponse<EitherBody<Encoder<B>>>;
|
||||
type Response = ServiceResponse<Encoder<AnyBody<B>>>;
|
||||
type Error = Error;
|
||||
type Future = Either<CompressResponse<S, B>, Ready<Result<Self::Response, Self::Error>>>;
|
||||
|
||||
@@ -146,12 +147,15 @@ where
|
||||
let res = HttpResponse::with_body(
|
||||
StatusCode::NOT_ACCEPTABLE,
|
||||
SUPPORTED_ALGORITHM_NAMES.clone(),
|
||||
);
|
||||
)
|
||||
.map_body(|_, body| match body {
|
||||
AnyBody::Full { body } => AnyBody::Stream {
|
||||
body: Encoder::not_acceptable(body),
|
||||
},
|
||||
_ => unreachable!("probably"),
|
||||
});
|
||||
|
||||
Either::right(ok(req
|
||||
.into_response(res)
|
||||
.map_into_boxed_body()
|
||||
.map_into_right_body()))
|
||||
Either::right(ok(req.into_response(res)))
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -174,7 +178,7 @@ where
|
||||
B: MessageBody,
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
{
|
||||
type Output = Result<ServiceResponse<EitherBody<Encoder<B>>>, Error>;
|
||||
type Output = Result<ServiceResponse<Encoder<AnyBody<B>>>, Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
let this = self.project();
|
||||
@@ -187,8 +191,8 @@ where
|
||||
*this.encoding
|
||||
};
|
||||
|
||||
Poll::Ready(Ok(resp.map_body(move |head, body| {
|
||||
EitherBody::left(Encoder::response(enc, head, body))
|
||||
Poll::Ready(Ok(resp.map_body(move |head, body| AnyBody::Stream {
|
||||
body: Encoder::response(enc, head, body),
|
||||
})))
|
||||
}
|
||||
|
||||
|
@@ -106,7 +106,7 @@ mod tests {
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
middleware::{err_handlers::*, Compat},
|
||||
middleware::err_handlers::*,
|
||||
test::{self, TestRequest},
|
||||
HttpResponse,
|
||||
};
|
||||
@@ -116,8 +116,7 @@ mod tests {
|
||||
res.response_mut()
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
|
||||
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||
Ok(ErrorHandlerResponse::Response(res))
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
@@ -126,9 +125,7 @@ mod tests {
|
||||
ok(req.into_response(HttpResponse::InternalServerError().finish()))
|
||||
};
|
||||
|
||||
let mw = Compat::new(
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
|
||||
);
|
||||
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mw = Condition::new(true, mw)
|
||||
.new_transform(srv.into_service())
|
||||
@@ -144,9 +141,7 @@ mod tests {
|
||||
ok(req.into_response(HttpResponse::InternalServerError().finish()))
|
||||
};
|
||||
|
||||
let mw = Compat::new(
|
||||
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
|
||||
);
|
||||
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
|
||||
|
||||
let mw = Condition::new(false, mw)
|
||||
.new_transform(srv.into_service())
|
||||
|
@@ -16,7 +16,7 @@ use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
dev::{Service, Transform},
|
||||
http::header::{HeaderMap, HeaderName, HeaderValue, TryIntoHeaderPair, CONTENT_TYPE},
|
||||
http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE},
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
Error,
|
||||
};
|
||||
@@ -29,81 +29,79 @@ use crate::{
|
||||
/// ```
|
||||
/// use actix_web::{web, http, middleware, App, HttpResponse};
|
||||
///
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::DefaultHeaders::new().add(("X-Version", "0.2")))
|
||||
/// .service(
|
||||
/// web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// fn main() {
|
||||
/// let app = App::new()
|
||||
/// .wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
|
||||
/// .service(
|
||||
/// web::resource("/test")
|
||||
/// .route(web::get().to(|| HttpResponse::Ok()))
|
||||
/// .route(web::method(http::Method::HEAD).to(|| HttpResponse::MethodNotAllowed()))
|
||||
/// );
|
||||
/// }
|
||||
/// ```
|
||||
#[derive(Debug, Clone, Default)]
|
||||
#[derive(Clone)]
|
||||
pub struct DefaultHeaders {
|
||||
inner: Rc<Inner>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
struct Inner {
|
||||
headers: HeaderMap,
|
||||
}
|
||||
|
||||
impl Default for DefaultHeaders {
|
||||
fn default() -> Self {
|
||||
DefaultHeaders {
|
||||
inner: Rc::new(Inner {
|
||||
headers: HeaderMap::new(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DefaultHeaders {
|
||||
/// Constructs an empty `DefaultHeaders` middleware.
|
||||
#[inline]
|
||||
pub fn new() -> DefaultHeaders {
|
||||
DefaultHeaders::default()
|
||||
}
|
||||
|
||||
/// Adds a header to the default set.
|
||||
///
|
||||
/// # Panics
|
||||
/// Panics when resolved header name or value is invalid.
|
||||
#[allow(clippy::should_implement_trait)]
|
||||
pub fn add(mut self, header: impl TryIntoHeaderPair) -> Self {
|
||||
// standard header terminology `insert` or `append` for this method would make the behavior
|
||||
// of this middleware less obvious since it only adds the headers if they are not present
|
||||
|
||||
match header.try_into_pair() {
|
||||
Ok((key, value)) => Rc::get_mut(&mut self.inner)
|
||||
.expect("All default headers must be added before cloning.")
|
||||
.headers
|
||||
.append(key, value),
|
||||
Err(err) => panic!("Invalid header: {}", err.into()),
|
||||
}
|
||||
|
||||
self
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
#[deprecated(
|
||||
since = "4.0.0",
|
||||
note = "Prefer `.add((key, value))`. Will be removed in v5."
|
||||
)]
|
||||
pub fn header<K, V>(self, key: K, value: V) -> Self
|
||||
#[inline]
|
||||
pub fn header<K, V>(mut self, key: K, value: V) -> Self
|
||||
where
|
||||
HeaderName: TryFrom<K>,
|
||||
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
|
||||
HeaderValue: TryFrom<V>,
|
||||
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
|
||||
{
|
||||
self.add((
|
||||
HeaderName::try_from(key)
|
||||
.map_err(Into::into)
|
||||
.expect("Invalid header name"),
|
||||
HeaderValue::try_from(value)
|
||||
.map_err(Into::into)
|
||||
.expect("Invalid header value"),
|
||||
))
|
||||
#[allow(clippy::match_wild_err_arm)]
|
||||
match HeaderName::try_from(key) {
|
||||
Ok(key) => match HeaderValue::try_from(value) {
|
||||
Ok(value) => {
|
||||
Rc::get_mut(&mut self.inner)
|
||||
.expect("Multiple copies exist")
|
||||
.headers
|
||||
.append(key, value);
|
||||
}
|
||||
Err(_) => panic!("Can not create header value"),
|
||||
},
|
||||
Err(_) => panic!("Can not create header name"),
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Adds a default *Content-Type* header if response does not contain one.
|
||||
///
|
||||
/// Default is `application/octet-stream`.
|
||||
pub fn add_content_type(self) -> Self {
|
||||
self.add((
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/octet-stream"),
|
||||
))
|
||||
pub fn add_content_type(mut self) -> Self {
|
||||
Rc::get_mut(&mut self.inner)
|
||||
.expect("Multiple `Inner` copies exist.")
|
||||
.headers
|
||||
.insert(
|
||||
CONTENT_TYPE,
|
||||
HeaderValue::from_static("application/octet-stream"),
|
||||
);
|
||||
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -121,7 +119,7 @@ where
|
||||
fn new_transform(&self, service: S) -> Self::Future {
|
||||
ready(Ok(DefaultHeadersMiddleware {
|
||||
service,
|
||||
inner: Rc::clone(&self.inner),
|
||||
inner: self.inner.clone(),
|
||||
}))
|
||||
}
|
||||
}
|
||||
@@ -194,27 +192,22 @@ mod tests {
|
||||
use crate::{
|
||||
dev::ServiceRequest,
|
||||
http::header::CONTENT_TYPE,
|
||||
test::{self, TestRequest},
|
||||
test::{ok_service, TestRequest},
|
||||
HttpResponse,
|
||||
};
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn adding_default_headers() {
|
||||
async fn test_default_headers() {
|
||||
let mw = DefaultHeaders::new()
|
||||
.add(("X-TEST", "0001"))
|
||||
.add(("X-TEST-TWO", HeaderValue::from_static("123")))
|
||||
.new_transform(test::ok_service())
|
||||
.header(CONTENT_TYPE, "0001")
|
||||
.new_transform(ok_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let res = mw.call(req).await.unwrap();
|
||||
assert_eq!(res.headers().get("x-test").unwrap(), "0001");
|
||||
assert_eq!(res.headers().get("x-test-two").unwrap(), "123");
|
||||
}
|
||||
let resp = mw.call(req).await.unwrap();
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn no_override_existing() {
|
||||
let req = TestRequest::default().to_srv_request();
|
||||
let srv = |req: ServiceRequest| {
|
||||
ok(req.into_response(
|
||||
@@ -224,7 +217,7 @@ mod tests {
|
||||
))
|
||||
};
|
||||
let mw = DefaultHeaders::new()
|
||||
.add((CONTENT_TYPE, "0001"))
|
||||
.header(CONTENT_TYPE, "0001")
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -233,10 +226,11 @@ mod tests {
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn adding_content_type() {
|
||||
async fn test_content_type() {
|
||||
let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
|
||||
let mw = DefaultHeaders::new()
|
||||
.add_content_type()
|
||||
.new_transform(test::ok_service())
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
@@ -247,16 +241,4 @@ mod tests {
|
||||
"application/octet-stream"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn invalid_header_name() {
|
||||
DefaultHeaders::new().add((":", "hello"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn invalid_header_value() {
|
||||
DefaultHeaders::new().add(("x-test", "\n"));
|
||||
}
|
||||
}
|
||||
|
@@ -13,7 +13,6 @@ use futures_core::{future::LocalBoxFuture, ready};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::EitherBody,
|
||||
dev::{ServiceRequest, ServiceResponse},
|
||||
http::StatusCode,
|
||||
Error, Result,
|
||||
@@ -22,10 +21,10 @@ use crate::{
|
||||
/// Return type for [`ErrorHandlers`] custom handlers.
|
||||
pub enum ErrorHandlerResponse<B> {
|
||||
/// Immediate HTTP response.
|
||||
Response(ServiceResponse<EitherBody<B>>),
|
||||
Response(ServiceResponse<B>),
|
||||
|
||||
/// A future that resolves to an HTTP response.
|
||||
Future(LocalBoxFuture<'static, Result<ServiceResponse<EitherBody<B>>, Error>>),
|
||||
Future(LocalBoxFuture<'static, Result<ServiceResponse<B>, Error>>),
|
||||
}
|
||||
|
||||
type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>>;
|
||||
@@ -45,8 +44,7 @@ type ErrorHandler<B> = dyn Fn(ServiceResponse<B>) -> Result<ErrorHandlerResponse
|
||||
/// res.response_mut()
|
||||
/// .headers_mut()
|
||||
/// .insert(header::CONTENT_TYPE, header::HeaderValue::from_static("Error"));
|
||||
///
|
||||
/// Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||
/// Ok(ErrorHandlerResponse::Response(res))
|
||||
/// }
|
||||
///
|
||||
/// let app = App::new()
|
||||
@@ -68,7 +66,7 @@ type Handlers<B> = Rc<AHashMap<StatusCode, Box<ErrorHandler<B>>>>;
|
||||
impl<B> Default for ErrorHandlers<B> {
|
||||
fn default() -> Self {
|
||||
ErrorHandlers {
|
||||
handlers: Default::default(),
|
||||
handlers: Rc::new(AHashMap::default()),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -97,7 +95,7 @@ where
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<EitherBody<B>>;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type Transform = ErrorHandlersMiddleware<S, B>;
|
||||
type InitError = ();
|
||||
@@ -121,7 +119,7 @@ where
|
||||
S::Future: 'static,
|
||||
B: 'static,
|
||||
{
|
||||
type Response = ServiceResponse<EitherBody<B>>;
|
||||
type Response = ServiceResponse<B>;
|
||||
type Error = Error;
|
||||
type Future = ErrorHandlersFuture<S::Future, B>;
|
||||
|
||||
@@ -145,8 +143,8 @@ pin_project! {
|
||||
fut: Fut,
|
||||
handlers: Handlers<B>,
|
||||
},
|
||||
ErrorHandlerFuture {
|
||||
fut: LocalBoxFuture<'static, Result<ServiceResponse<EitherBody<B>>, Error>>,
|
||||
HandlerFuture {
|
||||
fut: LocalBoxFuture<'static, Fut::Output>,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -155,29 +153,25 @@ impl<Fut, B> Future for ErrorHandlersFuture<Fut, B>
|
||||
where
|
||||
Fut: Future<Output = Result<ServiceResponse<B>, Error>>,
|
||||
{
|
||||
type Output = Result<ServiceResponse<EitherBody<B>>, Error>;
|
||||
type Output = Fut::Output;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
match self.as_mut().project() {
|
||||
ErrorHandlersProj::ServiceFuture { fut, handlers } => {
|
||||
let res = ready!(fut.poll(cx))?;
|
||||
|
||||
match handlers.get(&res.status()) {
|
||||
Some(handler) => match handler(res)? {
|
||||
ErrorHandlerResponse::Response(res) => Poll::Ready(Ok(res)),
|
||||
ErrorHandlerResponse::Future(fut) => {
|
||||
self.as_mut()
|
||||
.set(ErrorHandlersFuture::ErrorHandlerFuture { fut });
|
||||
|
||||
.set(ErrorHandlersFuture::HandlerFuture { fut });
|
||||
self.poll(cx)
|
||||
}
|
||||
},
|
||||
|
||||
None => Poll::Ready(Ok(res.map_into_left_body())),
|
||||
None => Poll::Ready(Ok(res)),
|
||||
}
|
||||
}
|
||||
|
||||
ErrorHandlersProj::ErrorHandlerFuture { fut } => fut.as_mut().poll(cx),
|
||||
ErrorHandlersProj::HandlerFuture { fut } => fut.as_mut().poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -186,33 +180,32 @@ where
|
||||
mod tests {
|
||||
use actix_service::IntoService;
|
||||
use actix_utils::future::ok;
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::FutureExt as _;
|
||||
|
||||
use super::*;
|
||||
use crate::{
|
||||
http::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
},
|
||||
test::{self, TestRequest},
|
||||
use crate::http::{
|
||||
header::{HeaderValue, CONTENT_TYPE},
|
||||
StatusCode,
|
||||
};
|
||||
use crate::test::{self, TestRequest};
|
||||
use crate::HttpResponse;
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn render_500<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||
res.response_mut()
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
Ok(ErrorHandlerResponse::Response(res))
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn add_header_error_handler() {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn error_handler<B>(mut res: ServiceResponse<B>) -> Result<ErrorHandlerResponse<B>> {
|
||||
res.response_mut()
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
|
||||
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||
}
|
||||
|
||||
let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
async fn test_handler() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
ok(req.into_response(HttpResponse::InternalServerError().finish()))
|
||||
};
|
||||
|
||||
let mw = ErrorHandlers::new()
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500)
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -221,25 +214,24 @@ mod tests {
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn render_500_async<B: 'static>(
|
||||
mut res: ServiceResponse<B>,
|
||||
) -> Result<ErrorHandlerResponse<B>> {
|
||||
res.response_mut()
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
Ok(ErrorHandlerResponse::Future(ok(res).boxed_local()))
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn add_header_error_handler_async() {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn error_handler<B: 'static>(
|
||||
mut res: ServiceResponse<B>,
|
||||
) -> Result<ErrorHandlerResponse<B>> {
|
||||
res.response_mut()
|
||||
.headers_mut()
|
||||
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
|
||||
|
||||
Ok(ErrorHandlerResponse::Future(
|
||||
ok(res.map_into_left_body()).boxed_local(),
|
||||
))
|
||||
}
|
||||
|
||||
let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
async fn test_handler_async() {
|
||||
let srv = |req: ServiceRequest| {
|
||||
ok(req.into_response(HttpResponse::InternalServerError().finish()))
|
||||
};
|
||||
|
||||
let mw = ErrorHandlers::new()
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, render_500_async)
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
@@ -247,34 +239,4 @@ mod tests {
|
||||
let resp = test::call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
|
||||
}
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn changes_body_type() {
|
||||
#[allow(clippy::unnecessary_wraps)]
|
||||
fn error_handler<B: 'static>(
|
||||
res: ServiceResponse<B>,
|
||||
) -> Result<ErrorHandlerResponse<B>> {
|
||||
let (req, res) = res.into_parts();
|
||||
let res = res.set_body(Bytes::from("sorry, that's no bueno"));
|
||||
|
||||
let res = ServiceResponse::new(req, res)
|
||||
.map_into_boxed_body()
|
||||
.map_into_right_body();
|
||||
|
||||
Ok(ErrorHandlerResponse::Response(res))
|
||||
}
|
||||
|
||||
let srv = test::simple_service(StatusCode::INTERNAL_SERVER_ERROR);
|
||||
|
||||
let mw = ErrorHandlers::new()
|
||||
.handler(StatusCode::INTERNAL_SERVER_ERROR, error_handler)
|
||||
.new_transform(srv.into_service())
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let res = test::call_service(&mw, TestRequest::default().to_srv_request()).await;
|
||||
assert_eq!(test::read_body(res).await, "sorry, that's no bueno");
|
||||
}
|
||||
|
||||
// TODO: test where error is thrown
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ use regex::{Regex, RegexSet};
|
||||
use time::{format_description::well_known::Rfc3339, OffsetDateTime};
|
||||
|
||||
use crate::{
|
||||
any_body::AnyBody,
|
||||
body::{BodySize, MessageBody},
|
||||
http::header::HeaderName,
|
||||
service::{ServiceRequest, ServiceResponse},
|
||||
@@ -175,7 +176,7 @@ impl Default for Logger {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Transform<S, ServiceRequest> for Logger
|
||||
impl<S, B: 'static> Transform<S, ServiceRequest> for Logger
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
@@ -210,7 +211,7 @@ pub struct LoggerMiddleware<S> {
|
||||
service: S,
|
||||
}
|
||||
|
||||
impl<S, B> Service<ServiceRequest> for LoggerMiddleware<S>
|
||||
impl<S, B: 'static> Service<ServiceRequest> for LoggerMiddleware<S>
|
||||
where
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
B: MessageBody,
|
||||
@@ -262,7 +263,7 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, B> Future for LoggerResponse<S, B>
|
||||
impl<S, B: 'static> Future for LoggerResponse<S, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
S: Service<ServiceRequest, Response = ServiceResponse<B>, Error = Error>,
|
||||
@@ -290,11 +291,13 @@ where
|
||||
let time = *this.time;
|
||||
let format = this.format.take();
|
||||
|
||||
Poll::Ready(Ok(res.map_body(move |_, body| StreamLog {
|
||||
body,
|
||||
time,
|
||||
format,
|
||||
size: 0,
|
||||
Poll::Ready(Ok(res.map_body(move |_, body| AnyBody::Stream {
|
||||
body: StreamLog {
|
||||
body: body.into_body(),
|
||||
time,
|
||||
format,
|
||||
size: 0,
|
||||
},
|
||||
})))
|
||||
}
|
||||
}
|
||||
@@ -302,7 +305,7 @@ where
|
||||
pin_project! {
|
||||
pub struct StreamLog<B> {
|
||||
#[pin]
|
||||
body: B,
|
||||
body: AnyBody<B>,
|
||||
format: Option<Format>,
|
||||
size: usize,
|
||||
time: OffsetDateTime,
|
||||
@@ -322,10 +325,13 @@ pin_project! {
|
||||
}
|
||||
}
|
||||
|
||||
impl<B: MessageBody> MessageBody for StreamLog<B> {
|
||||
type Error = B::Error;
|
||||
impl<B> MessageBody for StreamLog<B>
|
||||
where
|
||||
B: MessageBody,
|
||||
B::Error: Into<Error>,
|
||||
{
|
||||
type Error = Error;
|
||||
|
||||
#[inline]
|
||||
fn size(&self) -> BodySize {
|
||||
self.body.size()
|
||||
}
|
||||
@@ -341,7 +347,7 @@ impl<B: MessageBody> MessageBody for StreamLog<B> {
|
||||
*this.size += chunk.len();
|
||||
Poll::Ready(Some(Ok(chunk)))
|
||||
}
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(err))),
|
||||
Some(Err(err)) => Poll::Ready(Some(Err(err.into()))),
|
||||
None => Poll::Ready(None),
|
||||
}
|
||||
}
|
||||
|
@@ -33,9 +33,9 @@ mod tests {
|
||||
let _ = App::new()
|
||||
.wrap(Compat::new(Logger::default()))
|
||||
.wrap(Condition::new(true, DefaultHeaders::new()))
|
||||
.wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2")))
|
||||
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
|
||||
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
|
||||
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||
Ok(ErrorHandlerResponse::Response(res))
|
||||
}))
|
||||
.wrap(Logger::default())
|
||||
.wrap(NormalizePath::new(TrailingSlash::Trim));
|
||||
@@ -44,9 +44,9 @@ mod tests {
|
||||
.wrap(NormalizePath::new(TrailingSlash::Trim))
|
||||
.wrap(Logger::default())
|
||||
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
|
||||
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
|
||||
Ok(ErrorHandlerResponse::Response(res))
|
||||
}))
|
||||
.wrap(DefaultHeaders::new().add(("X-Test2", "X-Value2")))
|
||||
.wrap(DefaultHeaders::new().header("X-Test2", "X-Value2"))
|
||||
.wrap(Condition::new(true, DefaultHeaders::new()))
|
||||
.wrap(Compat::new(Logger::default()));
|
||||
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user