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

Compare commits

..

7 Commits

Author SHA1 Message Date
Rob Ede
8d2abe4b35 expose encoder error 2021-12-08 22:53:08 +00:00
Rob Ede
f12f62ba73 body ergo v4 using any body 2021-12-08 22:45:54 +00:00
Rob Ede
fb5b4734a4 Merge remote-tracking branch 'origin/master' into remove-extensions-from-head 2021-12-08 07:30:14 +00:00
Rob Ede
4d9dd30c72 fix doc test 2021-12-07 21:50:20 +00:00
Rob Ede
d7a1b434cb update changelog 2021-12-07 21:26:28 +00:00
Rob Ede
d5ad250193 update changelog 2021-12-07 21:04:35 +00:00
Rob Ede
818c0f8cad split request data container out from requesthead 2021-12-07 20:56:28 +00:00
105 changed files with 1228 additions and 1525 deletions

View File

@@ -2,22 +2,6 @@
## Unreleased - 2021-xx-xx
### 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]
### Removed
* Top-level `EitherExtractError` export. [#2510]
* Conversion implementations for `either` crate. [#2516]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2516]: https://github.com/actix/actix-web/pull/2516
## 4.0.0-beta.14 - 2021-12-11
### Added
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
* `AcceptEncoding` typed header. [#2482]
* `Range` typed header. [#2485]
@@ -25,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]
@@ -54,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

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-web"
version = "4.0.0-beta.14"
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.15"
actix-http = "3.0.0-beta.14"
actix-router = "0.5.0-beta.2"
actix-web-codegen = "0.5.0-beta.6"
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.8", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.13", 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"

View File

@@ -6,10 +6,10 @@
<p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.14)](https://docs.rs/actix-web/4.0.0-beta.14)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.13)](https://docs.rs/actix-web/4.0.0-beta.13)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-web.svg)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.14)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.13)
<br />
[![build status](https://github.com/actix/actix-web/workflows/CI%20%28Linux%29/badge.svg?branch=master&event=push)](https://github.com/actix/actix-web/actions)
[![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View File

@@ -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]

View File

@@ -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.15"
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.14", 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.8"
actix-web = "4.0.0-beta.14"
actix-web = "4.0.0-beta.11"
actix-test = "0.1.0-beta.7"

View File

@@ -3,11 +3,11 @@
> Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.10)](https://docs.rs/actix-files/0.6.0-beta.10)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.9)](https://docs.rs/actix-files/0.6.0-beta.9)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-files.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.10/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.10)
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.9/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.9)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -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;

View File

@@ -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]

View File

@@ -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.13", 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.14", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.15"
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.14"

View File

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

View File

@@ -1,15 +1,6 @@
# Changes
## Unreleased - 2021-xx-xx
### Changed
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
## 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]
@@ -27,7 +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 optimisations on body types that are done in exactly one poll/chunk. [#2497]
### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468]
@@ -55,7 +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
## 3.0.0-beta.14 - 2021-11-30
@@ -266,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]
@@ -277,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]

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-http"
version = "3.0.0-beta.15"
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"]
@@ -56,7 +56,7 @@ derive_more = "0.99.5"
encoding_rs = "0.8"
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"
h2 = "0.3.1"
http = "0.2.5"
httparse = "1.5.1"
httpdate = "1.0.1"
@@ -81,11 +81,10 @@ 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.14"
actix-web = "4.0.0-beta.13"
async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9"

View File

@@ -3,11 +3,11 @@
> HTTP primitives for the Actix ecosystem.
[![crates.io](https://img.shields.io/crates/v/actix-http?label=latest)](https://crates.io/crates/actix-http)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.15)](https://docs.rs/actix-http/3.0.0-beta.15)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.14)](https://docs.rs/actix-http/3.0.0-beta.14)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-http.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.15)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.14/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.14)
[![Download](https://img.shields.io/crates/d/actix-http.svg)](https://crates.io/crates/actix-http)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -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
}

View File

@@ -51,34 +51,6 @@ impl MessageBody for BoxBody {
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err))
}
fn is_complete_body(&self) -> bool {
self.0.is_complete_body()
}
fn take_complete_body(&mut self) -> Bytes {
debug_assert!(
self.is_complete_body(),
"boxed type does not allow taking complete body; caller should make sure to \
call `is_complete_body` first",
);
// we do not have DerefMut access to call take_complete_body directly but since
// is_complete_body is true we should expect the entire bytes chunk in one poll_next
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
match self.as_pin_mut().poll_next(&mut cx) {
Poll::Ready(Some(Ok(data))) => data,
_ => {
panic!(
"boxed type indicated it allows taking complete body but failed to \
return Bytes when polled",
);
}
}
}
}
#[cfg(test)]

View File

@@ -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,22 +67,6 @@ where
.map_err(|err| Error::new_body().with_cause(err)),
}
}
#[inline]
fn is_complete_body(&self) -> bool {
match self {
EitherBody::Left { body } => body.is_complete_body(),
EitherBody::Right { body } => body.is_complete_body(),
}
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
match self {
EitherBody::Left { body } => body.take_complete_body(),
EitherBody::Right { body } => body.take_complete_body(),
}
}
}
#[cfg(test)]

View File

@@ -19,64 +19,16 @@ use super::BodySize;
pub trait MessageBody {
// 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>>;
type Error: Into<Box<dyn StdError>> + 'static;
/// Body size hint.
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>>>;
/// Returns true if entire body bytes chunk is obtainable in one call to `poll_next`.
///
/// This method's implementation should agree with [`take_complete_body`] and should always be
/// checked before taking the body.
///
/// The default implementation returns `false.
///
/// [`take_complete_body`]: MessageBody::take_complete_body
fn is_complete_body(&self) -> bool {
false
}
/// Returns the complete chunk of body bytes.
///
/// Implementors of this method should note the following:
/// - It is acceptable to skip the omit checks of [`is_complete_body`]. The responsibility of
/// performing this check is delegated to the caller.
/// - If the result of [`is_complete_body`] is conditional, that condition should be given
/// equivalent attention here.
/// - A second call call to [`take_complete_body`] should return an empty `Bytes` or panic.
/// - A call to [`poll_next`] after calling [`take_complete_body`] should return `None` unless
/// the chunk is guaranteed to be empty.
///
/// The default implementation panics unconditionally, indicating a control flow bug in the
/// calling code.
///
/// # Panics
/// With a correct implementation, panics if called without first checking [`is_complete_body`].
///
/// [`is_complete_body`]: MessageBody::is_complete_body
/// [`take_complete_body`]: MessageBody::take_complete_body
/// [`poll_next`]: MessageBody::poll_next
fn take_complete_body(&mut self) -> Bytes {
assert!(
self.is_complete_body(),
"type ({}) allows taking complete body but did not provide an implementation \
of `take_complete_body`",
std::any::type_name::<Self>()
);
unimplemented!(
"type ({}) does not allow taking complete body; caller should make sure to \
check `is_complete_body` first",
std::any::type_name::<Self>()
);
}
}
mod foreign_impls {
@@ -85,24 +37,18 @@ 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<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match *self {}
}
fn is_complete_body(&self) -> bool {
true
}
fn take_complete_body(&mut self) -> Bytes {
match *self {}
}
}
impl MessageBody for () {
@@ -120,16 +66,6 @@ mod foreign_impls {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(None)
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::new()
}
}
impl<B> MessageBody for Box<B>
@@ -150,16 +86,6 @@ mod foreign_impls {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Pin::new(self.get_mut().as_mut()).poll_next(cx)
}
#[inline]
fn is_complete_body(&self) -> bool {
self.as_ref().is_complete_body()
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
self.as_mut().take_complete_body()
}
}
impl<B> MessageBody for Pin<Box<B>>
@@ -180,164 +106,92 @@ mod foreign_impls {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.as_mut().poll_next(cx)
}
#[inline]
fn is_complete_body(&self) -> bool {
self.as_ref().is_complete_body()
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
debug_assert!(
self.is_complete_body(),
"inner type \"{}\" does not allow taking complete body; caller should make sure to \
call `is_complete_body` first",
std::any::type_name::<B>(),
);
// we do not have DerefMut access to call take_complete_body directly but since
// is_complete_body is true we should expect the entire bytes chunk in one poll_next
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
match self.as_mut().poll_next(&mut cx) {
Poll::Ready(Some(Ok(data))) => data,
_ => {
panic!(
"inner type \"{}\" indicated it allows taking complete body but failed to \
return Bytes when polled",
std::any::type_name::<B>()
);
}
}
}
}
impl MessageBody for &'static [u8] {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
fn poll_next(
mut self: Pin<&mut Self>,
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(self.take_complete_body())))
let bytes = mem::take(self.get_mut());
let bytes = Bytes::from_static(bytes);
Poll::Ready(Some(Ok(bytes)))
}
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from_static(mem::take(self))
}
}
impl MessageBody for Bytes {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
fn poll_next(
mut self: Pin<&mut Self>,
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(self.take_complete_body())))
let bytes = mem::take(self.get_mut());
Poll::Ready(Some(Ok(bytes)))
}
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
mem::take(self)
}
}
impl MessageBody for BytesMut {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
fn poll_next(
mut self: Pin<&mut Self>,
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(self.take_complete_body())))
let bytes = mem::take(self.get_mut()).freeze();
Poll::Ready(Some(Ok(bytes)))
}
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
mem::take(self).freeze()
}
}
impl MessageBody for Vec<u8> {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
fn poll_next(
mut self: Pin<&mut Self>,
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
if self.is_empty() {
Poll::Ready(None)
} else {
Poll::Ready(Some(Ok(self.take_complete_body())))
let bytes = mem::take(self.get_mut());
Poll::Ready(Some(Ok(Bytes::from(bytes))))
}
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from(mem::take(self))
}
}
impl MessageBody for &'static str {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
@@ -354,22 +208,11 @@ mod foreign_impls {
Poll::Ready(Some(Ok(bytes)))
}
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from_static(mem::take(self).as_bytes())
}
}
impl MessageBody for String {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
@@ -385,22 +228,11 @@ mod foreign_impls {
Poll::Ready(Some(Ok(Bytes::from(string))))
}
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::from(mem::take(self))
}
}
impl MessageBody for bytestring::ByteString {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
@@ -412,16 +244,6 @@ mod foreign_impls {
let string = mem::take(self.get_mut());
Poll::Ready(Some(Ok(string.into_bytes())))
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
mem::take(self).into_bytes()
}
}
}
@@ -450,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()
}
@@ -485,6 +306,8 @@ mod tests {
use super::*;
// static_assertions::assert_obj_safe!(MessageBody<()>);
macro_rules! assert_poll_next {
($pin:expr, $exp:expr) => {
assert_eq!(
@@ -585,51 +408,6 @@ mod tests {
assert_poll_next!(pl, Bytes::from("test"));
}
#[test]
fn take_string() {
let mut data = "test".repeat(2);
let data_bytes = Bytes::from(data.clone());
assert!(data.is_complete_body());
assert_eq!(data.take_complete_body(), data_bytes);
let mut big_data = "test".repeat(64 * 1024);
let data_bytes = Bytes::from(big_data.clone());
assert!(big_data.is_complete_body());
assert_eq!(big_data.take_complete_body(), data_bytes);
}
#[test]
fn take_boxed_equivalence() {
let mut data = Bytes::from_static(b"test");
assert!(data.is_complete_body());
assert_eq!(data.take_complete_body(), b"test".as_ref());
let mut data = Box::new(Bytes::from_static(b"test"));
assert!(data.is_complete_body());
assert_eq!(data.take_complete_body(), b"test".as_ref());
let mut data = Box::pin(Bytes::from_static(b"test"));
assert!(data.is_complete_body());
assert_eq!(data.take_complete_body(), b"test".as_ref());
}
#[test]
fn take_policy() {
let mut data = Bytes::from_static(b"test");
// first call returns chunk
assert_eq!(data.take_complete_body(), b"test".as_ref());
// second call returns empty
assert_eq!(data.take_complete_body(), b"".as_ref());
let waker = futures_util::task::noop_waker();
let mut cx = Context::from_waker(&waker);
let mut data = Bytes::from_static(b"test");
// take returns whole chunk
assert_eq!(data.take_complete_body(), b"test".as_ref());
// subsequent poll_next returns None
assert_eq!(Pin::new(&mut data).poll_next(&mut cx), Poll::Ready(None));
}
// down-casting used to be done with a method on MessageBody trait
// test is kept to demonstrate equivalence of Any trait
#[actix_rt::test]

View File

@@ -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;

View File

@@ -40,14 +40,4 @@ impl MessageBody for None {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(Option::None)
}
#[inline]
fn is_complete_body(&self) -> bool {
true
}
#[inline]
fn take_complete_body(&mut self) -> Bytes {
Bytes::new()
}
}

View File

@@ -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.

View File

@@ -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)
}

View File

@@ -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,7 +21,7 @@ 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::{BodySize, MessageBody},
error::BlockingError,
@@ -53,32 +51,41 @@ impl<B: MessageBody> Encoder<B> {
}
}
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, mut body: B) -> Self {
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
|| head.status == StatusCode::NO_CONTENT
|| 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 = if body.is_complete_body() {
let body = body.take_complete_body();
EncoderBody::Full { body }
} else {
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,
@@ -99,7 +106,7 @@ pin_project! {
#[project = EncoderBodyProj]
enum EncoderBody<B> {
None,
Full { body: Bytes },
Bytes { body: Bytes },
Stream { #[pin] body: B },
}
}
@@ -113,7 +120,7 @@ where
fn size(&self) -> BodySize {
match self {
EncoderBody::None => BodySize::None,
EncoderBody::Full { body } => body.size(),
EncoderBody::Bytes { body } => body.size(),
EncoderBody::Stream { body } => body.size(),
}
}
@@ -124,7 +131,7 @@ where
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EncoderBodyProj::None => Poll::Ready(None),
EncoderBodyProj::Full { body } => {
EncoderBodyProj::Bytes { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Stream { body } => body
@@ -132,24 +139,6 @@ where
.map_err(|err| EncoderError::Body(err.into())),
}
}
fn is_complete_body(&self) -> bool {
match self {
EncoderBody::None => true,
EncoderBody::Full { .. } => true,
EncoderBody::Stream { .. } => false,
}
}
fn take_complete_body(&mut self) -> Bytes {
match self {
EncoderBody::None => Bytes::new(),
EncoderBody::Full { body } => body.take_complete_body(),
EncoderBody::Stream { .. } => {
panic!("EncoderBody::Stream variant cannot be taken")
}
}
}
}
impl<B> MessageBody for Encoder<B>
@@ -159,10 +148,10 @@ where
type Error = EncoderError;
fn size(&self) -> BodySize {
if self.encoder.is_some() {
BodySize::Stream
} else {
if self.encoder.is_none() {
self.body.size()
} else {
BodySize::Stream
}
}
@@ -233,22 +222,6 @@ where
}
}
}
fn is_complete_body(&self) -> bool {
if self.encoder.is_some() {
false
} else {
self.body.is_complete_body()
}
}
fn take_complete_body(&mut self) -> Bytes {
if self.encoder.is_some() {
panic!("compressed body stream cannot be taken")
} else {
self.body.take_complete_body()
}
}
}
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)
}
}

View File

@@ -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);

View File

@@ -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>,
}

View File

@@ -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)

View File

@@ -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),
}
}
}

View File

@@ -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)
}

View File

@@ -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()?))
}
}

View File

@@ -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]

View File

@@ -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

View File

@@ -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;

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -32,7 +32,6 @@ pub mod body;
mod builder;
mod config;
#[cfg(feature = "__compress")]
pub mod encoding;
mod extensions;
pub mod header;

View File

@@ -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),

View File

@@ -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,
};
@@ -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,

View File

@@ -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() {

View File

@@ -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);
}

View File

@@ -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 {

View File

@@ -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]

View File

@@ -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.14", 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.15"
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"

View File

@@ -3,11 +3,11 @@
> Multipart form support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-multipart?label=latest)](https://crates.io/crates/actix-multipart)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.10)](https://docs.rs/actix-multipart/0.4.0-beta.10)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.9)](https://docs.rs/actix-multipart/0.4.0-beta.9)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-multipart.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.10/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.10)
[![dependency status](https://deps.rs/crate/actix-multipart/0.4.0-beta.9/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.9)
[![Download](https://img.shields.io/crates/d/actix-multipart.svg)](https://crates.io/crates/actix-multipart)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -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()
}
}
}

View File

@@ -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);

View File

@@ -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()
}
}

View File

@@ -3,10 +3,6 @@
## Unreleased - 2021-xx-xx
## 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]

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-test"
version = "0.1.0-beta.8"
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.15"
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.14", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.13", 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 = [] }

View File

@@ -163,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)
@@ -179,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)
@@ -195,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)
@@ -214,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)
@@ -230,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)
@@ -246,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)
@@ -265,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)
@@ -281,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)
@@ -297,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)

View File

@@ -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.

View File

@@ -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.15"
actix-web = { version = "4.0.0-beta.14", 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.8"
awc = { version = "3.0.0-beta.13", 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 }

View File

@@ -3,11 +3,11 @@
> Actix actors support for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web-actors/4.0.0-beta.8)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8)
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -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]

View File

@@ -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"
@@ -21,11 +21,11 @@ proc-macro2 = "1"
actix-router = "0.5.0-beta.2"
[dev-dependencies]
actix-macros = "0.2.3"
actix-rt = "2.2"
actix-test = "0.1.0-beta.8"
actix-macros = "0.2.3"
actix-test = "0.1.0-beta.7"
actix-utils = "3.0.0"
actix-web = "4.0.0-beta.14"
actix-web = "4.0.0-beta.11"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1"

View File

@@ -3,11 +3,11 @@
> Routing and runtime macros for Actix Web.
[![crates.io](https://img.shields.io/crates/v/actix-web-codegen?label=latest)](https://crates.io/crates/actix-web-codegen)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.6)](https://docs.rs/actix-web-codegen/0.5.0-beta.6)
[![Documentation](https://docs.rs/actix-web-codegen/badge.svg?version=0.5.0-beta.5)](https://docs.rs/actix-web-codegen/0.5.0-beta.5)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![License](https://img.shields.io/crates/l/actix-web-codegen.svg)
<br />
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6)
[![dependency status](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5)
[![Download](https://img.shields.io/crates/d/actix-web-codegen.svg)](https://crates.io/crates/actix-web-codegen)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)

View File

@@ -1,13 +1,6 @@
# Changes
## Unreleased - 2021-xx-xx
* 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
@@ -63,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

View File

@@ -1,6 +1,6 @@
[package]
name = "awc"
version = "3.0.0-beta.13"
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.15"
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"
@@ -72,7 +72,7 @@ cfg-if = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
h2 = "0.3.9"
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.15", 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.8", 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.14", 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"

View File

@@ -3,9 +3,9 @@
> Async HTTP and WebSocket client library.
[![crates.io](https://img.shields.io/crates/v/awc?label=latest)](https://crates.io/crates/awc)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.13)](https://docs.rs/awc/3.0.0-beta.13)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.12)](https://docs.rs/awc/3.0.0-beta.12)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.13/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.13)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.12/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.12)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources

View File

@@ -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()

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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)
}
}

View File

@@ -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);

View File

@@ -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"));

View File

@@ -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) => {

View File

@@ -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;
}

View File

@@ -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");

View File

@@ -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)),
)

View File

@@ -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)),
)

View File

@@ -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

146
src/any_body.rs Normal file
View 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 ()
}

View File

@@ -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();
}
}

View File

@@ -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

View File

@@ -102,41 +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),
}
}
}

View File

@@ -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()
}
}

View File

@@ -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) => {

View File

@@ -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.

View File

@@ -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!("")
}
}

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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> {

View File

@@ -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]

View File

@@ -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> {

View File

@@ -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>;

View File

@@ -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),
})))
}

View File

@@ -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(),
}))
}
}
@@ -199,22 +197,17 @@ mod tests {
};
#[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")))
.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,7 +226,7 @@ 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()
@@ -248,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"));
}
}

View File

@@ -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,

View File

@@ -33,7 +33,7 @@ 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))
}))
@@ -46,7 +46,7 @@ mod tests {
.wrap(ErrorHandlers::new().handler(StatusCode::FORBIDDEN, |res| {
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()));

View File

@@ -349,7 +349,7 @@ impl Drop for HttpRequest {
fn drop(&mut self) {
// if possible, contribute to current worker's HttpRequest allocation pool
// This relies on no weak references to inner existing anywhere within the codebase.
// This relies on no Weak<HttpRequestInner> exists anywhere. (There is none.)
if let Some(inner) = Rc::get_mut(&mut self.inner) {
if inner.app_state.pool().is_available() {
// clear additional app_data and keep the root one for reuse.
@@ -360,7 +360,7 @@ impl Drop for HttpRequest {
Rc::get_mut(&mut inner.req_data).unwrap().get_mut().clear();
// a re-borrow of pool is necessary here.
let req = Rc::clone(&self.inner);
let req = self.inner.clone();
self.app_state().pool().push(req);
}
}

View File

@@ -17,7 +17,7 @@ use crate::{dev::Payload, error::ErrorInternalServerError, Error, FromRequest, H
/// # Mutating Request Data
/// Note that since extractors must output owned data, only types that `impl Clone` can use this
/// extractor. A clone is taken of the required request data and can, therefore, not be directly
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::req_data_mut`] or
/// mutated in-place. To mutate request data, continue to use [`HttpRequest::extensions_mut`] or
/// re-insert the cloned data back into the extensions map. A `DerefMut` impl is intentionally not
/// provided to make this potential foot-gun more obvious.
///

View File

@@ -15,12 +15,13 @@ use crate::{
dev::{ensure_leading_slash, AppService, ResourceDef},
guard::Guard,
handler::Handler,
responder::Responder,
route::{Route, RouteService},
service::{
BoxedHttpService, BoxedHttpServiceFactory, HttpServiceFactory, ServiceRequest,
ServiceResponse,
},
BoxError, Error, FromRequest, HttpResponse, Responder,
BoxError, Error, FromRequest, HttpResponse,
};
/// *Resource* is an entry in resources table which corresponds to requested URL.
@@ -525,7 +526,7 @@ mod tests {
.name("test")
.wrap(
DefaultHeaders::new()
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
)
.route(web::get().to(HttpResponse::Ok)),
),

View File

@@ -1,59 +1,67 @@
use std::borrow::Cow;
use actix_http::{
body::{BoxBody, EitherBody, MessageBody},
header::TryIntoHeaderPair,
body::{BoxBody, MessageBody},
error::HttpError,
header::HeaderMap,
header::IntoHeaderPair,
StatusCode,
};
use bytes::{Bytes, BytesMut};
use crate::{BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder};
use super::CustomizeResponder;
use crate::{
any_body::AnyBody, BoxError, Error, HttpRequest, HttpResponse, HttpResponseBuilder,
};
/// Trait implemented by types that can be converted to an HTTP response.
///
/// Any types that implement this trait can be used in the return type of a handler.
// # TODO: more about implementation notes and foreign impls
pub trait Responder {
type Body: MessageBody + 'static;
/// Convert self to `HttpResponse`.
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body>;
/// Wraps responder to allow alteration of its response.
/// Override a status code for a Responder.
///
/// See [`CustomizeResponder`] docs for its capabilities.
///
/// # Examples
/// ```
/// use actix_web::{Responder, http::StatusCode, test::TestRequest};
/// use actix_web::{http::StatusCode, HttpRequest, Responder};
///
/// let responder = "Hello world!"
/// .customize()
/// .with_status(StatusCode::BAD_REQUEST)
/// .insert_header(("x-hello", "world"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.status(), StatusCode::BAD_REQUEST);
/// assert_eq!(response.headers().get("x-hello").unwrap(), "world");
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// ```
#[inline]
fn customize(self) -> CustomizeResponder<Self>
fn with_status(self, status: StatusCode) -> CustomResponder<Self>
where
Self: Sized,
{
CustomizeResponder::new(self)
CustomResponder::new(self).with_status(status)
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Prefer `.customize().insert_header(header)`.")]
fn with_header(self, header: impl TryIntoHeaderPair) -> CustomizeResponder<Self>
/// Insert header to the final response.
///
/// Overrides other headers with the same name.
///
/// ```
/// use actix_web::{web, HttpRequest, Responder};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(MyObj { name: "Name".to_owned() })
/// .with_header(("x-version", "1.2.3"))
/// }
/// ```
fn with_header<H>(self, header: H) -> CustomResponder<Self>
where
Self: Sized,
H: IntoHeaderPair,
{
self.customize().insert_header(header)
CustomResponder::new(self).with_header(header)
}
}
@@ -66,7 +74,7 @@ impl Responder for HttpResponse {
}
}
impl Responder for actix_http::Response<BoxBody> {
impl Responder for actix_http::Response<AnyBody<BoxBody>> {
type Body = BoxBody;
#[inline]
@@ -89,7 +97,10 @@ impl Responder for actix_http::ResponseBuilder {
#[inline]
fn respond_to(mut self, req: &HttpRequest) -> HttpResponse<Self::Body> {
self.finish().map_into_boxed_body().respond_to(req)
self.finish()
.map_into_boxed_body()
.map_body(|_, body| AnyBody::Boxed { body })
.respond_to(req)
}
}
@@ -98,12 +109,12 @@ where
T: Responder,
<T::Body as MessageBody>::Error: Into<BoxError>,
{
type Body = EitherBody<T::Body>;
type Body = T::Body;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
match self {
Some(val) => val.respond_to(req).map_into_left_body(),
None => HttpResponse::new(StatusCode::NOT_FOUND).map_into_right_body(),
Some(val) => val.respond_to(req),
None => HttpResponse::new(StatusCode::NOT_FOUND).map_into_body(),
}
}
}
@@ -114,12 +125,12 @@ where
<T::Body as MessageBody>::Error: Into<BoxError>,
E: Into<Error>,
{
type Body = EitherBody<T::Body>;
type Body = T::Body;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
match self {
Ok(val) => val.respond_to(req).map_into_left_body(),
Err(err) => HttpResponse::from_error(err.into()).map_into_right_body(),
Ok(val) => val.respond_to(req),
Err(err) => HttpResponse::from_error(err.into()).map_into_body(),
}
}
}
@@ -140,7 +151,12 @@ macro_rules! impl_responder_by_forward_into_base_response {
type Body = $body;
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
let res: actix_http::Response<_> = self.into();
let res = actix_http::Response::with_body(
StatusCode::default(),
AnyBody::Full {
body: Bytes::from(self),
},
);
res.into()
}
}
@@ -165,7 +181,12 @@ macro_rules! impl_into_string_responder {
fn respond_to(self, _: &HttpRequest) -> HttpResponse<Self::Body> {
let string: String = self.into();
let res: actix_http::Response<_> = string.into();
let res = actix_http::Response::with_body(
StatusCode::default(),
AnyBody::Full {
body: Bytes::from(string),
},
);
res.into()
}
}
@@ -175,6 +196,98 @@ macro_rules! impl_into_string_responder {
impl_into_string_responder!(&'_ String);
impl_into_string_responder!(Cow<'_, str>);
/// Allows overriding status code and headers for a responder.
pub struct CustomResponder<T> {
responder: T,
status: Option<StatusCode>,
headers: Result<HeaderMap, HttpError>,
}
impl<T: Responder> CustomResponder<T> {
fn new(responder: T) -> Self {
CustomResponder {
responder,
status: None,
headers: Ok(HeaderMap::new()),
}
}
/// Override a status code for the Responder's response.
///
/// ```
/// use actix_web::{HttpRequest, Responder, http::StatusCode};
///
/// fn index(req: HttpRequest) -> impl Responder {
/// "Welcome!".with_status(StatusCode::OK)
/// }
/// ```
pub fn with_status(mut self, status: StatusCode) -> Self {
self.status = Some(status);
self
}
/// Insert header to the final response.
///
/// Overrides other headers with the same name.
///
/// ```
/// use actix_web::{web, HttpRequest, Responder};
/// use serde::Serialize;
///
/// #[derive(Serialize)]
/// struct MyObj {
/// name: String,
/// }
///
/// fn index(req: HttpRequest) -> impl Responder {
/// web::Json(MyObj { name: "Name".to_string() })
/// .with_header(("x-version", "1.2.3"))
/// .with_header(("x-version", "1.2.3"))
/// }
/// ```
pub fn with_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
if let Ok(ref mut headers) = self.headers {
match header.try_into_header_pair() {
Ok((key, value)) => headers.append(key, value),
Err(e) => self.headers = Err(e.into()),
};
}
self
}
}
impl<T> Responder for CustomResponder<T>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<BoxError>,
{
type Body = T::Body;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
let headers = match self.headers {
Ok(headers) => headers,
Err(err) => return HttpResponse::from_error(err).map_into_body(),
};
let mut res = self.responder.respond_to(req);
if let Some(status) = self.status {
*res.status_mut() = status;
}
for (k, v) in headers {
// TODO: before v4, decide if this should be append instead
res.headers_mut().insert(k, v);
}
res
}
}
#[cfg(test)]
pub(crate) mod tests {
use actix_service::Service;
@@ -342,4 +455,59 @@ pub(crate) mod tests {
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
}
#[actix_rt::test]
async fn test_custom_responder() {
let req = TestRequest::default().to_http_request();
let res = "test"
.to_string()
.with_status(StatusCode::BAD_REQUEST)
.respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let res = "test"
.to_string()
.with_header(("content-type", "json"))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
#[actix_rt::test]
async fn test_tuple_responder_with_status_code() {
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK)
.with_header((CONTENT_TYPE, mime::APPLICATION_JSON))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
}

View File

@@ -9,7 +9,7 @@ use std::{
use actix_http::{
body::{BodyStream, BoxBody, MessageBody},
error::HttpError,
header::{self, HeaderName, TryIntoHeaderPair, TryIntoHeaderValue},
header::{self, HeaderName, IntoHeaderPair, IntoHeaderValue},
ConnectionType, Extensions, Response, ResponseHead, StatusCode,
};
use bytes::Bytes;
@@ -22,6 +22,7 @@ use actix_http::header::HeaderValue;
use cookie::{Cookie, CookieJar};
use crate::{
any_body::AnyBody,
error::{Error, JsonPayloadError},
BoxError, HttpResponse,
};
@@ -67,9 +68,12 @@ impl HttpResponseBuilder {
/// .insert_header(("X-TEST", "value"))
/// .finish();
/// ```
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);
}
@@ -91,9 +95,12 @@ impl HttpResponseBuilder {
/// .append_header(("X-TEST", "value2"))
/// .finish();
/// ```
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()),
};
@@ -112,7 +119,7 @@ impl HttpResponseBuilder {
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: TryIntoHeaderValue,
V: IntoHeaderValue,
{
if self.err.is_some() {
return self;
@@ -137,7 +144,7 @@ impl HttpResponseBuilder {
where
K: TryInto<HeaderName>,
K::Error: Into<HttpError>,
V: TryIntoHeaderValue,
V: IntoHeaderValue,
{
if self.err.is_some() {
return self;
@@ -174,7 +181,7 @@ impl HttpResponseBuilder {
#[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);
@@ -212,7 +219,7 @@ impl HttpResponseBuilder {
#[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() {
@@ -327,7 +334,7 @@ impl HttpResponseBuilder {
.set_body(body);
#[allow(unused_mut)] // mut is only unused when cookies are disabled
let mut res = HttpResponse::from(res);
let mut res = HttpResponse::from(res.map_body(|_, body| AnyBody::Stream { body }));
#[cfg(feature = "cookies")]
if let Some(ref jar) = self.cookies {
@@ -410,7 +417,7 @@ impl From<HttpResponseBuilder> for HttpResponse {
}
}
impl From<HttpResponseBuilder> for Response<BoxBody> {
impl From<HttpResponseBuilder> for Response<AnyBody<BoxBody>> {
fn from(mut builder: HttpResponseBuilder) -> Self {
builder.finish().into()
}

View File

@@ -1,245 +0,0 @@
use actix_http::{
body::{EitherBody, MessageBody},
error::HttpError,
header::HeaderMap,
header::TryIntoHeaderPair,
StatusCode,
};
use crate::{BoxError, HttpRequest, HttpResponse, Responder};
/// Allows overriding status code and headers for a [`Responder`].
///
/// Created by the [`Responder::customize`] method.
pub struct CustomizeResponder<R> {
inner: CustomizeResponderInner<R>,
error: Option<HttpError>,
}
struct CustomizeResponderInner<R> {
responder: R,
status: Option<StatusCode>,
override_headers: HeaderMap,
append_headers: HeaderMap,
}
impl<R: Responder> CustomizeResponder<R> {
pub(crate) fn new(responder: R) -> Self {
CustomizeResponder {
inner: CustomizeResponderInner {
responder,
status: None,
override_headers: HeaderMap::new(),
append_headers: HeaderMap::new(),
},
error: None,
}
}
/// Override a status code for the Responder's response.
///
/// # Examples
/// ```
/// use actix_web::{Responder, http::StatusCode, test::TestRequest};
///
/// let responder = "Welcome!".customize().with_status(StatusCode::ACCEPTED);
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.status(), StatusCode::ACCEPTED);
/// ```
pub fn with_status(mut self, status: StatusCode) -> Self {
if let Some(inner) = self.inner() {
inner.status = Some(status);
}
self
}
/// Insert (override) header in the final response.
///
/// Overrides other headers with the same name.
/// See [`HeaderMap::insert`](crate::http::header::HeaderMap::insert).
///
/// Headers added with this method will be inserted before those added
/// with [`append_header`](Self::append_header). As such, header(s) can be overridden with more
/// than one new header by first calling `insert_header` followed by `append_header`.
///
/// # Examples
/// ```
/// use actix_web::{Responder, test::TestRequest};
///
/// let responder = "Hello world!"
/// .customize()
/// .insert_header(("x-version", "1.2.3"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3");
/// ```
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Some(inner) = self.inner() {
match header.try_into_pair() {
Ok((key, value)) => {
inner.override_headers.insert(key, value);
}
Err(err) => self.error = Some(err.into()),
};
}
self
}
/// Append header to the final response.
///
/// Unlike [`insert_header`](Self::insert_header), this will not override existing headers.
/// See [`HeaderMap::append`](crate::http::header::HeaderMap::append).
///
/// Headers added here are appended _after_ additions/overrides from `insert_header`.
///
/// # Examples
/// ```
/// use actix_web::{Responder, test::TestRequest};
///
/// let responder = "Hello world!"
/// .customize()
/// .append_header(("x-version", "1.2.3"));
///
/// let request = TestRequest::default().to_http_request();
/// let response = responder.respond_to(&request);
/// assert_eq!(response.headers().get("x-version").unwrap(), "1.2.3");
/// ```
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Some(inner) = self.inner() {
match header.try_into_pair() {
Ok((key, value)) => {
inner.append_headers.append(key, value);
}
Err(err) => self.error = Some(err.into()),
};
}
self
}
#[doc(hidden)]
#[deprecated(since = "4.0.0", note = "Renamed to `insert_header`.")]
pub fn with_header(self, header: impl TryIntoHeaderPair) -> Self
where
Self: Sized,
{
self.insert_header(header)
}
fn inner(&mut self) -> Option<&mut CustomizeResponderInner<R>> {
if self.error.is_some() {
None
} else {
Some(&mut self.inner)
}
}
}
impl<T> Responder for CustomizeResponder<T>
where
T: Responder,
<T::Body as MessageBody>::Error: Into<BoxError>,
{
type Body = EitherBody<T::Body>;
fn respond_to(self, req: &HttpRequest) -> HttpResponse<Self::Body> {
if let Some(err) = self.error {
return HttpResponse::from_error(err).map_into_right_body();
}
let mut res = self.inner.responder.respond_to(req);
if let Some(status) = self.inner.status {
*res.status_mut() = status;
}
for (k, v) in self.inner.override_headers {
res.headers_mut().insert(k, v);
}
for (k, v) in self.inner.append_headers {
res.headers_mut().append(k, v);
}
res.map_into_left_body()
}
}
#[cfg(test)]
mod tests {
use bytes::Bytes;
use actix_http::body::to_bytes;
use super::*;
use crate::{
http::{
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
test::TestRequest,
};
#[actix_rt::test]
async fn customize_responder() {
let req = TestRequest::default().to_http_request();
let res = "test"
.to_string()
.customize()
.with_status(StatusCode::BAD_REQUEST)
.respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let res = "test"
.to_string()
.customize()
.insert_header(("content-type", "json"))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
#[actix_rt::test]
async fn tuple_responder_with_status_code() {
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::BAD_REQUEST).respond_to(&req);
assert_eq!(res.status(), StatusCode::BAD_REQUEST);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
let req = TestRequest::default().to_http_request();
let res = ("test".to_string(), StatusCode::OK)
.customize()
.insert_header((CONTENT_TYPE, mime::APPLICATION_JSON))
.respond_to(&req);
assert_eq!(res.status(), StatusCode::OK);
assert_eq!(
res.headers().get(CONTENT_TYPE).unwrap(),
HeaderValue::from_static("application/json")
);
assert_eq!(
to_bytes(res.into_body()).await.unwrap(),
Bytes::from_static(b"test"),
);
}
}

View File

@@ -1,13 +1,9 @@
mod builder;
mod customize_responder;
mod http_codes;
mod responder;
#[allow(clippy::module_inception)]
mod response;
pub use self::builder::HttpResponseBuilder;
pub use self::customize_responder::CustomizeResponder;
pub use self::responder::Responder;
pub use self::response::HttpResponse;
#[cfg(feature = "cookies")]

View File

@@ -8,7 +8,7 @@ use std::{
};
use actix_http::{
body::{BoxBody, EitherBody, MessageBody},
body::{BoxBody, MessageBody, None as NoneBody},
header::HeaderMap,
Extensions, Response, ResponseHead, StatusCode,
};
@@ -22,11 +22,11 @@ use {
cookie::Cookie,
};
use crate::{error::Error, HttpResponseBuilder};
use crate::{any_body::AnyBody, error::Error, HttpResponseBuilder};
/// An outgoing response.
pub struct HttpResponse<B = BoxBody> {
res: Response<B>,
res: Response<AnyBody<B>>,
pub(crate) error: Option<Error>,
}
@@ -35,7 +35,7 @@ impl HttpResponse<BoxBody> {
#[inline]
pub fn new(status: StatusCode) -> Self {
Self {
res: Response::new(status),
res: Response::with_body(status, AnyBody::default()),
error: None,
}
}
@@ -54,6 +54,13 @@ impl HttpResponse<BoxBody> {
response.error = Some(error);
response
}
pub fn map_into_body<B>(self) -> HttpResponse<B>
where
B: MessageBody + 'static,
{
self.map_body(|_, body| body.into_body())
}
}
impl<B> HttpResponse<B> {
@@ -61,7 +68,7 @@ impl<B> HttpResponse<B> {
#[inline]
pub fn with_body(status: StatusCode, body: B) -> Self {
Self {
res: Response::with_body(status, body),
res: Response::with_body(status, AnyBody::Stream { body }),
error: None,
}
}
@@ -182,12 +189,12 @@ impl<B> HttpResponse<B> {
/// Get body of this response
#[inline]
pub fn body(&self) -> &B {
pub fn body(&self) -> &AnyBody<B> {
self.res.body()
}
/// Set a body
pub fn set_body<B2>(self, body: B2) -> HttpResponse<B2> {
pub fn set_body<B2>(self, body: AnyBody<B2>) -> HttpResponse<B2> {
HttpResponse {
res: self.res.set_body(body),
error: None,
@@ -196,12 +203,12 @@ impl<B> HttpResponse<B> {
}
/// Split response and body
pub fn into_parts(self) -> (HttpResponse<()>, B) {
pub fn into_parts(self) -> (HttpResponse<()>, AnyBody<B>) {
let (head, body) = self.res.into_parts();
(
HttpResponse {
res: head,
res: head.map_body(|_, _b| AnyBody::default()),
error: None,
},
body,
@@ -211,7 +218,7 @@ impl<B> HttpResponse<B> {
/// Drop request's body
pub fn drop_body(self) -> HttpResponse<()> {
HttpResponse {
res: self.res.drop_body(),
res: self.res.drop_body().map_body(|_, _b| AnyBody::default()),
error: None,
}
}
@@ -219,7 +226,7 @@ impl<B> HttpResponse<B> {
/// Set a body and return previous body value
pub fn map_body<F, B2>(self, f: F) -> HttpResponse<B2>
where
F: FnOnce(&mut ResponseHead, B) -> B2,
F: FnOnce(&mut ResponseHead, AnyBody<B>) -> AnyBody<B2>,
{
HttpResponse {
res: self.res.map_body(f),
@@ -229,27 +236,28 @@ impl<B> HttpResponse<B> {
// TODO: docs for the body map methods below
#[inline]
pub fn map_into_left_body<R>(self) -> HttpResponse<EitherBody<B, R>> {
self.map_body(|_, body| EitherBody::left(body))
}
#[inline]
pub fn map_into_right_body<L>(self) -> HttpResponse<EitherBody<L, B>> {
self.map_body(|_, body| EitherBody::right(body))
}
#[inline]
pub fn map_into_boxed_body(self) -> HttpResponse<BoxBody>
where
B: MessageBody + 'static,
{
// TODO: avoid double boxing with down-casting, if it improves perf
self.map_body(|_, body| BoxBody::new(body))
self.map_body(|_, body| AnyBody::Boxed {
body: match body {
AnyBody::None => BoxBody::new(NoneBody::new()),
AnyBody::Full { body } => BoxBody::new(body),
AnyBody::Stream { body } => BoxBody::new(body),
AnyBody::Boxed { body } => body,
},
})
}
/// Extract response body
pub fn into_body(self) -> B {
pub fn take_body(&mut self) -> AnyBody<B> {
self.res.take_body()
}
/// Extract response body
pub fn into_body(self) -> AnyBody<B> {
self.res.into_body()
}
}
@@ -266,8 +274,8 @@ where
}
}
impl<B> From<Response<B>> for HttpResponse<B> {
fn from(res: Response<B>) -> Self {
impl<B> From<Response<AnyBody<B>>> for HttpResponse<B> {
fn from(res: Response<AnyBody<B>>) -> Self {
HttpResponse { res, error: None }
}
}
@@ -278,7 +286,7 @@ impl From<Error> for HttpResponse {
}
}
impl<B> From<HttpResponse<B>> for Response<B> {
impl<B> From<HttpResponse<B>> for Response<AnyBody<B>> {
fn from(res: HttpResponse<B>) -> Self {
// this impl will always be called as part of dispatcher
@@ -291,14 +299,14 @@ impl<B> From<HttpResponse<B>> for Response<B> {
}
}
// Future is only implemented for BoxBody payload type because it's the most useful for making
// Future is only implemented for default payload type because it's the most useful for making
// simple handlers without async blocks. Making it generic over all MessageBody types requires a
// future impl on Response which would cause it's body field to be, undesirably, Option<B>.
//
// This impl is not particularly efficient due to the Response construction and should probably
// not be invoked if performance is important. Prefer an async fn/block in such cases.
impl Future for HttpResponse<BoxBody> {
type Output = Result<Response<BoxBody>, Error>;
impl Future for HttpResponse {
type Output = Result<Response<AnyBody>, Error>;
fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll<Self::Output> {
if let Some(err) = self.error.take() {
@@ -307,7 +315,7 @@ impl Future for HttpResponse<BoxBody> {
Poll::Ready(Ok(mem::replace(
&mut self.res,
Response::new(StatusCode::default()),
Response::with_body(StatusCode::default(), AnyBody::None),
)))
}
}

View File

@@ -935,7 +935,7 @@ mod tests {
web::scope("app")
.wrap(
DefaultHeaders::new()
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
)
.service(web::resource("/test").route(web::get().to(HttpResponse::Ok))),
),

View File

@@ -17,7 +17,7 @@ use actix_tls::accept::openssl::reexports::{AlpnError, SslAcceptor, SslAcceptorB
#[cfg(feature = "rustls")]
use actix_tls::accept::rustls::reexports::ServerConfig as RustlsServerConfig;
use crate::{config::AppConfig, Error};
use crate::{any_body::AnyBody, config::AppConfig, Error};
struct Socket {
scheme: &'static str,
@@ -55,7 +55,7 @@ where
S: ServiceFactory<Request, Config = AppConfig>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
S::Response: Into<Response<AnyBody<B>>>,
B: MessageBody,
{
pub(super) factory: F,
@@ -75,7 +75,7 @@ where
S: ServiceFactory<Request, Config = AppConfig> + 'static,
S::Error: Into<Error> + 'static,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>> + 'static,
S::Response: Into<Response<AnyBody<B>>> + 'static,
<S::Service as Service<Request>>::Future: 'static,
S::Service: 'static,
@@ -300,9 +300,10 @@ where
})
};
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()
});
svc.finish(map_config(fac, move |_| {
AppConfig::new(false, host.clone(), addr)
@@ -360,9 +361,10 @@ where
svc
};
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()
});
svc.finish(map_config(fac, move |_| {
AppConfig::new(true, host.clone(), addr)
@@ -544,9 +546,10 @@ where
.on_connect_ext(move |io: &_, ext: _| (&*handler)(io as &dyn Any, ext));
}
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()
});
svc.finish(map_config(fac, move |_| config.clone()))
})
@@ -585,9 +588,10 @@ where
socket_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()
});
fn_service(|io: UnixStream| async { Ok((io, Protocol::Http1, None)) }).and_then(
HttpService::build()
@@ -610,7 +614,7 @@ where
S: ServiceFactory<Request, Config = AppConfig>,
S::Error: Into<Error>,
S::InitError: fmt::Debug,
S::Response: Into<Response<B>>,
S::Response: Into<Response<AnyBody<B>>>,
S::Service: 'static,
B: MessageBody,
{

View File

@@ -5,7 +5,7 @@ use std::{
};
use actix_http::{
body::{BoxBody, EitherBody, MessageBody},
body::{self, BoxBody, MessageBody},
header::HeaderMap,
Extensions, HttpMessage, Method, Payload, PayloadStream, RequestHead, Response,
ResponseHead, StatusCode, Uri, Version,
@@ -19,6 +19,7 @@ use actix_service::{
use cookie::{Cookie, ParseError as CookieParseError};
use crate::{
any_body::AnyBody,
config::{AppConfig, AppService},
dev::ensure_leading_slash,
guard::Guard,
@@ -112,7 +113,7 @@ impl ServiceRequest {
/// Create service response
#[inline]
pub fn into_response<B, R: Into<Response<B>>>(self, res: R) -> ServiceResponse<B> {
pub fn into_response<B, R: Into<Response<AnyBody<B>>>>(self, res: R) -> ServiceResponse<B> {
let res = HttpResponse::from(res.into());
ServiceResponse::new(self.req, res)
}
@@ -410,15 +411,14 @@ impl<B> ServiceResponse<B> {
self.response.headers_mut()
}
/// Destructures `ServiceResponse` into request and response components.
#[inline]
pub fn into_parts(self) -> (HttpRequest, HttpResponse<B>) {
(self.request, self.response)
pub fn take_body(&mut self) -> AnyBody<B> {
self.response.take_body()
}
/// Extract response body
#[inline]
pub fn into_body(self) -> B {
pub fn into_body(self) -> AnyBody<B> {
self.response.into_body()
}
@@ -426,7 +426,7 @@ impl<B> ServiceResponse<B> {
#[inline]
pub fn map_body<F, B2>(self, f: F) -> ServiceResponse<B2>
where
F: FnOnce(&mut ResponseHead, B) -> B2,
F: FnOnce(&mut ResponseHead, AnyBody<B>) -> AnyBody<B2>,
{
let response = self.response.map_body(f);
@@ -436,22 +436,29 @@ impl<B> ServiceResponse<B> {
}
}
#[inline]
pub fn map_into_left_body<R>(self) -> ServiceResponse<EitherBody<B, R>> {
self.map_body(|_, body| EitherBody::left(body))
}
// #[inline]
// pub fn map_into_left_body<R>(self) -> ServiceResponse<EitherBody<B, R>> {
// self.map_body(|_, body| EitherBody::left(body))
// }
#[inline]
pub fn map_into_right_body<L>(self) -> ServiceResponse<EitherBody<L, B>> {
self.map_body(|_, body| EitherBody::right(body))
}
// #[inline]
// pub fn map_into_right_body<L>(self) -> ServiceResponse<EitherBody<L, B>> {
// self.map_body(|_, body| EitherBody::right(body))
// }
#[inline]
pub fn map_into_boxed_body(self) -> ServiceResponse<BoxBody>
where
B: MessageBody + 'static,
{
self.map_body(|_, body| BoxBody::new(body))
self.map_body(|_, body| AnyBody::Stream {
body: match body {
AnyBody::None => BoxBody::new(body::None::new()),
AnyBody::Full { body } => BoxBody::new(body),
AnyBody::Stream { body } => BoxBody::new(body),
AnyBody::Boxed { body } => body,
},
})
}
}
@@ -461,8 +468,8 @@ impl<B> From<ServiceResponse<B>> for HttpResponse<B> {
}
}
impl<B> From<ServiceResponse<B>> for Response<B> {
fn from(res: ServiceResponse<B>) -> Response<B> {
impl<B> From<ServiceResponse<B>> for Response<AnyBody<B>> {
fn from(res: ServiceResponse<B>) -> Response<AnyBody<B>> {
res.response.into()
}
}

View File

@@ -4,8 +4,8 @@ use std::{borrow::Cow, net::SocketAddr, rc::Rc};
pub use actix_http::test::TestBuffer;
use actix_http::{
header::TryIntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method,
Request, StatusCode, Uri, Version,
header::IntoHeaderPair, test::TestRequest as HttpTestRequest, Extensions, Method, Request,
StatusCode, Uri, Version,
};
use actix_router::{Path, ResourceDef, Url};
use actix_service::{IntoService, IntoServiceFactory, Service, ServiceFactory};
@@ -163,7 +163,7 @@ where
actix_rt::pin!(body);
while let Some(item) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
bytes.extend_from_slice(&item.map_err(Into::into).unwrap());
bytes.extend_from_slice(&item.unwrap());
}
bytes.freeze()
@@ -205,7 +205,7 @@ where
actix_rt::pin!(body);
while let Some(item) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
bytes.extend_from_slice(&item.map_err(Into::into).unwrap());
bytes.extend_from_slice(&item.unwrap());
}
bytes.freeze()
@@ -445,13 +445,19 @@ impl TestRequest {
}
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
pub fn insert_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
self.req.insert_header(header);
self
}
/// Append a header, keeping any that were set with an equivalent field name.
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
pub fn append_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
self.req.append_header(header);
self
}

Some files were not shown because too many files have changed in this diff Show More