1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-01 04:51:51 +02:00

Compare commits

...

49 Commits

Author SHA1 Message Date
Rob Ede
44e9d790fd Merge branch 'master' into robjtede/issue2502 2021-12-18 10:49:19 +00:00
Rob Ede
d2b9724010 update bump script to detect prerelease versions 2021-12-18 03:27:32 +00:00
Rob Ede
5c53db1e4d remove hidden anybody 2021-12-18 01:48:16 +00:00
Thales
84ea9e7e88 http: Replace header::map::GetAll with std::slice::Iter (#2527) 2021-12-18 00:05:12 +00:00
Rob Ede
0bd5ccc432 update changelog 2021-12-17 21:39:15 +00:00
Rob Ede
9cd8526085 prepare actix-router release 0.5.0-beta.3 2021-12-17 21:24:34 +00:00
Rob Ede
73bbe56971 prepare actix-test release 0.1.0-beta.9 2021-12-17 21:00:15 +00:00
Rob Ede
8340b63b7b prepare awc release 3.0.0-beta.14 2021-12-17 20:59:59 +00:00
Rob Ede
6c2c7b68e2 prepare actix-web release 4.0.0-beta.15 2021-12-17 20:59:01 +00:00
Rob Ede
7bf47967cc prepare actix-http release 3.0.0-beta.16 2021-12-17 20:57:51 +00:00
Rob Ede
ae47d96fc6 use body::None in encoder body 2021-12-17 20:56:54 +00:00
Rob Ede
5842a3279d update messagebody documentation 2021-12-17 19:35:08 +00:00
Rob Ede
1d6f5ba6d6 improve codegen on BoxBody poll_next 2021-12-17 19:19:21 +00:00
Rob Ede
aa31086af5 improve BoxBody Debug impl 2021-12-17 19:16:42 +00:00
Ali MJ Al-Nasrawy
57ea322ce5 simplify MessageBody::complete_body interface (#2522) 2021-12-17 19:09:08 +00:00
Rob Ede
2cf27863cb remove direct dep on pin-project in -http (#2524) 2021-12-17 14:13:54 +00:00
Rob Ede
5359fa56c2 include source for dispatch body errors 2021-12-17 01:29:41 +00:00
Rob Ede
a2467718ac passthrough StreamLog error type 2021-12-17 01:27:27 +00:00
Ali MJ Al-Nasrawy
3c0d059d92 MessageBody::boxed (#2520)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-17 00:43:40 +00:00
Rob Ede
44b7302845 minimize futures-util dep in actix-http 2021-12-16 22:26:45 +00:00
Ali MJ Al-Nasrawy
a6d5776481 various fixes to MessageBody::complete_body (#2519) 2021-12-16 22:25:10 +00:00
Rob Ede
156cc20ac8 refactor testing utils (#2518) 2021-12-15 01:44:51 +00:00
Rob Ede
dd4a372613 allow error handler middleware to return different body type (#2515) 2021-12-14 21:17:50 +00:00
Rob Ede
05255c7f7c remove either crate conversions (#2516) 2021-12-14 19:57:18 +00:00
Rob Ede
fb091b2b88 split up router pattern and resource_path modules 2021-12-14 18:59:59 +00:00
Rob Ede
11ee8ec3ab align remaining header map terminology (#2510) 2021-12-13 16:08:08 +00:00
Rob Ede
551a0d973c doc tweaks 2021-12-13 02:58:19 +00:00
Rob Ede
cea44be670 add test for returning App from function 2021-12-11 16:18:28 +00:00
Rob Ede
b41b346c00 inline trivial body methods 2021-12-11 16:05:08 +00:00
Rob Ede
1270b4faf2 Merge branch 'master' into robjtede/issue2502 2021-12-11 00:44:19 +00:00
Rob Ede
626d100852 fix old field reference 2021-12-11 00:38:23 +00:00
Rob Ede
5b0a50249b prepare actix-multipart release 0.4.0-beta.10 2021-12-11 00:35:26 +00:00
Rob Ede
60b030ff53 prepare actix-web-actors release 4.0.0-beta.8 2021-12-11 00:34:23 +00:00
Rob Ede
fc4e9ff96b prepare actix-web-codegen release 0.5.0-beta.6 2021-12-11 00:33:31 +00:00
Rob Ede
6481a5fb73 prepare actix-test release 0.1.0-beta.8 2021-12-11 00:32:26 +00:00
Rob Ede
0cd7c17682 prepare actix-http-test release 3.0.0-beta.9 2021-12-11 00:32:00 +00:00
Rob Ede
ed2f5b40b9 prepare actix-files release 0.6.0-beta.10 2021-12-11 00:31:41 +00:00
Rob Ede
cc37be9700 prepare actix-web release 4.0.0-beta.14 2021-12-11 00:30:12 +00:00
Rob Ede
e1cdabe5cb prepare awc release 3.0.0-beta.13 2021-12-11 00:28:38 +00:00
Rob Ede
d0f4c809ca prepare actix-http release 3.0.0-beta.15 2021-12-11 00:22:09 +00:00
Rob Ede
65dd5dfa7b bump script updates referenced crate versions 2021-12-11 00:21:30 +00:00
Rob Ede
818c65af57 fix clippy lints 2021-12-10 23:17:51 +00:00
Rob Ede
92b8673475 only build SslConnectorBuilder once
fixes #2502
2021-12-10 23:06:21 +00:00
Rob Ede
f62383a975 unpin h2 2021-12-10 22:13:12 +00:00
Ali MJ Al-Nasrawy
f9348d7129 add ServiceResponse::into_parts (#2499) 2021-12-09 14:57:27 +00:00
Rob Ede
774ac7fec4 provide optimisation path for single-chunk body types (#2497) 2021-12-09 13:52:35 +00:00
Rob Ede
69fa17f66f clean future h2 dispatcher 2021-12-09 11:27:29 +00:00
Rob Ede
816d68dee8 pin h2 temporarily 2021-12-09 00:46:28 +00:00
Rob Ede
7dc034f0fb Remove extensions from head (#2487) 2021-12-08 22:58:50 +00:00
123 changed files with 2883 additions and 2152 deletions

View File

@@ -1,6 +1,34 @@
# Changes
## Unreleased - 2021-xx-xx
## 4.0.0-beta.15 - 2021-12-17
### Added
* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510]
* Implement `Debug` for `DefaultHeaders`. [#2510]
### Changed
* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510]
* Response service types in `ErrorHandlers` middleware now use `ServiceResponse<EitherBody<B>>` to allow changing the body type. [#2515]
* Both variants in `ErrorHandlerResponse` now use `ServiceResponse<EitherBody<B>>`. [#2515]
* Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518]
* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518]
* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518]
* Relax body type and error bounds on test utilities. [#2518]
### Removed
* Top-level `EitherExtractError` export. [#2510]
* Conversion implementations for `either` crate. [#2516]
* `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2515]: https://github.com/actix/actix-web/pull/2515
[#2516]: https://github.com/actix/actix-web/pull/2516
[#2518]: https://github.com/actix/actix-web/pull/2518
## 4.0.0-beta.14 - 2021-12-11
### Added
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
* `AcceptEncoding` typed header. [#2482]
@@ -8,6 +36,8 @@
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
* `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]
@@ -16,21 +46,27 @@
* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430]
* Remove `B` (body) type parameter on `App`. [#2493]
* Add `B` (body) type parameter on `Scope`. [#2492]
* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487]
### Fixed
* Accept wildcard `*` items in `AcceptLanguage`. [#2480]
* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468]
* Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
### Removed
* `ConnectionInfo::get`. [#2487]
[#2430]: https://github.com/actix/actix-web/pull/2430
[#2468]: https://github.com/actix/actix-web/pull/2468
[#2480]: https://github.com/actix/actix-web/pull/2480
[#2482]: https://github.com/actix/actix-web/pull/2482
[#2484]: https://github.com/actix/actix-web/pull/2484
[#2485]: https://github.com/actix/actix-web/pull/2485
[#2487]: https://github.com/actix/actix-web/pull/2487
[#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.13"
version = "4.0.0-beta.15"
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,16 +77,15 @@ 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.14"
actix-router = "0.5.0-beta.2"
actix-web-codegen = "0.5.0-beta.5"
actix-http = "3.0.0-beta.16"
actix-router = "0.5.0-beta.3"
actix-web-codegen = "0.5.0-beta.6"
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 }
@@ -107,8 +106,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
url = "2.1"
[dev-dependencies]
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.11", features = ["openssl"] }
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.14", features = ["openssl"] }
brotli2 = "0.3.2"
criterion = { version = "0.3", features = ["html_reports"] }

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.13)](https://docs.rs/actix-web/4.0.0-beta.13)
[![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.15)](https://docs.rs/actix-web/4.0.0-beta.15)
[![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.13/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.13)
[![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.15/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.15)
<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,6 +3,10 @@
## 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.9"
version = "0.6.0-beta.10"
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-web = { version = "4.0.0-beta.11", default-features = false }
actix-http = "3.0.0-beta.14"
actix-http = "3.0.0-beta.16"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4.0.0-beta.15", default-features = false }
askama_escape = "0.10"
bitflags = "1"
bytes = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
http-range = "0.1.4"
derive_more = "0.99.5"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.1"
@@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true }
[dev-dependencies]
actix-rt = "2.2"
actix-web = "4.0.0-beta.11"
actix-test = "0.1.0-beta.7"
actix-test = "0.1.0-beta.9"
actix-web = "4.0.0-beta.15"

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.9)](https://docs.rs/actix-files/0.6.0-beta.9)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.10)](https://docs.rs/actix-files/0.6.0-beta.10)
[![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.9/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.9)
[![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)
[![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,14 +2,10 @@ 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},
@@ -27,6 +23,7 @@ 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;
@@ -71,8 +68,11 @@ 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,14 +364,18 @@ 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
@@ -472,17 +476,17 @@ impl NamedFile {
false
};
let mut resp = HttpResponse::build(self.status_code);
let mut res = HttpResponse::build(self.status_code);
if self.flags.contains(Flags::PREFER_UTF8) {
let ct = equiv_utf8_text(self.content_type.clone());
resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
res.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else {
resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
}
if self.flags.contains(Flags::CONTENT_DISPOSITION) {
resp.insert_header((
res.insert_header((
header::CONTENT_DISPOSITION,
self.content_disposition.to_string(),
));
@@ -490,18 +494,18 @@ impl NamedFile {
// default compressing
if let Some(current_encoding) = self.encoding {
resp.encoding(current_encoding);
res.encoding(current_encoding);
}
if let Some(lm) = last_modified {
resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
res.insert_header((header::LAST_MODIFIED, lm.to_string()));
}
if let Some(etag) = etag {
resp.insert_header((header::ETAG, etag.to_string()));
res.insert_header((header::ETAG, etag.to_string()));
}
resp.insert_header((header::ACCEPT_RANGES, "bytes"));
res.insert_header((header::ACCEPT_RANGES, "bytes"));
let mut length = self.md.len();
let mut offset = 0;
@@ -513,24 +517,24 @@ impl NamedFile {
length = ranges[0].length;
offset = ranges[0].start;
resp.encoding(ContentEncoding::Identity);
resp.insert_header((
res.encoding(ContentEncoding::Identity);
res.insert_header((
header::CONTENT_RANGE,
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
));
} else {
resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
};
} else {
return resp.status(StatusCode::BAD_REQUEST).finish();
return res.status(StatusCode::BAD_REQUEST).finish();
};
};
if precondition_failed {
return resp.status(StatusCode::PRECONDITION_FAILED).finish();
return res.status(StatusCode::PRECONDITION_FAILED).finish();
} else if not_modified {
return resp
return res
.status(StatusCode::NOT_MODIFIED)
.body(body::None::new())
.map_into_boxed_body();
@@ -539,10 +543,10 @@ impl NamedFile {
let reader = chunked::new_chunked_read(length, offset, self.file);
if offset != 0 || length != self.md.len() {
resp.status(StatusCode::PARTIAL_CONTENT);
res.status(StatusCode::PARTIAL_CONTENT);
}
resp.body(SizedStream::new(length, reader))
res.body(SizedStream::new(length, reader))
}
}
@@ -586,20 +590,6 @@ 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,6 +3,10 @@
## 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.8"
version = "3.0.0-beta.9"
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.11", default-features = false }
awc = { version = "3.0.0-beta.14", 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.11", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.14"
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.16"

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.8)](https://docs.rs/actix-http-test/3.0.0-beta.8)
[![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)
[![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.8/status.svg)](https://deps.rs/crate/actix-http-test/3.0.0-beta.8)
[![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)
[![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

@@ -107,7 +107,7 @@ pub async fn test_server_with_addr<F: ServiceFactory<TcpStream>>(
Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
.ssl(builder.build())
.openssl(builder.build())
};
#[cfg(not(feature = "openssl"))]

View File

@@ -1,6 +1,29 @@
# Changes
## Unreleased - 2021-xx-xx
### Removed
* `header::map::GetAll` iterator, its `Iterator::size_hint` method was wrongly implemented. Replaced with `std::slice::Iter`. [#2527]
[#2527]: https://github.com/actix/actix-web/pull/2527
## 3.0.0-beta.16 - 2021-12-17
### Added
* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522]
### Changed
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
### Removed
* `MessageBody::{is_complete_body,take_complete_body}`. [#2522]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2522]: https://github.com/actix/actix-web/pull/2522
## 3.0.0-beta.15 - 2021-12-11
### Added
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
@@ -16,6 +39,10 @@
* `impl Display` for `header::Quality`. [#2486]
* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491]
* `Request::take_conn_data()`. [#2491]
* `Request::take_req_data()`. [#2487]
* `impl Clone` for `RequestHead`. [#2487]
* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497]
* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468]
@@ -40,8 +67,11 @@
[#2468]: https://github.com/actix/actix-web/pull/2468
[#1920]: https://github.com/actix/actix-web/pull/1920
[#2486]: https://github.com/actix/actix-web/pull/2486
[#2487]: https://github.com/actix/actix-web/pull/2487
[#2488]: https://github.com/actix/actix-web/pull/2488
[#2491]: https://github.com/actix/actix-web/pull/2491
[#2497]: https://github.com/actix/actix-web/pull/2497
[#2520]: https://github.com/actix/actix-web/pull/2520
## 3.0.0-beta.14 - 2021-11-30
@@ -252,7 +282,7 @@
## 3.0.0-beta.2 - 2021-02-10
### Added
* `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
* `TryIntoHeaderPair` 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]
@@ -263,9 +293,9 @@
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed
* `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed
`mime` types. [#1894]
* Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
* Renamed `TryIntoHeaderValue::{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.14"
version = "3.0.0-beta.16"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"]
@@ -45,7 +45,7 @@ __compress = []
actix-service = "2.0.0"
actix-codec = "0.4.1"
actix-utils = "3.0.0"
actix-rt = "2.2"
actix-rt = { version = "2.2", default-features = false }
ahash = "0.7"
base64 = "0.13"
@@ -55,8 +55,8 @@ bytestring = "1"
derive_more = "0.99.5"
encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.1"
futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] }
h2 = "0.3.9"
http = "0.2.5"
httparse = "1.5.1"
httpdate = "1.0.1"
@@ -66,7 +66,6 @@ local-channel = "0.1"
log = "0.4"
mime = "0.3"
percent-encoding = "2.1"
pin-project = "1.0.0"
pin-project-lite = "0.2"
rand = "0.8"
sha-1 = "0.9"
@@ -81,13 +80,15 @@ 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.13"
actix-web = "4.0.0-beta.15"
async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
rcgen = "0.8"
regex = "1.3"
rustls-pemfile = "0.2"

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.14)](https://docs.rs/actix-http/3.0.0-beta.14)
[![Documentation](https://docs.rs/actix-http/badge.svg?version=3.0.0-beta.16)](https://docs.rs/actix-http/3.0.0-beta.16)
[![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.14/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.14)
[![dependency status](https://deps.rs/crate/actix-http/3.0.0-beta.16/status.svg)](https://deps.rs/crate/actix-http/3.0.0-beta.16)
[![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

@@ -1,8 +1,9 @@
use std::{convert::Infallible, io};
use actix_http::{HttpService, Response, StatusCode};
use actix_http::{
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
};
use actix_server::Server;
use http::header::HeaderValue;
#[actix_rt::main]
async fn main() -> io::Result<()> {
@@ -13,12 +14,21 @@ async fn main() -> io::Result<()> {
HttpService::build()
.client_timeout(1000)
.client_disconnect(1000)
.finish(|req| async move {
.on_connect_ext(|_, ext| {
ext.insert(42u32);
})
.finish(|req: Request| async move {
log::info!("{:?}", req);
let mut res = Response::build(StatusCode::OK);
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
let forty_two = req.extensions().get::<u32>().unwrap().to_string();
res.insert_header((
"x-forty-two",
HeaderValue::from_str(&forty_two).unwrap(),
));
Ok::<_, Infallible>(res.body("Hello world!"))
})
.tcp()

View File

@@ -27,6 +27,7 @@ where
S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static,
{
#[inline]
pub fn new(stream: S) -> Self {
BodyStream { stream }
}
@@ -39,6 +40,7 @@ where
{
type Error = E;
#[inline]
fn size(&self) -> BodySize {
BodySize::Stream
}

View File

@@ -8,48 +8,97 @@ use std::{
use bytes::Bytes;
use super::{BodySize, MessageBody, MessageBodyMapErr};
use crate::Error;
use crate::body;
/// A boxed message body with boxed errors.
pub struct BoxBody(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>);
#[derive(Debug)]
pub struct BoxBody(BoxBodyInner);
enum BoxBodyInner {
None(body::None),
Bytes(Bytes),
Stream(Pin<Box<dyn MessageBody<Error = Box<dyn StdError>>>>),
}
impl fmt::Debug for BoxBodyInner {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::None(arg0) => f.debug_tuple("None").field(arg0).finish(),
Self::Bytes(arg0) => f.debug_tuple("Bytes").field(arg0).finish(),
Self::Stream(_) => f.debug_tuple("Stream").field(&"dyn MessageBody").finish(),
}
}
}
impl BoxBody {
/// Boxes a `MessageBody` and any errors it generates.
/// Same as `MessageBody::boxed`.
///
/// If the body type to wrap is unknown or generic it is better to use [`MessageBody::boxed`] to
/// avoid double boxing.
#[inline]
pub fn new<B>(body: B) -> Self
where
B: MessageBody + 'static,
{
let body = MessageBodyMapErr::new(body, Into::into);
Self(Box::pin(body))
match body.size() {
BodySize::None => Self(BoxBodyInner::None(body::None)),
_ => match body.try_into_bytes() {
Ok(bytes) => Self(BoxBodyInner::Bytes(bytes)),
Err(body) => {
let body = MessageBodyMapErr::new(body, Into::into);
Self(BoxBodyInner::Stream(Box::pin(body)))
}
},
}
}
/// Returns a mutable pinned reference to the inner message body type.
pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
self.0.as_mut()
}
}
impl fmt::Debug for BoxBody {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str("BoxBody(dyn MessageBody)")
#[inline]
pub fn as_pin_mut(&mut self) -> Pin<&mut Self> {
Pin::new(self)
}
}
impl MessageBody for BoxBody {
type Error = Error;
type Error = Box<dyn StdError>;
#[inline]
fn size(&self) -> BodySize {
self.0.size()
match &self.0 {
BoxBodyInner::None(none) => none.size(),
BoxBodyInner::Bytes(bytes) => bytes.size(),
BoxBodyInner::Stream(stream) => stream.size(),
}
}
#[inline]
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.0
.as_mut()
.poll_next(cx)
.map_err(|err| Error::new_body().with_cause(err))
match &mut self.0 {
BoxBodyInner::None(body) => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
BoxBodyInner::Bytes(body) => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
BoxBodyInner::Stream(body) => Pin::new(body).poll_next(cx),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
match self.0 {
BoxBodyInner::None(body) => Ok(body.try_into_bytes().unwrap()),
BoxBodyInner::Bytes(body) => Ok(body.try_into_bytes().unwrap()),
_ => Err(self),
}
}
#[inline]
fn boxed(self) -> BoxBody {
self
}
}

View File

@@ -23,6 +23,7 @@ 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 }
}
@@ -30,11 +31,13 @@ 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 }
}
@@ -47,6 +50,7 @@ where
{
type Error = Error;
#[inline]
fn size(&self) -> BodySize {
match self {
EitherBody::Left { body } => body.size(),
@@ -54,6 +58,7 @@ where
}
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
@@ -67,6 +72,26 @@ where
.map_err(|err| Error::new_body().with_cause(err)),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
match self {
EitherBody::Left { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Left { body }),
EitherBody::Right { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Right { body }),
}
}
#[inline]
fn boxed(self) -> BoxBody {
match self {
EitherBody::Left { body } => body.boxed(),
EitherBody::Right { body } => body.boxed(),
}
}
}
#[cfg(test)]

View File

@@ -12,23 +12,56 @@ use bytes::{Bytes, BytesMut};
use futures_core::ready;
use pin_project_lite::pin_project;
use super::BodySize;
use super::{BodySize, BoxBody};
/// An interface types that can converted to bytes and used as response bodies.
// TODO: examples
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
/// The type of error that will be returned if streaming body fails.
///
/// Since it is not appropriate to generate a response mid-stream, it only requires `Error` for
/// internal use and logging.
type Error: Into<Box<dyn StdError>>;
/// Body size hint.
///
/// If [`BodySize::None`] is returned, optimizations that skip reading the body are allowed.
fn size(&self) -> BodySize;
/// Attempt to pull out the next chunk of body bytes.
// TODO: expand documentation
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>>;
/// Try to convert into the complete chunk of body bytes.
///
/// Implement this method if the entire body can be trivially extracted. This is useful for
/// optimizations where `poll_next` calls can be avoided.
///
/// Body types with [`BodySize::None`] are allowed to return empty `Bytes`. Although, if calling
/// this method, it is recommended to check `size` first and return early.
///
/// # Errors
/// The default implementation will error and return the original type back to the caller for
/// further use.
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
Err(self)
}
/// Converts this body into `BoxBody`.
#[inline]
fn boxed(self) -> BoxBody
where
Self: Sized + 'static,
{
BoxBody::new(self)
}
}
mod foreign_impls {
@@ -37,12 +70,10 @@ 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<'_>,
@@ -66,11 +97,16 @@ mod foreign_impls {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(None)
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::new())
}
}
impl<B> MessageBody for Box<B>
where
B: MessageBody + Unpin,
B: MessageBody + Unpin + ?Sized,
{
type Error = B::Error;
@@ -90,7 +126,7 @@ mod foreign_impls {
impl<B> MessageBody for Pin<Box<B>>
where
B: MessageBody,
B: MessageBody + ?Sized,
{
type Error = B::Error;
@@ -101,20 +137,22 @@ mod foreign_impls {
#[inline]
fn poll_next(
mut self: Pin<&mut Self>,
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
self.as_mut().poll_next(cx)
self.get_mut().as_mut().poll_next(cx)
}
}
impl MessageBody for &'static [u8] {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -122,20 +160,25 @@ mod foreign_impls {
if self.is_empty() {
Poll::Ready(None)
} else {
let bytes = mem::take(self.get_mut());
let bytes = Bytes::from_static(bytes);
Poll::Ready(Some(Ok(bytes)))
Poll::Ready(Some(Ok(Bytes::from_static(mem::take(self.get_mut())))))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from_static(self))
}
}
impl MessageBody for Bytes {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -143,19 +186,25 @@ mod foreign_impls {
if self.is_empty() {
Poll::Ready(None)
} else {
let bytes = mem::take(self.get_mut());
Poll::Ready(Some(Ok(bytes)))
Poll::Ready(Some(Ok(mem::take(self.get_mut()))))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self)
}
}
impl MessageBody for BytesMut {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -163,19 +212,25 @@ mod foreign_impls {
if self.is_empty() {
Poll::Ready(None)
} else {
let bytes = mem::take(self.get_mut()).freeze();
Poll::Ready(Some(Ok(bytes)))
Poll::Ready(Some(Ok(mem::take(self.get_mut()).freeze())))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self.freeze())
}
}
impl MessageBody for Vec<u8> {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -183,19 +238,25 @@ mod foreign_impls {
if self.is_empty() {
Poll::Ready(None)
} else {
let bytes = mem::take(self.get_mut());
Poll::Ready(Some(Ok(Bytes::from(bytes))))
Poll::Ready(Some(Ok(mem::take(self.get_mut()).into())))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from(self))
}
}
impl MessageBody for &'static str {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -208,15 +269,22 @@ mod foreign_impls {
Poll::Ready(Some(Ok(bytes)))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from_static(self.as_bytes()))
}
}
impl MessageBody for String {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -228,15 +296,22 @@ mod foreign_impls {
Poll::Ready(Some(Ok(Bytes::from(string))))
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::from(self))
}
}
impl MessageBody for bytestring::ByteString {
type Error = Infallible;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.len() as u64)
}
#[inline]
fn poll_next(
self: Pin<&mut Self>,
_cx: &mut Context<'_>,
@@ -244,6 +319,11 @@ mod foreign_impls {
let string = mem::take(self.get_mut());
Poll::Ready(Some(Ok(string.into_bytes())))
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(self.into_bytes())
}
}
}
@@ -276,6 +356,7 @@ where
{
type Error = E;
#[inline]
fn size(&self) -> BodySize {
self.body.size()
}
@@ -296,6 +377,12 @@ where
None => Poll::Ready(None),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
let Self { body, mapper } = self;
body.try_into_bytes().map_err(|body| Self { body, mapper })
}
}
#[cfg(test)]
@@ -305,6 +392,7 @@ mod tests {
use bytes::{Bytes, BytesMut};
use super::*;
use crate::body::{self, EitherBody};
macro_rules! assert_poll_next {
($pin:expr, $exp:expr) => {
@@ -406,6 +494,47 @@ mod tests {
assert_poll_next!(pl, Bytes::from("test"));
}
#[actix_rt::test]
async fn complete_body_combinators() {
let body = Bytes::from_static(b"test");
let body = BoxBody::new(body);
let body = EitherBody::<_, ()>::left(body);
let body = EitherBody::<(), _>::right(body);
// Do not support try_into_bytes:
// let body = Box::new(body);
// let body = Box::pin(body);
assert_eq!(body.try_into_bytes().unwrap(), Bytes::from("test"));
}
#[actix_rt::test]
async fn complete_body_combinators_poll() {
let body = Bytes::from_static(b"test");
let body = BoxBody::new(body);
let body = EitherBody::<_, ()>::left(body);
let body = EitherBody::<(), _>::right(body);
let mut body = body;
assert_eq!(body.size(), BodySize::Sized(4));
assert_poll_next!(Pin::new(&mut body), Bytes::from("test"));
assert_poll_next_none!(Pin::new(&mut body));
}
#[actix_rt::test]
async fn none_body_combinators() {
fn none_body() -> BoxBody {
let body = body::None;
let body = BoxBody::new(body);
let body = EitherBody::<_, ()>::left(body);
let body = EitherBody::<(), _>::right(body);
body.boxed()
}
assert_eq!(none_body().size(), BodySize::None);
assert_eq!(none_body().try_into_bytes().unwrap(), Bytes::new());
assert_poll_next_none!(Pin::new(&mut none_body()));
}
// down-casting used to be done with a method on MessageBody trait
// test is kept to demonstrate equivalence of Any trait
#[actix_rt::test]

View File

@@ -40,4 +40,9 @@ impl MessageBody for None {
) -> Poll<Option<Result<Bytes, Self::Error>>> {
Poll::Ready(Option::None)
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
Ok(Bytes::new())
}
}

View File

@@ -1,9 +1,11 @@
/// Body size hint.
#[derive(Debug, Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum BodySize {
/// Absence of body can be assumed from method or status code.
/// Implicitly empty body.
///
/// Will skip writing Content-Length header.
/// 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.
None,
/// Known size body.
@@ -18,6 +20,9 @@ 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,6 +27,7 @@ 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 }
}
@@ -41,6 +42,7 @@ where
{
type Error = E;
#[inline]
fn size(&self) -> BodySize {
BodySize::Sized(self.size as u64)
}

View File

@@ -25,7 +25,7 @@ use zstd::stream::write::Encoder as ZstdEncoder;
use super::Writer;
use crate::{
body::{BodySize, MessageBody},
body::{self, BodySize, MessageBody},
error::BlockingError,
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
ResponseHead, StatusCode,
@@ -46,7 +46,9 @@ pin_project! {
impl<B: MessageBody> Encoder<B> {
fn none() -> Self {
Encoder {
body: EncoderBody::None,
body: EncoderBody::None {
body: body::None::new(),
},
encoder: None,
fut: None,
eof: true,
@@ -60,25 +62,23 @@ impl<B: MessageBody> Encoder<B> {
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto);
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 => {}
// no need to compress an empty body
if matches!(body.size(), BodySize::None) {
return Self::none();
}
// 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
let body = match body.try_into_bytes() {
Ok(body) => EncoderBody::Full { body },
Err(body) => EncoderBody::Stream { body },
};
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: EncoderBody::Stream { body },
body,
encoder: Some(enc),
fut: None,
eof: false,
@@ -87,7 +87,7 @@ impl<B: MessageBody> Encoder<B> {
}
Encoder {
body: EncoderBody::Stream { body },
body,
encoder: None,
fut: None,
eof: false,
@@ -98,7 +98,8 @@ impl<B: MessageBody> Encoder<B> {
pin_project! {
#[project = EncoderBodyProj]
enum EncoderBody<B> {
None,
None { body: body::None },
Full { body: Bytes },
Stream { #[pin] body: B },
}
}
@@ -109,9 +110,11 @@ where
{
type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize {
match self {
EncoderBody::None => BodySize::None,
EncoderBody::None { body } => body.size(),
EncoderBody::Full { body } => body.size(),
EncoderBody::Stream { body } => body.size(),
}
}
@@ -121,13 +124,29 @@ where
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EncoderBodyProj::None => Poll::Ready(None),
EncoderBodyProj::None { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Full { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Stream { body } => body
.poll_next(cx)
.map_err(|err| EncoderError::Body(err.into())),
}
}
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
match self {
EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()),
EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()),
_ => Err(self),
}
}
}
impl<B> MessageBody for Encoder<B>
@@ -136,11 +155,12 @@ where
{
type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize {
if self.encoder.is_none() {
self.body.size()
} else {
if self.encoder.is_some() {
BodySize::Stream
} else {
self.body.size()
}
}
@@ -211,6 +231,24 @@ where
}
}
}
#[inline]
fn try_into_bytes(mut self) -> Result<Bytes, Self>
where
Self: Sized,
{
if self.encoder.is_some() {
Err(self)
} else {
match self.body.try_into_bytes() {
Ok(body) => Ok(body),
Err(body) => {
self.body = body;
Err(self)
}
}
}
}
}
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
@@ -218,6 +256,8 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
header::CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()),
);
head.no_chunking(false);
}
enum ContentEncoder {

View File

@@ -332,31 +332,28 @@ impl From<PayloadError> for Error {
}
/// A set of errors that can occur during dispatching HTTP requests.
#[derive(Debug, Display, Error, From)]
#[non_exhaustive]
#[derive(Debug, Display, From)]
pub enum DispatchError {
/// Service error
// FIXME: display and error type
/// Service error.
#[display(fmt = "Service Error")]
Service(#[error(not(source))] Response<BoxBody>),
Service(Response<BoxBody>),
/// Body error
// FIXME: display and error type
#[display(fmt = "Body Error")]
Body(#[error(not(source))] Box<dyn StdError>),
/// Body streaming error.
#[display(fmt = "Body error: {}", _0)]
Body(Box<dyn StdError>),
/// Upgrade service error
/// Upgrade service error.
Upgrade,
/// An `io::Error` that occurred while trying to read or write to a network stream.
#[display(fmt = "IO error: {}", _0)]
Io(io::Error),
/// Http request parse error.
#[display(fmt = "Parse error: {}", _0)]
/// Request parse error.
#[display(fmt = "Request parse error: {}", _0)]
Parse(ParseError),
/// Http/2 error
/// HTTP/2 error.
#[display(fmt = "{}", _0)]
H2(h2::Error),
@@ -368,21 +365,23 @@ pub enum DispatchError {
#[display(fmt = "Connection shutdown timeout")]
DisconnectTimeout,
/// Payload is not consumed
#[display(fmt = "Task is completed but request's payload is not consumed")]
PayloadIsNotConsumed,
/// Malformed request
#[display(fmt = "Malformed request")]
MalformedRequest,
/// Internal error
/// Internal error.
#[display(fmt = "Internal error")]
InternalError,
}
/// Unknown error
#[display(fmt = "Unknown error")]
Unknown,
impl StdError for DispatchError {
fn source(&self) -> Option<&(dyn StdError + 'static)> {
match self {
// TODO: error source extraction?
DispatchError::Service(_res) => None,
DispatchError::Body(err) => Some(&**err),
DispatchError::Io(err) => Some(err),
DispatchError::Parse(err) => Some(err),
DispatchError::H2(err) => Some(err),
_ => None,
}
}
}
/// A set of error that can occur during parsing content type.

View File

@@ -19,7 +19,7 @@ impl Extensions {
#[inline]
pub fn new() -> Extensions {
Extensions {
map: AHashMap::default(),
map: AHashMap::new(),
}
}

View File

@@ -15,14 +15,14 @@ use bitflags::bitflags;
use bytes::{Buf, BytesMut};
use futures_core::ready;
use log::{error, trace};
use pin_project::pin_project;
use pin_project_lite::pin_project;
use crate::{
body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
Extensions, OnConnectData, Request, Response, StatusCode,
Error, Extensions, OnConnectData, Request, Response, StatusCode,
};
use super::{
@@ -46,79 +46,111 @@ bitflags! {
}
}
#[pin_project]
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
// there's 2 versions of Dispatcher state because of:
// https://github.com/taiki-e/pin-project-lite/issues/3
//
// tl;dr: pin-project-lite doesn't play well with other attribute macros
B: MessageBody,
#[cfg(not(test))]
pin_project! {
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
B: MessageBody,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
#[cfg(test)]
poll_count: u64,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
}
}
#[pin_project(project = DispatcherStateProj)]
enum DispatcherState<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
#[cfg(test)]
pin_project! {
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody,
B: MessageBody,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
Upgrade(#[pin] U::Future),
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
// used in tests
poll_count: u64,
}
}
#[pin_project(project = InnerDispatcherProj)]
struct InnerDispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
pin_project! {
#[project = DispatcherStateProj]
enum DispatcherState<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody,
B: MessageBody,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
flow: Rc<HttpFlow<S, X, U>>,
flags: Flags,
peer_addr: Option<net::SocketAddr>,
conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
Normal { #[pin] inner: InnerDispatcher<T, S, B, X, U> },
Upgrade { #[pin] fut: U::Future },
}
}
#[pin]
state: State<S, B, X>,
payload: Option<PayloadSender>,
messages: VecDeque<DispatcherMessage>,
pin_project! {
#[project = InnerDispatcherProj]
struct InnerDispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
ka_expire: Instant,
#[pin]
ka_timer: Option<Sleep>,
B: MessageBody,
io: Option<T>,
read_buf: BytesMut,
write_buf: BytesMut,
codec: Codec,
X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display,
{
flow: Rc<HttpFlow<S, X, U>>,
flags: Flags,
peer_addr: Option<net::SocketAddr>,
conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>,
#[pin]
state: State<S, B, X>,
payload: Option<PayloadSender>,
messages: VecDeque<DispatcherMessage>,
ka_expire: Instant,
#[pin]
ka_timer: Option<Sleep>,
io: Option<T>,
read_buf: BytesMut,
write_buf: BytesMut,
codec: Codec,
}
}
enum DispatcherMessage {
@@ -127,19 +159,21 @@ enum DispatcherMessage {
Error(Response<()>),
}
#[pin_project(project = StateProj)]
enum State<S, B, X>
where
S: Service<Request>,
X: Service<Request, Response = Request>,
pin_project! {
#[project = StateProj]
enum State<S, B, X>
where
S: Service<Request>,
X: Service<Request, Response = Request>,
B: MessageBody,
{
None,
ExpectCall(#[pin] X::Future),
ServiceCall(#[pin] S::Future),
SendPayload(#[pin] B),
SendErrorPayload(#[pin] BoxBody),
B: MessageBody,
{
None,
ExpectCall { #[pin] fut: X::Future },
ServiceCall { #[pin] fut: S::Future },
SendPayload { #[pin] body: B },
SendErrorPayload { #[pin] body: BoxBody },
}
}
impl<S, B, X> State<S, B, X>
@@ -198,25 +232,27 @@ where
};
Dispatcher {
inner: DispatcherState::Normal(InnerDispatcher {
flow,
flags,
peer_addr,
conn_data: conn_data.0.map(Rc::new),
error: None,
inner: DispatcherState::Normal {
inner: InnerDispatcher {
flow,
flags,
peer_addr,
conn_data: conn_data.0.map(Rc::new),
error: None,
state: State::None,
payload: None,
messages: VecDeque::new(),
state: State::None,
payload: None,
messages: VecDeque::new(),
ka_expire,
ka_timer,
ka_expire,
ka_timer,
io: Some(io),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
codec: Codec::new(config),
}),
io: Some(io),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
codec: Codec::new(config),
},
},
#[cfg(test)]
poll_count: 0,
@@ -316,7 +352,7 @@ where
let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size {
BodySize::None | BodySize::Sized(0) => State::None,
_ => State::SendPayload(body),
_ => State::SendPayload { body },
};
self.project().state.set(state);
Ok(())
@@ -330,7 +366,7 @@ where
let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size {
BodySize::None | BodySize::Sized(0) => State::None,
_ => State::SendErrorPayload(body),
_ => State::SendErrorPayload { body },
};
self.project().state.set(state);
Ok(())
@@ -356,12 +392,12 @@ where
// Handle `EXPECT: 100-Continue` header
if req.head().expect() {
// set InnerDispatcher state and continue loop to poll it.
let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall(task));
let fut = this.flow.expect.call(req);
this.state.set(State::ExpectCall { fut });
} else {
// the same as expect call.
let task = this.flow.service.call(req);
this.state.set(State::ServiceCall(task));
let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut });
};
}
@@ -381,7 +417,7 @@ where
// all messages are dealt with.
None => return Ok(PollResponse::DoNothing),
},
StateProj::ServiceCall(fut) => match fut.poll(cx) {
StateProj::ServiceCall { fut } => match fut.poll(cx) {
// service call resolved. send response.
Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(());
@@ -407,11 +443,11 @@ where
}
},
StateProj::SendPayload(mut stream) => {
StateProj::SendPayload { mut body } => {
// keep populate writer buffer until buffer size limit hit,
// get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) {
match body.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec
.encode(Message::Chunk(Some(item)), this.write_buf)?;
@@ -437,13 +473,13 @@ where
return Ok(PollResponse::DrainWriteBuf);
}
StateProj::SendErrorPayload(mut stream) => {
StateProj::SendErrorPayload { mut body } => {
// TODO: de-dupe impl with SendPayload
// keep populate writer buffer until buffer size limit hit,
// get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) {
match body.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec
.encode(Message::Chunk(Some(item)), this.write_buf)?;
@@ -458,7 +494,9 @@ where
}
Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Service(err.into()))
return Err(DispatchError::Body(
Error::new_body().with_cause(err).into(),
))
}
Poll::Pending => return Ok(PollResponse::DoNothing),
@@ -469,14 +507,14 @@ where
return Ok(PollResponse::DrainWriteBuf);
}
StateProj::ExpectCall(fut) => match fut.poll(cx) {
StateProj::ExpectCall { fut } => match fut.poll(cx) {
// expect resolved. write continue to buffer and set InnerDispatcher state
// to service call.
Poll::Ready(Ok(req)) => {
this.write_buf
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall(fut));
this.state.set(State::ServiceCall { fut });
}
// send expect error as response
@@ -502,25 +540,25 @@ where
let mut this = self.as_mut().project();
if req.head().expect() {
// set dispatcher state so the future is pinned.
let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall(task));
let fut = this.flow.expect.call(req);
this.state.set(State::ExpectCall { fut });
} else {
// the same as above.
let task = this.flow.service.call(req);
this.state.set(State::ServiceCall(task));
let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut });
};
// eagerly poll the future for once(or twice if expect is resolved immediately).
loop {
match self.as_mut().project().state.project() {
StateProj::ExpectCall(fut) => {
StateProj::ExpectCall { fut } => {
match fut.poll(cx) {
// expect is resolved. continue loop and poll the service call branch.
Poll::Ready(Ok(req)) => {
self.as_mut().send_continue();
let mut this = self.as_mut().project();
let task = this.flow.service.call(req);
this.state.set(State::ServiceCall(task));
let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut });
continue;
}
// future is pending. return Ok(()) to notify that a new state is
@@ -536,7 +574,7 @@ where
}
}
}
StateProj::ServiceCall(fut) => {
StateProj::ServiceCall { fut } => {
// return no matter the service call future's result.
return match fut.poll(cx) {
// future is resolved. send response and return a result. On success
@@ -901,7 +939,7 @@ where
}
match this.inner.project() {
DispatcherStateProj::Normal(mut inner) => {
DispatcherStateProj::Normal { mut inner } => {
inner.as_mut().poll_keepalive(cx)?;
if inner.flags.contains(Flags::SHUTDOWN) {
@@ -941,7 +979,7 @@ where
self.as_mut()
.project()
.inner
.set(DispatcherState::Upgrade(upgrade));
.set(DispatcherState::Upgrade { fut: upgrade });
return self.poll(cx);
}
};
@@ -993,8 +1031,8 @@ where
}
}
}
DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
error!("Upgrade handler error: {}", e);
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| {
error!("Upgrade handler error: {}", err);
DispatchError::Upgrade
}),
}
@@ -1088,7 +1126,7 @@ mod tests {
Poll::Ready(res) => assert!(res.is_err()),
}
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
assert!(inner.flags.contains(Flags::READ_DISCONNECT));
assert_eq!(
&inner.project().io.take().unwrap().write_buf[..26],
@@ -1123,7 +1161,7 @@ mod tests {
actix_rt::pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"),
@@ -1133,7 +1171,7 @@ mod tests {
// polls: initial => shutdown
assert_eq!(h1.poll_count, 2);
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
let res = &mut inner.project().io.take().unwrap().write_buf[..];
stabilize_date_header(res);
@@ -1177,7 +1215,7 @@ mod tests {
actix_rt::pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"),
@@ -1187,7 +1225,7 @@ mod tests {
// polls: initial => shutdown
assert_eq!(h1.poll_count, 1);
if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() {
let res = &mut inner.project().io.take().unwrap().write_buf[..];
stabilize_date_header(res);
@@ -1237,13 +1275,13 @@ mod tests {
actix_rt::pin!(h1);
assert!(h1.as_mut().poll(cx).is_pending());
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
// polls: manual
assert_eq!(h1.poll_count, 1);
eprintln!("poll count: {}", h1.poll_count);
if let DispatcherState::Normal(ref inner) = h1.inner {
if let DispatcherState::Normal { ref inner } = h1.inner {
let io = inner.io.as_ref().unwrap();
let res = &io.write_buf()[..];
assert_eq!(
@@ -1258,7 +1296,7 @@ mod tests {
// polls: manual manual shutdown
assert_eq!(h1.poll_count, 3);
if let DispatcherState::Normal(ref inner) = h1.inner {
if let DispatcherState::Normal { ref inner } = h1.inner {
let io = inner.io.as_ref().unwrap();
let mut res = (&io.write_buf()[..]).to_owned();
stabilize_date_header(&mut res);
@@ -1309,12 +1347,12 @@ mod tests {
actix_rt::pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
assert!(matches!(&h1.inner, DispatcherState::Normal { .. }));
// polls: manual shutdown
assert_eq!(h1.poll_count, 2);
if let DispatcherState::Normal(ref inner) = h1.inner {
if let DispatcherState::Normal { ref inner } = h1.inner {
let io = inner.io.as_ref().unwrap();
let mut res = (&io.write_buf()[..]).to_owned();
stabilize_date_header(&mut res);
@@ -1386,7 +1424,7 @@ mod tests {
actix_rt::pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Upgrade(_)));
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. }));
// polls: manual shutdown
assert_eq!(h1.poll_count, 2);

View File

@@ -356,9 +356,9 @@ where
type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self._poll_ready(cx).map_err(|e| {
log::error!("HTTP/1 service readiness error: {:?}", e);
DispatchError::Service(e)
self._poll_ready(cx).map_err(|err| {
log::error!("HTTP/1 service readiness error: {:?}", err);
DispatchError::Service(err)
})
}

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,25 +217,28 @@ 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(cmp::min(chunk.len(), CHUNK_SIZE));
stream.reserve_capacity(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(cap, len));
let bytes = chunk.split_to(cmp::min(len, cap));
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(|e| {
let e = e.into();
error!("Service readiness error: {:?}", e);
DispatchError::Service(e)
self.flow.service.poll_ready(cx).map_err(|err| {
let err = err.into();
error!("Service readiness error: {:?}", err);
DispatchError::Service(err)
})
}
@@ -297,7 +297,6 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S::Future: 'static,
{
Incoming(Dispatcher<T, S, B, (), ()>),
Handshake(
Option<Rc<HttpFlow<S, (), ()>>>,
Option<ServiceConfig>,
@@ -305,6 +304,7 @@ where
OnConnectData,
HandshakeWithTimeout<T>,
),
Established(Dispatcher<T, S, B, (), ()>),
}
pub struct H2ServiceHandlerResponse<T, S, B>
@@ -332,7 +332,6 @@ 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,
@@ -343,7 +342,7 @@ where
Ok((conn, timer)) => {
let on_connect_data = mem::take(conn_data);
self.state = State::Incoming(Dispatcher::new(
self.state = State::Established(Dispatcher::new(
conn,
srv.take().unwrap(),
config.take().unwrap(),
@@ -360,6 +359,8 @@ where
Poll::Ready(Err(err))
}
},
State::Established(ref mut disp) => Pin::new(disp).poll(cx),
}
}
}

View File

@@ -16,6 +16,7 @@ pub trait Sealed {
}
impl Sealed for HeaderName {
#[inline]
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(self))
}
@@ -23,6 +24,7 @@ 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))
}
@@ -30,6 +32,7 @@ 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)
}
@@ -37,6 +40,7 @@ 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)
}
@@ -44,6 +48,7 @@ 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,22 +1,20 @@
//! [`IntoHeaderPair`] trait and implementations.
//! [`TryIntoHeaderPair`] trait and implementations.
use std::convert::TryFrom as _;
use http::{
header::{HeaderName, InvalidHeaderName, InvalidHeaderValue},
Error as HttpError, HeaderValue,
use super::{
Header, HeaderName, HeaderValue, InvalidHeaderName, InvalidHeaderValue, TryIntoHeaderValue,
};
use crate::error::HttpError;
use super::{Header, IntoHeaderValue};
/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for
/// An interface for types that can be converted into a [`HeaderName`] + [`HeaderValue`] pair for
/// insertion into a [`HeaderMap`].
///
/// [`HeaderMap`]: super::HeaderMap
pub trait IntoHeaderPair: Sized {
pub trait TryIntoHeaderPair: Sized {
type Error: Into<HttpError>;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error>;
}
#[derive(Debug)]
@@ -34,14 +32,14 @@ impl From<InvalidHeaderPart> for HttpError {
}
}
impl<V> IntoHeaderPair for (HeaderName, V)
impl<V> TryIntoHeaderPair for (HeaderName, V)
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let value = value
.try_into_value()
@@ -50,14 +48,14 @@ where
}
}
impl<V> IntoHeaderPair for (&HeaderName, V)
impl<V> TryIntoHeaderPair for (&HeaderName, V)
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let value = value
.try_into_value()
@@ -66,14 +64,14 @@ where
}
}
impl<V> IntoHeaderPair for (&[u8], V)
impl<V> TryIntoHeaderPair for (&[u8], V)
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
let value = value
@@ -83,14 +81,14 @@ where
}
}
impl<V> IntoHeaderPair for (&str, V)
impl<V> TryIntoHeaderPair for (&str, V)
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
let name = HeaderName::try_from(name).map_err(InvalidHeaderPart::Name)?;
let value = value
@@ -100,23 +98,25 @@ where
}
}
impl<V> IntoHeaderPair for (String, V)
impl<V> TryIntoHeaderPair for (String, V)
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
V::Error: Into<InvalidHeaderValue>,
{
type Error = InvalidHeaderPart;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
#[inline]
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
let (name, value) = self;
(name.as_str(), value).try_into_header_pair()
(name.as_str(), value).try_into_pair()
}
}
impl<T: Header> IntoHeaderPair for T {
type Error = <T as IntoHeaderValue>::Error;
impl<T: Header> TryIntoHeaderPair for T {
type Error = <T as TryIntoHeaderValue>::Error;
fn try_into_header_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
#[inline]
fn try_into_pair(self) -> Result<(HeaderName, HeaderValue), Self::Error> {
Ok((T::name(), self.try_into_value()?))
}
}

View File

@@ -1,4 +1,4 @@
//! [`IntoHeaderValue`] trait and implementations.
//! [`TryIntoHeaderValue`] 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 IntoHeaderValue: Sized {
pub trait TryIntoHeaderValue: Sized {
/// The type returned in the event of a conversion error.
type Error: Into<HttpError>;
@@ -15,7 +15,7 @@ pub trait IntoHeaderValue: Sized {
fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
}
impl IntoHeaderValue for HeaderValue {
impl TryIntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
@@ -24,7 +24,7 @@ impl IntoHeaderValue for HeaderValue {
}
}
impl IntoHeaderValue for &HeaderValue {
impl TryIntoHeaderValue for &HeaderValue {
type Error = InvalidHeaderValue;
#[inline]
@@ -33,7 +33,7 @@ impl IntoHeaderValue for &HeaderValue {
}
}
impl IntoHeaderValue for &str {
impl TryIntoHeaderValue for &str {
type Error = InvalidHeaderValue;
#[inline]
@@ -42,7 +42,7 @@ impl IntoHeaderValue for &str {
}
}
impl IntoHeaderValue for &[u8] {
impl TryIntoHeaderValue for &[u8] {
type Error = InvalidHeaderValue;
#[inline]
@@ -51,7 +51,7 @@ impl IntoHeaderValue for &[u8] {
}
}
impl IntoHeaderValue for Bytes {
impl TryIntoHeaderValue for Bytes {
type Error = InvalidHeaderValue;
#[inline]
@@ -60,7 +60,7 @@ impl IntoHeaderValue for Bytes {
}
}
impl IntoHeaderValue for Vec<u8> {
impl TryIntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValue;
#[inline]
@@ -69,7 +69,7 @@ impl IntoHeaderValue for Vec<u8> {
}
}
impl IntoHeaderValue for String {
impl TryIntoHeaderValue for String {
type Error = InvalidHeaderValue;
#[inline]
@@ -78,7 +78,7 @@ impl IntoHeaderValue for String {
}
}
impl IntoHeaderValue for usize {
impl TryIntoHeaderValue for usize {
type Error = InvalidHeaderValue;
#[inline]
@@ -87,7 +87,7 @@ impl IntoHeaderValue for usize {
}
}
impl IntoHeaderValue for i64 {
impl TryIntoHeaderValue for i64 {
type Error = InvalidHeaderValue;
#[inline]
@@ -96,7 +96,7 @@ impl IntoHeaderValue for i64 {
}
}
impl IntoHeaderValue for u64 {
impl TryIntoHeaderValue for u64 {
type Error = InvalidHeaderValue;
#[inline]
@@ -105,7 +105,7 @@ impl IntoHeaderValue for u64 {
}
}
impl IntoHeaderValue for i32 {
impl TryIntoHeaderValue for i32 {
type Error = InvalidHeaderValue;
#[inline]
@@ -114,7 +114,7 @@ impl IntoHeaderValue for i32 {
}
}
impl IntoHeaderValue for u32 {
impl TryIntoHeaderValue for u32 {
type Error = InvalidHeaderValue;
#[inline]
@@ -123,7 +123,7 @@ impl IntoHeaderValue for u32 {
}
}
impl IntoHeaderValue for Mime {
impl TryIntoHeaderValue for Mime {
type Error = InvalidHeaderValue;
#[inline]

View File

@@ -306,8 +306,11 @@ impl HeaderMap {
/// assert_eq!(set_cookies_iter.next().unwrap(), "two=2");
/// assert!(set_cookies_iter.next().is_none());
/// ```
pub fn get_all(&self, key: impl AsHeaderName) -> GetAll<'_> {
GetAll::new(self.get_value(key))
pub fn get_all(&self, key: impl AsHeaderName) -> std::slice::Iter<'_, HeaderValue> {
match self.get_value(key) {
Some(value) => value.iter(),
None => (&[]).iter(),
}
}
// TODO: get_all_mut ?
@@ -333,7 +336,7 @@ impl HeaderMap {
}
}
/// Inserts a name-value pair into the map.
/// Inserts (overrides) a name-value pair in 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 +375,7 @@ impl HeaderMap {
Removed::new(value)
}
/// Inserts a name-value pair into the map.
/// Appends a name-value pair to 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
@@ -602,52 +605,6 @@ impl<'a> IntoIterator for &'a HeaderMap {
}
}
/// Iterator over borrowed values with the same associated name.
///
/// See [`HeaderMap::get_all`].
#[derive(Debug)]
pub struct GetAll<'a> {
idx: usize,
value: Option<&'a Value>,
}
impl<'a> GetAll<'a> {
fn new(value: Option<&'a Value>) -> Self {
Self { idx: 0, value }
}
}
impl<'a> Iterator for GetAll<'a> {
type Item = &'a HeaderValue;
fn next(&mut self) -> Option<Self::Item> {
let val = self.value?;
match val.get(self.idx) {
Some(val) => {
self.idx += 1;
Some(val)
}
None => {
// current index is none; remove value to fast-path future next calls
self.value = None;
None
}
}
}
fn size_hint(&self) -> (usize, Option<usize>) {
match self.value {
Some(val) => (val.len(), Some(val.len())),
None => (0, Some(0)),
}
}
}
impl ExactSizeIterator for GetAll<'_> {}
impl iter::FusedIterator for GetAll<'_> {}
/// Iterator over removed, owned values with the same associated name.
///
/// Returned from methods that remove or replace items. See [`HeaderMap::insert`]
@@ -895,7 +852,7 @@ mod tests {
assert_impl_all!(HeaderMap: IntoIterator);
assert_impl_all!(Keys<'_>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(GetAll<'_>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(std::slice::Iter<'_, HeaderValue>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(Removed: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(Iter<'_>: Iterator, ExactSizeIterator, FusedIterator);
assert_impl_all!(IntoIter: Iterator, ExactSizeIterator, FusedIterator);

View File

@@ -37,8 +37,8 @@ mod shared;
mod utils;
pub use self::as_name::AsHeaderName;
pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::IntoHeaderValue;
pub use self::into_pair::TryIntoHeaderPair;
pub use self::into_value::TryIntoHeaderValue;
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: IntoHeaderValue {
pub trait Header: TryIntoHeaderValue {
/// 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, IntoHeaderValue},
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue},
HttpMessage,
};
@@ -96,7 +96,7 @@ impl TryFrom<&str> for ContentEncoding {
}
}
impl IntoHeaderValue for ContentEncoding {
impl TryIntoHeaderValue for ContentEncoding {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {

View File

@@ -4,7 +4,8 @@ use bytes::BytesMut;
use http::header::{HeaderValue, InvalidHeaderValue};
use crate::{
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, helpers::MutWriter,
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue,
helpers::MutWriter,
};
/// A timestamp with HTTP-style formatting and parsing.
@@ -29,7 +30,7 @@ impl fmt::Display for HttpDate {
}
}
impl IntoHeaderValue for HttpDate {
impl TryIntoHeaderValue for HttpDate {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

@@ -44,13 +44,12 @@ pub trait Head: Default + 'static {
F: FnOnce(&MessagePool<Self>) -> R;
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct RequestHead {
pub method: Method,
pub uri: Uri,
pub version: Version,
pub headers: HeaderMap,
pub extensions: RefCell<Extensions>,
pub peer_addr: Option<net::SocketAddr>,
flags: Flags,
}
@@ -62,7 +61,6 @@ impl Default for RequestHead {
uri: Uri::default(),
version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16),
extensions: RefCell::new(Extensions::new()),
peer_addr: None,
flags: Flags::empty(),
}
@@ -73,7 +71,6 @@ impl Head for RequestHead {
fn clear(&mut self) {
self.flags = Flags::empty();
self.headers.clear();
self.extensions.get_mut().clear();
}
fn with_pool<F, R>(f: F) -> R
@@ -85,18 +82,6 @@ impl Head for RequestHead {
}
impl RequestHead {
/// Message extensions
#[inline]
pub fn extensions(&self) -> Ref<'_, Extensions> {
self.extensions.borrow()
}
/// Mutable reference to a the message's extensions
#[inline]
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.extensions.borrow_mut()
}
/// Read the message headers.
pub fn headers(&self) -> &HeaderMap {
&self.headers

View File

@@ -7,10 +7,10 @@ use h2::RecvStream;
use crate::error::PayloadError;
/// Type represent boxed payload
/// A boxed payload.
pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
/// Type represent streaming payload
/// A streaming payload.
pub enum Payload<S = PayloadStream> {
None,
H1(crate::h1::Payload),

View File

@@ -1,8 +1,8 @@
//! HTTP requests.
use std::{
cell::{Ref, RefMut},
fmt, net,
cell::{Ref, RefCell, RefMut},
fmt, mem, net,
rc::Rc,
str,
};
@@ -22,6 +22,7 @@ pub struct Request<P = PayloadStream> {
pub(crate) payload: Payload<P>,
pub(crate) head: Message<RequestHead>,
pub(crate) conn_data: Option<Rc<Extensions>>,
pub(crate) req_data: RefCell<Extensions>,
}
impl<P> HttpMessage for Request<P> {
@@ -33,19 +34,19 @@ impl<P> HttpMessage for Request<P> {
}
fn take_payload(&mut self) -> Payload<P> {
std::mem::replace(&mut self.payload, Payload::None)
mem::replace(&mut self.payload, Payload::None)
}
/// Request extensions
#[inline]
fn extensions(&self) -> Ref<'_, Extensions> {
self.head.extensions()
self.req_data.borrow()
}
/// Mutable reference to a the request's extensions
#[inline]
fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.head.extensions_mut()
self.req_data.borrow_mut()
}
}
@@ -54,6 +55,7 @@ impl From<Message<RequestHead>> for Request<PayloadStream> {
Request {
head,
payload: Payload::None,
req_data: RefCell::new(Extensions::default()),
conn_data: None,
}
}
@@ -65,6 +67,7 @@ impl Request<PayloadStream> {
Request {
head: Message::new(),
payload: Payload::None,
req_data: RefCell::new(Extensions::default()),
conn_data: None,
}
}
@@ -76,6 +79,7 @@ impl<P> Request<P> {
Request {
payload,
head: Message::new(),
req_data: RefCell::new(Extensions::default()),
conn_data: None,
}
}
@@ -88,6 +92,7 @@ impl<P> Request<P> {
Request {
payload,
head: self.head,
req_data: self.req_data,
conn_data: self.conn_data,
},
pl,
@@ -101,7 +106,7 @@ impl<P> Request<P> {
/// Get request's payload
pub fn take_payload(&mut self) -> Payload<P> {
std::mem::replace(&mut self.payload, Payload::None)
mem::replace(&mut self.payload, Payload::None)
}
/// Split request into request head and payload
@@ -124,7 +129,7 @@ impl<P> Request<P> {
/// Mutable reference to the message's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head_mut().headers
&mut self.head.headers
}
/// Request's uri.
@@ -136,7 +141,7 @@ impl<P> Request<P> {
/// Mutable reference to the request's uri.
#[inline]
pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.head_mut().uri
&mut self.head.uri
}
/// Read the Request method.
@@ -198,6 +203,11 @@ impl<P> Request<P> {
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
self.conn_data.take()
}
/// Returns the request data container, leaving an empty one in it's place.
pub fn take_req_data(&mut self) -> Extensions {
mem::take(&mut self.req_data.get_mut())
}
}
impl<P> fmt::Debug for Request<P> {

View File

@@ -11,7 +11,7 @@ use bytestring::ByteString;
use crate::{
body::{BoxBody, MessageBody},
extensions::Extensions,
header::{self, HeaderMap, IntoHeaderValue},
header::{self, HeaderMap, TryIntoHeaderValue},
message::{BoxedResponseHead, ResponseHead},
Error, ResponseBuilder, StatusCode,
};
@@ -170,7 +170,7 @@ impl<B> Response<B> {
/// Returns split head and body.
///
/// # Implementation Notes
/// Due to internal performance optimisations, the first element of the returned tuple is a
/// Due to internal performance optimizations, the first element of the returned tuple is a
/// `Response` as well but only contains the head of the response this was called on.
pub fn into_parts(self) -> (Response<()>, B) {
self.replace_body(())
@@ -194,7 +194,7 @@ impl<B> Response<B> {
where
B: MessageBody + 'static,
{
self.map_body(|_, body| BoxBody::new(body))
self.map_body(|_, body| body.boxed())
}
/// Returns body, consuming this response.

View File

@@ -8,7 +8,7 @@ use std::{
use crate::{
body::{EitherBody, MessageBody},
error::{Error, HttpError},
header::{self, IntoHeaderPair, IntoHeaderValue},
header::{self, TryIntoHeaderPair, TryIntoHeaderValue},
message::{BoxedResponseHead, ConnectionType, ResponseHead},
Extensions, Response, StatusCode,
};
@@ -90,12 +90,9 @@ impl ResponseBuilder {
/// assert!(res.headers().contains_key("content-type"));
/// assert!(res.headers().contains_key("x-test"));
/// ```
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
if let Some(parts) = self.inner() {
match header.try_into_header_pair() {
match header.try_into_pair() {
Ok((key, value)) => {
parts.headers.insert(key, value);
}
@@ -121,12 +118,9 @@ 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<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
if let Some(parts) = self.inner() {
match header.try_into_header_pair() {
match header.try_into_pair() {
Ok((key, value)) => parts.headers.append(key, value),
Err(e) => self.err = Some(e.into()),
};
@@ -157,7 +151,7 @@ impl ResponseBuilder {
#[inline]
pub fn upgrade<V>(&mut self, value: V) -> &mut Self
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
if let Some(parts) = self.inner() {
parts.set_connection_type(ConnectionType::Upgrade);
@@ -195,7 +189,7 @@ impl ResponseBuilder {
#[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self
where
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
if let Some(parts) = self.inner() {
match value.try_into_value() {

View File

@@ -493,9 +493,9 @@ where
type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self._poll_ready(cx).map_err(|e| {
log::error!("HTTP service readiness error: {:?}", e);
DispatchError::Service(e)
self._poll_ready(cx).map_err(|err| {
log::error!("HTTP service readiness error: {:?}", err);
DispatchError::Service(err)
})
}

View File

@@ -14,7 +14,7 @@ use bytes::{Bytes, BytesMut};
use http::{Method, Uri, Version};
use crate::{
header::{HeaderMap, IntoHeaderPair},
header::{HeaderMap, TryIntoHeaderPair},
payload::Payload,
Request,
};
@@ -92,11 +92,8 @@ impl TestRequest {
}
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
match header.try_into_pair() {
Ok((key, value)) => {
parts(&mut self.0).headers.insert(key, value);
}
@@ -109,11 +106,8 @@ impl TestRequest {
}
/// Append a header, keeping any that were set with an equivalent field name.
pub fn append_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self {
match header.try_into_pair() {
Ok((key, value)) => {
parts(&mut self.0).headers.append(key, value);
}
@@ -270,7 +264,7 @@ impl TestSeqBuffer {
/// Create new empty `TestBuffer` instance.
pub fn empty() -> Self {
Self::new("")
Self::new(BytesMut::new())
}
pub fn read_buf(&self) -> Ref<'_, BytesMut> {

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);
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB
let mut srv = test_server(move || {
HttpService::build()
.h2(|mut req: Request<_>| async move {

View File

@@ -3,6 +3,10 @@
## 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.9"
version = "0.4.0-beta.10"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart form support for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"]
@@ -14,8 +14,8 @@ name = "actix_multipart"
path = "src/lib.rs"
[dependencies]
actix-web = { version = "4.0.0-beta.11", default-features = false }
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", default-features = false }
bytes = "1"
derive_more = "0.99.5"
@@ -28,7 +28,7 @@ twoway = "0.2"
[dev-dependencies]
actix-rt = "2.2"
actix-http = "3.0.0-beta.14"
actix-http = "3.0.0-beta.16"
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.9)](https://docs.rs/actix-multipart/0.4.0-beta.9)
[![Documentation](https://docs.rs/actix-multipart/badge.svg?version=0.4.0-beta.10)](https://docs.rs/actix-multipart/0.4.0-beta.10)
[![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.9/status.svg)](https://deps.rs/crate/actix-multipart/0.4.0-beta.9)
[![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)
[![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

@@ -1,6 +1,9 @@
# Changes
## Unreleased - 2021-xx-xx
## 0.5.0-beta.3 - 2021-12-17
* Minimum supported Rust version (MSRV) is now 1.52.

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-router"
version = "0.5.0-beta.2"
version = "0.5.0-beta.3"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"Ali MJ Al-Nasrawy <alimjalnasrawy@gmail.com>",

View File

@@ -7,144 +7,20 @@
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

@@ -0,0 +1,92 @@
/// 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

@@ -0,0 +1,36 @@
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

@@ -2,22 +2,28 @@ use crate::ResourcePath;
#[allow(dead_code)]
const GEN_DELIMS: &[u8] = b":/?#[]@";
#[allow(dead_code)]
const SUB_DELIMS_WITHOUT_QS: &[u8] = b"!$'()*,";
#[allow(dead_code)]
const SUB_DELIMS: &[u8] = b"!$'()*,+?=;";
#[allow(dead_code)]
const RESERVED: &[u8] = b":/?#[]@!$'()*,+?=;";
#[allow(dead_code)]
const UNRESERVED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~";
const ALLOWED: &[u8] = b"abcdefghijklmnopqrstuvwxyz
ABCDEFGHIJKLMNOPQRSTUVWXYZ
1234567890
-._~
!$'()*,";
const QS: &[u8] = b"+&=;b";
#[inline]
@@ -34,19 +40,20 @@ thread_local! {
static DEFAULT_QUOTER: Quoter = Quoter::new(b"@:", b"%/+");
}
#[derive(Default, Clone, Debug)]
#[derive(Debug, Clone, Default)]
pub struct Url {
uri: http::Uri,
path: Option<String>,
}
impl Url {
#[inline]
pub fn new(uri: http::Uri) -> Url {
let path = DEFAULT_QUOTER.with(|q| q.requote(uri.path().as_bytes()));
Url { uri, path }
}
#[inline]
pub fn with_quoter(uri: http::Uri, quoter: &Quoter) -> Url {
Url {
path: quoter.requote(uri.path().as_bytes()),
@@ -54,15 +61,16 @@ impl Url {
}
}
#[inline]
pub fn uri(&self) -> &http::Uri {
&self.uri
}
#[inline]
pub fn path(&self) -> &str {
if let Some(ref s) = self.path {
s
} else {
self.uri.path()
match self.path {
Some(ref path) => path,
_ => self.uri.path(),
}
}
@@ -86,6 +94,7 @@ impl ResourcePath for Url {
}
}
/// A quoter
pub struct Quoter {
safe_table: [u8; 16],
protected_table: [u8; 16],
@@ -93,7 +102,7 @@ pub struct Quoter {
impl Quoter {
pub fn new(safe: &[u8], protected: &[u8]) -> Quoter {
let mut q = Quoter {
let mut quoter = Quoter {
safe_table: [0; 16],
protected_table: [0; 16],
};
@@ -101,24 +110,24 @@ impl Quoter {
// prepare safe table
for i in 0..128 {
if ALLOWED.contains(&i) {
set_bit(&mut q.safe_table, i);
set_bit(&mut quoter.safe_table, i);
}
if QS.contains(&i) {
set_bit(&mut q.safe_table, i);
set_bit(&mut quoter.safe_table, i);
}
}
for ch in safe {
set_bit(&mut q.safe_table, *ch)
set_bit(&mut quoter.safe_table, *ch)
}
// prepare protected table
for ch in protected {
set_bit(&mut q.safe_table, *ch);
set_bit(&mut q.protected_table, *ch);
set_bit(&mut quoter.safe_table, *ch);
set_bit(&mut quoter.protected_table, *ch);
}
q
quoter
}
pub fn requote(&self, val: &[u8]) -> Option<String> {
@@ -215,7 +224,7 @@ mod tests {
}
#[test]
fn test_parse_url() {
fn parse_url() {
let re = "/user/{id}/test";
let path = match_url(re, "/user/2345/test");
@@ -231,24 +240,24 @@ mod tests {
}
#[test]
fn test_protected_chars() {
fn protected_chars() {
let encoded = percent_encode(PROTECTED);
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
assert_eq!(path.get("id").unwrap(), &encoded);
}
#[test]
fn test_non_protecteed_ascii() {
let nonprotected_ascii = ('\u{0}'..='\u{7F}')
fn non_protected_ascii() {
let non_protected_ascii = ('\u{0}'..='\u{7F}')
.filter(|&c| c.is_ascii() && !PROTECTED.contains(&(c as u8)))
.collect::<String>();
let encoded = percent_encode(nonprotected_ascii.as_bytes());
let encoded = percent_encode(non_protected_ascii.as_bytes());
let path = match_url("/user/{id}/test", format!("/user/{}/test", encoded));
assert_eq!(path.get("id").unwrap(), &nonprotected_ascii);
assert_eq!(path.get("id").unwrap(), &non_protected_ascii);
}
#[test]
fn test_valid_utf8_multibyte() {
fn valid_utf8_multibyte() {
let test = ('\u{FF00}'..='\u{FFFF}').collect::<String>();
let encoded = percent_encode(test.as_bytes());
let path = match_url("/a/{id}/b", format!("/a/{}/b", &encoded));
@@ -256,7 +265,7 @@ mod tests {
}
#[test]
fn test_invalid_utf8() {
fn invalid_utf8() {
let invalid_utf8 = percent_encode((0x80..=0xff).collect::<Vec<_>>().as_slice());
let uri = Uri::try_from(format!("/{}", invalid_utf8)).unwrap();
let path = Path::new(Url::new(uri));
@@ -266,7 +275,7 @@ mod tests {
}
#[test]
fn test_from_hex() {
fn hex_encoding() {
let hex = b"0123456789abcdefABCDEF";
for i in 0..256 {

View File

@@ -3,6 +3,17 @@
## Unreleased - 2021-xx-xx
## 0.1.0-beta.9 - 2021-12-17
* Re-export `actix_http::body::to_bytes`. [#2518]
* Update `actix_web::test` re-exports. [#2518]
[#2518]: https://github.com/actix/actix-web/pull/2518
## 0.1.0-beta.8 - 2021-12-11
* No significant changes since `0.1.0-beta.7`.
## 0.1.0-beta.7 - 2021-11-22
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]

View File

@@ -1,6 +1,6 @@
[package]
name = "actix-test"
version = "0.1.0-beta.7"
version = "0.1.0-beta.9"
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.14"
actix-http-test = "3.0.0-beta.7"
actix-http = "3.0.0-beta.16"
actix-http-test = "3.0.0-beta.9"
actix-rt = "2.1"
actix-service = "2.0.0"
actix-utils = "3.0.0"
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"] }
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.14", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] }

View File

@@ -37,9 +37,14 @@ extern crate tls_rustls as rustls;
use std::{fmt, net, thread, time::Duration};
use actix_codec::{AsyncRead, AsyncWrite, Framed};
pub use actix_http::test::TestBuffer;
pub use actix_http::{body::to_bytes, test::TestBuffer};
use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
pub use actix_http_test::unused_addr;
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
pub use actix_web::test::{
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
read_body, read_body_json, simple_service, TestRequest,
};
use actix_web::{
body::MessageBody,
dev::{AppConfig, Server, ServerHandle, Service},
@@ -48,12 +53,6 @@ use actix_web::{
};
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
use futures_core::Stream;
pub use actix_http_test::unused_addr;
pub use actix_web::test::{
call_service, default_service, init_service, load_stream, ok_service, read_body,
read_body_json, read_response, read_response_json, TestRequest,
};
use tokio::sync::mpsc;
/// Start default [`TestServer`].
@@ -341,7 +340,7 @@ where
Connector::new()
.conn_lifetime(Duration::from_secs(0))
.timeout(Duration::from_millis(30000))
.ssl(builder.build())
.openssl(builder.build())
}
#[cfg(not(feature = "openssl"))]
{

View File

@@ -1,6 +1,9 @@
# 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.7"
version = "4.0.0-beta.8"
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.14"
actix-web = { version = "4.0.0-beta.11", default-features = false }
actix-http = "3.0.0-beta.16"
actix-web = { version = "4.0.0-beta.15", 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.7"
actix-test = "0.1.0-beta.9"
awc = { version = "3.0.0-beta.14", default-features = false }
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.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7)
[![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)
[![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.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
[![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)
[![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,6 +3,10 @@
## 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.5"
version = "0.5.0-beta.6"
description = "Routing and runtime macros for Actix Web"
homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git"
@@ -18,14 +18,14 @@ proc-macro = true
quote = "1"
syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1"
actix-router = "0.5.0-beta.2"
actix-router = "0.5.0-beta.3"
[dev-dependencies]
actix-rt = "2.2"
actix-macros = "0.2.3"
actix-test = "0.1.0-beta.7"
actix-rt = "2.2"
actix-test = "0.1.0-beta.9"
actix-utils = "3.0.0"
actix-web = "4.0.0-beta.11"
actix-web = "4.0.0-beta.15"
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.5)](https://docs.rs/actix-web-codegen/0.5.0-beta.5)
[![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)
[![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.5/status.svg)](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5)
[![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)
[![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,6 +1,20 @@
# Changes
## Unreleased - 2021-xx-xx
* Rename `Connector::{ssl => openssl}`. [#2503]
* Improve `Client` instantiation efficiency when using `openssl` by only building connectors once. [#2503]
[#2503]: https://github.com/actix/actix-web/pull/2503
## 3.0.0-beta.14 - 2021-12-17
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
## 3.0.0-beta.13 - 2021-12-11
* No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2021-11-30
@@ -56,7 +70,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 `IntoHeaderPair` tuples. [#2094]
methods now take `TryIntoHeaderPair` 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.12"
version = "3.0.0-beta.14"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
@@ -60,9 +60,9 @@ dangerous-h2c = []
[dependencies]
actix-codec = "0.4.1"
actix-service = "2.0.0"
actix-http = "3.0.0-beta.14"
actix-http = "3.0.0-beta.16"
actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
actix-tls = { version = "3.0.0-rc.2", features = ["connect", "uri"] }
actix-utils = "3.0.0"
ahash = "0.7"
@@ -70,9 +70,9 @@ base64 = "0.13"
bytes = "1"
cfg-if = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
h2 = "0.3"
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"
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-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-http = { version = "3.0.0-beta.16", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.1"
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", features = ["openssl"] }
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.12)](https://docs.rs/awc/3.0.0-beta.12)
[![Documentation](https://docs.rs/awc/badge.svg?version=3.0.0-beta.14)](https://docs.rs/awc/3.0.0-beta.14)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/awc)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.12/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.12)
[![Dependency Status](https://deps.rs/crate/awc/3.0.0-beta.14/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.14)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources

View File

@@ -45,9 +45,7 @@ impl AnyBody {
where
B: MessageBody + 'static,
{
Self::Body {
body: BoxBody::new(body),
}
Self::Body { body: body.boxed() }
}
/// Constructs new `AnyBody` instance from a slice of bytes by copying it.

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},
header::{self, HeaderMap, HeaderName, TryIntoHeaderPair},
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>,
headers: HeaderMap,
fundamental_headers: bool,
default_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,30 +153,46 @@ where
self
}
/// Do not add default request headers.
/// Do not add fundamental default request headers.
///
/// By default `Date` and `User-Agent` headers are set.
pub fn no_default_headers(mut self) -> Self {
self.default_headers = false;
self.fundamental_headers = false;
self
}
/// Add default header. Headers added by this method
/// get added to every request.
/// 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))`.")]
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::IntoHeaderValue,
V: header::TryIntoHeaderValue,
V::Error: fmt::Debug,
{
match HeaderName::try_from(key) {
Ok(key) => match value.try_into_value() {
Ok(value) => {
self.headers.append(key, value);
self.default_headers.append(key, value);
}
Err(e) => log::error!("Header value error: {:?}", e),
Err(err) => log::error!("Header value error: {:?}", err),
},
Err(e) => log::error!("Header name error: {:?}", e),
Err(err) => log::error!("Header name error: {:?}", err),
}
self
}
@@ -190,10 +206,10 @@ where
Some(password) => format!("{}:{}", username, password),
None => format!("{}:", username),
};
self.header(
self.add_default_header((
header::AUTHORIZATION,
format!("Basic {}", base64::encode(&auth)),
)
))
}
/// Set client wide HTTP bearer authentication header
@@ -201,13 +217,12 @@ where
where
T: fmt::Display,
{
self.header(header::AUTHORIZATION, format!("Bearer {}", token))
self.add_default_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,
@@ -218,11 +233,11 @@ where
{
ClientBuilder {
middleware: NestTransform::new(self.middleware, mw),
default_headers: self.default_headers,
fundamental_headers: self.fundamental_headers,
max_http_version: self.max_http_version,
stream_window_size: self.stream_window_size,
conn_window_size: self.conn_window_size,
headers: self.headers,
default_headers: self.default_headers,
timeout: self.timeout,
connector: self.connector,
local_address: self.local_address,
@@ -237,10 +252,10 @@ where
M::Transform:
Service<ConnectRequest, Response = ConnectResponse, Error = SendRequestError>,
{
let redirect_time = self.max_redirects;
let max_redirects = self.max_redirects;
if redirect_time > 0 {
self.wrap(Redirect::new().max_redirect_times(redirect_time))
if max_redirects > 0 {
self.wrap(Redirect::new().max_redirect_times(max_redirects))
._finish()
} else {
self._finish()
@@ -272,7 +287,7 @@ where
let connector = boxed::rc_service(self.middleware.new_transform(connector));
Client(ClientConfig {
headers: Rc::new(self.headers),
default_headers: Rc::new(self.default_headers),
timeout: self.timeout,
connector,
})
@@ -288,7 +303,7 @@ mod tests {
let client = ClientBuilder::new().basic_auth("username", Some("password"));
assert_eq!(
client
.headers
.default_headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
@@ -299,7 +314,7 @@ mod tests {
let client = ClientBuilder::new().basic_auth("username", None);
assert_eq!(
client
.headers
.default_headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()
@@ -313,7 +328,7 @@ mod tests {
let client = ClientBuilder::new().bearer_auth("someS3cr3tAutht0k3n");
assert_eq!(
client
.headers
.default_headers
.get(header::AUTHORIZATION)
.unwrap()
.to_str()

View File

@@ -22,11 +22,13 @@ use futures_core::{future::LocalBoxFuture, ready};
use http::Uri;
use pin_project_lite::pin_project;
use super::config::ConnectorConfig;
use super::connection::{Connection, ConnectionIo};
use super::error::ConnectError;
use super::pool::ConnectionPool;
use super::Connect;
use super::{
config::ConnectorConfig,
connection::{Connection, ConnectionIo},
error::ConnectError,
pool::ConnectionPool,
Connect,
};
enum OurTlsConnector {
#[allow(dead_code)] // only dead when no TLS feature is enabled
@@ -35,6 +37,12 @@ enum OurTlsConnector {
#[cfg(feature = "openssl")]
Openssl(actix_tls::connect::openssl::reexports::SslConnector),
/// Provided because building the OpenSSL context on newer versions can be very slow.
/// This prevents unnecessary calls to `.build()` while constructing the client connector.
#[cfg(feature = "openssl")]
#[allow(dead_code)] // false positive; used in build_ssl
OpensslBuilder(actix_tls::connect::openssl::reexports::SslConnectorBuilder),
#[cfg(feature = "rustls")]
Rustls(std::sync::Arc<actix_tls::connect::rustls::reexports::ClientConfig>),
}
@@ -57,7 +65,7 @@ pub struct Connector<T> {
config: ConnectorConfig,
#[allow(dead_code)] // only dead when no TLS feature is enabled
ssl: OurTlsConnector,
tls: OurTlsConnector,
}
impl Connector<()> {
@@ -72,7 +80,7 @@ impl Connector<()> {
Connector {
connector: TcpConnector::new(resolver::resolver()).service(),
config: ConnectorConfig::default(),
ssl: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
tls: Self::build_ssl(vec![b"h2".to_vec(), b"http/1.1".to_vec()]),
}
}
@@ -116,7 +124,7 @@ impl Connector<()> {
log::error!("Can not set ALPN protocol: {:?}", err);
}
OurTlsConnector::Openssl(ssl.build())
OurTlsConnector::OpensslBuilder(ssl)
}
}
@@ -134,7 +142,7 @@ impl<S> Connector<S> {
Connector {
connector,
config: self.config,
ssl: self.ssl,
tls: self.tls,
}
}
}
@@ -167,23 +175,35 @@ where
self
}
/// Use custom OpenSSL `SslConnector` instance.
#[cfg(feature = "openssl")]
/// Use custom `SslConnector` instance.
pub fn openssl(
mut self,
connector: actix_tls::connect::openssl::reexports::SslConnector,
) -> Self {
self.tls = OurTlsConnector::Openssl(connector);
self
}
/// See docs for [`Connector::openssl`].
#[doc(hidden)]
#[cfg(feature = "openssl")]
#[deprecated(since = "3.0.0", note = "Renamed to `Connector::openssl`.")]
pub fn ssl(
mut self,
connector: actix_tls::connect::openssl::reexports::SslConnector,
) -> Self {
self.ssl = OurTlsConnector::Openssl(connector);
self.tls = OurTlsConnector::Openssl(connector);
self
}
/// Use custom Rustls `ClientConfig` instance.
#[cfg(feature = "rustls")]
/// Use custom `ClientConfig` instance.
pub fn rustls(
mut self,
connector: std::sync::Arc<actix_tls::connect::rustls::reexports::ClientConfig>,
) -> Self {
self.ssl = OurTlsConnector::Rustls(connector);
self.tls = OurTlsConnector::Rustls(connector);
self
}
@@ -198,7 +218,7 @@ where
unimplemented!("actix-http client only supports versions http/1.1 & http/2")
}
};
self.ssl = Connector::build_ssl(versions);
self.tls = Connector::build_ssl(versions);
self
}
@@ -270,8 +290,8 @@ where
}
/// Finish configuration process and create connector service.
/// The Connector builder always concludes by calling `finish()` last in
/// its combinator chain.
///
/// The `Connector` builder always concludes by calling `finish()` last in its combinator chain.
pub fn finish(self) -> ConnectorService<S, IO> {
let local_address = self.config.local_address;
let timeout = self.config.timeout;
@@ -284,7 +304,15 @@ where
service: tcp_service_inner.clone(),
};
let tls_service = match self.ssl {
let tls = match self.tls {
#[cfg(feature = "openssl")]
OurTlsConnector::OpensslBuilder(builder) => {
OurTlsConnector::Openssl(builder.build())
}
tls => tls,
};
let tls_service = match tls {
OurTlsConnector::None => {
#[cfg(not(feature = "dangerous-h2c"))]
{
@@ -374,6 +402,11 @@ where
Some(actix_service::boxed::rc_service(tls_service))
}
#[cfg(feature = "openssl")]
OurTlsConnector::OpensslBuilder(_) => {
unreachable!("OpenSSL builder is built before this match.");
}
#[cfg(feature = "rustls")]
OurTlsConnector::Rustls(tls) => {
const H2: &[u8] = b"h2";
@@ -853,7 +886,7 @@ mod tests {
let connector = Connector {
connector: TcpConnector::new(resolver::resolver()).service(),
config: ConnectorConfig::default(),
ssl: OurTlsConnector::None,
tls: OurTlsConnector::None,
};
let client = Client::builder().connector(connector).finish();

View File

@@ -9,7 +9,7 @@ use actix_http::{
body::{BodySize, MessageBody},
error::PayloadError,
h1,
header::{HeaderMap, IntoHeaderValue, EXPECT, HOST},
header::{HeaderMap, TryIntoHeaderValue, 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, IntoHeaderValue},
header::{HeaderMap, HeaderName, TryIntoHeaderValue},
Method, RequestHead, Uri,
};
@@ -114,7 +114,7 @@ impl FrozenClientRequest {
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
V: IntoHeaderValue,
V: TryIntoHeaderValue,
{
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: IntoHeaderValue,
V: TryIntoHeaderValue,
{
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) headers: Rc<HeaderMap>,
pub(crate) default_headers: Rc<HeaderMap>,
pub(crate) timeout: Option<Duration>,
}
@@ -204,7 +204,9 @@ impl Client {
{
let mut req = ClientRequest::new(method, url, self.0.clone());
for header in self.0.headers.iter() {
for header in self.0.default_headers.iter() {
// header map is empty
// TODO: probably append instead
req = req.insert_header_if_none(header);
}
req
@@ -297,7 +299,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.headers.iter() {
for (key, value) in self.0.default_headers.iter() {
req.head.headers.insert(key.clone(), value.clone());
}
req
@@ -308,6 +310,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.headers)
Rc::get_mut(&mut self.0.default_headers)
}
}

View File

@@ -442,13 +442,15 @@ mod tests {
});
let client = ClientBuilder::new()
.header("custom", "value")
.add_default_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().header("custom", "value").finish();
let client = ClientBuilder::new()
.add_default_header(("custom", "value"))
.finish();
let res = client.get(srv.url("/")).send().await.unwrap();
assert_eq!(res.status().as_u16(), 200);
@@ -520,7 +522,7 @@ mod tests {
// send a request to different origins, http://srv1/ then http://srv2/. So it should remove the header
let client = ClientBuilder::new()
.header(header::AUTHORIZATION, "auth_key_value")
.add_default_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, IntoHeaderPair},
header::{self, HeaderMap, HeaderValue, TryIntoHeaderPair},
ConnectionType, Method, RequestHead, Uri, Version,
};
@@ -147,11 +147,8 @@ impl ClientRequest {
}
/// Insert a header, replacing any that were set with an equivalent field name.
pub fn insert_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
match header.try_into_pair() {
Ok((key, value)) => {
self.head.headers.insert(key, value);
}
@@ -162,11 +159,8 @@ impl ClientRequest {
}
/// Insert a header only if it is not yet set.
pub fn insert_header_if_none<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
pub fn insert_header_if_none(mut self, header: impl TryIntoHeaderPair) -> Self {
match header.try_into_pair() {
Ok((key, value)) => {
if !self.head.headers.contains_key(&key) {
self.head.headers.insert(key, value);
@@ -192,11 +186,8 @@ impl ClientRequest {
/// .insert_header((CONTENT_TYPE, mime::APPLICATION_JSON));
/// # }
/// ```
pub fn append_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
match header.try_into_header_pair() {
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
match header.try_into_pair() {
Ok((key, value)) => self.head.headers.append(key, value),
Err(e) => self.err = Some(e.into()),
};
@@ -588,7 +579,7 @@ mod tests {
#[actix_rt::test]
async fn test_client_header() {
let req = Client::builder()
.header(header::CONTENT_TYPE, "111")
.add_default_header((header::CONTENT_TYPE, "111"))
.finish()
.get("/");
@@ -606,7 +597,7 @@ mod tests {
#[actix_rt::test]
async fn test_client_header_override() {
let req = Client::builder()
.header(header::CONTENT_TYPE, "111")
.add_default_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, IntoHeaderValue},
header::{self, HeaderMap, HeaderName, TryIntoHeaderValue},
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: IntoHeaderValue,
V: TryIntoHeaderValue,
{
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::IntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
use actix_http::{h1, header::TryIntoHeaderPair, Payload, ResponseHead, StatusCode, Version};
use bytes::Bytes;
#[cfg(feature = "cookies")]
@@ -28,10 +28,7 @@ impl Default for TestResponse {
impl TestResponse {
/// Create TestResponse and set header
pub fn with_header<H>(header: H) -> Self
where
H: IntoHeaderPair,
{
pub fn with_header(header: impl TryIntoHeaderPair) -> Self {
Self::default().insert_header(header)
}
@@ -42,11 +39,8 @@ impl TestResponse {
}
/// Insert a header
pub fn insert_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
if let Ok((key, value)) = header.try_into_header_pair() {
pub fn insert_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Ok((key, value)) = header.try_into_pair() {
self.head.headers.insert(key, value);
return self;
}
@@ -54,11 +48,8 @@ impl TestResponse {
}
/// Append a header
pub fn append_header<H>(mut self, header: H) -> Self
where
H: IntoHeaderPair,
{
if let Ok((key, value)) = header.try_into_header_pair() {
pub fn append_header(mut self, header: impl TryIntoHeaderPair) -> Self {
if let Ok((key, value)) = header.try_into_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, IntoHeaderValue, AUTHORIZATION},
header::{self, HeaderName, HeaderValue, TryIntoHeaderValue, 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: IntoHeaderValue,
V: TryIntoHeaderValue,
{
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: IntoHeaderValue,
V: TryIntoHeaderValue,
{
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: IntoHeaderValue,
V: TryIntoHeaderValue,
{
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()
.header(header::CONTENT_TYPE, "111")
.add_default_header((header::CONTENT_TYPE, "111"))
.finish()
.ws("/")
.set_header(header::CONTENT_TYPE, "222");

View File

@@ -58,7 +58,7 @@ async fn test_connection_window_size() {
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
let client = awc::Client::builder()
.connector(awc::Connector::new().ssl(builder.build()))
.connector(awc::Connector::new().openssl(builder.build()))
.initial_window_size(100)
.initial_connection_window_size(100)
.finish();

View File

@@ -72,7 +72,7 @@ async fn test_connection_reuse_h2() {
.map_err(|e| log::error!("Can not set alpn protocol: {:?}", e));
let client = awc::Client::builder()
.connector(awc::Connector::new().ssl(builder.build()))
.connector(awc::Connector::new().openssl(builder.build()))
.finish();
// req 1

View File

@@ -22,14 +22,14 @@ async fn main() -> std::io::Result<()> {
HttpServer::new(|| {
App::new()
.wrap(middleware::DefaultHeaders::new().header("X-Version", "0.2"))
.wrap(middleware::DefaultHeaders::new().add(("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().header("X-Version-R2", "0.3"))
.wrap(middleware::DefaultHeaders::new().add(("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().header("X-Version", "0.2"))
.wrap(middleware::DefaultHeaders::new().add(("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().header("X-Version-R2", "0.3"))
.wrap(middleware::DefaultHeaders::new().add(("X-Version-R2", "0.3")))
.default_service(web::route().to(HttpResponse::MethodNotAllowed))
.route(web::get().to(index_async)),
)

View File

@@ -41,6 +41,8 @@ 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
@@ -53,6 +55,11 @@ else
read -p "Update version to: " NEW_VERSION
fi
# strip leading v from input
if [ "${NEW_VERSION:0:1}" = "v" ]; then
NEW_VERSION="${NEW_VERSION:1}"
fi
DATE="$(date -u +"%Y-%m-%d")"
echo "updating from $CURRENT_VERSION => $NEW_VERSION ($DATE)"
@@ -82,8 +89,33 @@ rm -f $README_FILE.bak
echo "manifest, changelog, and readme updated"
echo
echo "check other references:"
rg "$PACKAGE_NAME =" || true
rg "package = \"$PACKAGE_NAME\"" || true
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
if [ $MACOS ]; then
printf "prepare $PACKAGE_NAME release $NEW_VERSION" | pbcopy
@@ -97,15 +129,20 @@ SHORT_PACKAGE_NAME="$(echo $PACKAGE_NAME | sed 's/^actix-web-//' | sed 's/^actix
GIT_TAG="$(echo $SHORT_PACKAGE_NAME-v$NEW_VERSION)"
RELEASE_TITLE="$(echo $PACKAGE_NAME: v$NEW_VERSION)"
if [ "$(echo $NEW_VERSION | grep beta)" ] || [ "$(echo $NEW_VERSION | grep rc)" ] || [ "$(echo $NEW_VERSION | grep alpha)" ]; then
PRERELEASE="--prerelease"
fi
echo
echo "GitHub release command:"
echo "gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" --prerelease"
GH_CMD="gh release create \"$GIT_TAG\" --draft --title \"$RELEASE_TITLE\" --notes-file \"$CHANGE_CHUNK_FILE\" ${PRERELEASE:-}"
echo "$GH_CMD"
read -p "Submit draft GH release: (y/N) " GH_RELEASE
GH_RELEASE="${GH_RELEASE:-n}"
if [ "$GH_RELEASE" = 'y' ] || [ "$GH_RELEASE" = 'Y' ]; then
gh release create "$GIT_TAG" --draft --title "$RELEASE_TITLE" --notes-file "$CHANGE_CHUNK_FILE" --prerelease
eval "$GH_CMD"
fi
echo

View File

@@ -4,15 +4,25 @@
set -x
cargo test --lib --tests -p=actix-router --all-features
cargo test --lib --tests -p=actix-http --all-features
cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
cargo test --lib --tests -p=actix-web-codegen --all-features
cargo test --lib --tests -p=awc --all-features
cargo test --lib --tests -p=actix-http-test --all-features
cargo test --lib --tests -p=actix-test --all-features
cargo test --lib --tests -p=actix-files
cargo test --lib --tests -p=actix-multipart --all-features
cargo test --lib --tests -p=actix-web-actors --all-features
EXIT=0
cargo test --workspace --doc
save_exit_code() {
eval $@
local CMD_EXIT=$?
[ "$CMD_EXIT" = "0" ] || EXIT=$CMD_EXIT
}
save_exit_code cargo test --lib --tests -p=actix-router --all-features
save_exit_code cargo test --lib --tests -p=actix-http --all-features
save_exit_code cargo test --lib --tests -p=actix-web --features=rustls,openssl -- --skip=test_reading_deflate_encoding_large_random_rustls
save_exit_code cargo test --lib --tests -p=actix-web-codegen --all-features
save_exit_code cargo test --lib --tests -p=awc --all-features
save_exit_code cargo test --lib --tests -p=actix-http-test --all-features
save_exit_code cargo test --lib --tests -p=actix-test --all-features
save_exit_code cargo test --lib --tests -p=actix-files
save_exit_code cargo test --lib --tests -p=actix-multipart --all-features
save_exit_code cargo test --lib --tests -p=actix-web-actors --all-features
save_exit_code cargo test --workspace --doc
exit $EXIT

41
scripts/unreleased Executable file
View File

@@ -0,0 +1,41 @@
#!/bin/sh
set -euo pipefail
bold="\033[1m"
reset="\033[0m"
unreleased_for() {
DIR=$1
CARGO_MANIFEST=$DIR/Cargo.toml
CHANGELOG_FILE=$DIR/CHANGES.md
# get current version
PACKAGE_NAME="$(sed -nE 's/^name ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST" | head -n 1)"
CURRENT_VERSION="$(sed -nE 's/^version ?= ?"([^"]+)"$/\1/ p' "$CARGO_MANIFEST")"
CHANGE_CHUNK_FILE="$(mktemp)"
# get changelog chunk and save to temp file
cat "$CHANGELOG_FILE" |
# skip up to unreleased heading
sed '1,/Unreleased/ d' |
# take up to previous version heading
sed "/$CURRENT_VERSION/ q" |
# drop last line
sed '$d' \
>"$CHANGE_CHUNK_FILE"
# if word count of changelog chunk is 0 then exit
if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then
return 0;
fi
echo "${bold}# ${PACKAGE_NAME}${reset} since ${bold}v$CURRENT_VERSION${reset}"
cat "$CHANGE_CHUNK_FILE"
}
for f in $(fd --absolute-path CHANGES.md); do
unreleased_for $(dirname $f)
done

View File

@@ -486,19 +486,21 @@ where
#[cfg(test)]
mod tests {
use actix_service::Service;
use actix_service::Service as _;
use actix_utils::future::{err, ok};
use bytes::Bytes;
use super::*;
use crate::http::{
header::{self, HeaderValue},
Method, StatusCode,
use crate::{
http::{
header::{self, HeaderValue},
Method, StatusCode,
},
middleware::DefaultHeaders,
service::ServiceRequest,
test::{call_service, init_service, read_body, try_init_service, TestRequest},
web, HttpRequest, HttpResponse,
};
use crate::middleware::DefaultHeaders;
use crate::service::ServiceRequest;
use crate::test::{call_service, init_service, read_body, try_init_service, TestRequest};
use crate::{web, HttpRequest, HttpResponse};
#[actix_rt::test]
async fn test_default_resource() {
@@ -602,7 +604,7 @@ mod tests {
App::new()
.wrap(
DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
)
.route("/test", web::get().to(HttpResponse::Ok)),
)
@@ -623,7 +625,7 @@ mod tests {
.route("/test", web::get().to(HttpResponse::Ok))
.wrap(
DefaultHeaders::new()
.header(header::CONTENT_TYPE, HeaderValue::from_static("0001")),
.add((header::CONTENT_TYPE, HeaderValue::from_static("0001"))),
),
)
.await;
@@ -706,4 +708,25 @@ 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,6 +22,7 @@ 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
@@ -198,6 +199,7 @@ where
actix_service::forward_ready!(service);
fn call(&self, mut req: Request) -> Self::Future {
let req_data = Rc::new(RefCell::new(req.take_req_data()));
let conn_data = req.take_conn_data();
let (head, payload) = req.into_parts();
@@ -207,6 +209,7 @@ where
inner.path.reset();
inner.head = head;
inner.conn_data = conn_data;
inner.req_data = req_data;
req
} else {
HttpRequest::new(
@@ -215,6 +218,7 @@ where
self.app_state.clone(),
self.app_data.clone(),
conn_data,
req_data,
)
};
self.service.call(ServiceRequest::new(req, payload))

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::{cell::RefCell, fmt, io::Write as _};
use actix_http::{
body::BoxBody,
header::{self, IntoHeaderValue as _},
header::{self, TryIntoHeaderValue as _},
StatusCode,
};
use bytes::{BufMut as _, BytesMut};

View File

@@ -1,4 +1,5 @@
//! 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,7 +8,7 @@ use std::{
use actix_http::{
body::BoxBody,
header::{self, IntoHeaderValue},
header::{self, TryIntoHeaderValue},
Response, StatusCode,
};
use bytes::BytesMut;

View File

@@ -14,7 +14,7 @@ use once_cell::sync::Lazy;
use regex::Regex;
use std::fmt::{self, Write};
use super::{ExtendedValue, Header, IntoHeaderValue, Writer};
use super::{ExtendedValue, Header, TryIntoHeaderValue, 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 IntoHeaderValue for ContentDisposition {
impl TryIntoHeaderValue 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, IntoHeaderValue, InvalidHeaderValue, Writer, CONTENT_RANGE};
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer, CONTENT_RANGE};
use crate::error::ParseError;
crate::http::header::common_header! {
@@ -196,7 +196,7 @@ impl Display for ContentRangeSpec {
}
}
impl IntoHeaderValue for ContentRangeSpec {
impl TryIntoHeaderValue 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, IntoHeaderValue, InvalidHeaderValue, Writer};
use super::{HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, Writer};
/// check that each char in the slice is either:
/// 1. `%x21`, or
@@ -159,7 +159,7 @@ impl FromStr for EntityTag {
}
}
impl IntoHeaderValue for EntityTag {
impl TryIntoHeaderValue 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, IntoHeaderValue,
InvalidHeaderValue, Writer,
from_one_raw_str, EntityTag, Header, HeaderName, HeaderValue, HttpDate, InvalidHeaderValue,
TryIntoHeaderValue, Writer,
};
use crate::error::ParseError;
use crate::http::header;
@@ -96,7 +96,7 @@ impl Display for IfRange {
}
}
impl IntoHeaderValue for IfRange {
impl TryIntoHeaderValue 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::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]
@@ -172,7 +172,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]
@@ -211,7 +211,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue for $id {
type Error = $crate::http::header::InvalidHeaderValue;
#[inline]
@@ -266,7 +266,7 @@ macro_rules! common_header {
}
}
impl $crate::http::header::IntoHeaderValue for $id {
impl $crate::http::header::TryIntoHeaderValue 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, IntoHeaderValue, InvalidHeaderValue, Writer};
use super::{Header, HeaderName, HeaderValue, InvalidHeaderValue, TryIntoHeaderValue, 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 IntoHeaderValue for Range {
impl TryIntoHeaderValue for Range {
type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

@@ -1,4 +1,4 @@
use std::{cell::Ref, convert::Infallible, net::SocketAddr};
use std::{convert::Infallible, net::SocketAddr};
use actix_utils::future::{err, ok, Ready};
use derive_more::{Display, Error};
@@ -72,15 +72,7 @@ pub struct ConnectionInfo {
}
impl ConnectionInfo {
/// Create *ConnectionInfo* instance for a request.
pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> {
if !req.extensions().contains::<ConnectionInfo>() {
req.extensions_mut().insert(ConnectionInfo::new(req, cfg));
}
Ref::map(req.extensions(), |e| e.get().unwrap())
}
fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
let mut host = None;
let mut scheme = None;
let mut realip_remote_addr = None;

View File

@@ -86,7 +86,6 @@ pub mod middleware;
mod request;
mod request_data;
mod resource;
mod responder;
mod response;
mod rmap;
mod route;
@@ -109,12 +108,10 @@ 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::responder::Responder;
pub use crate::response::{HttpResponse, HttpResponseBuilder};
pub use crate::response::{CustomizeResponder, HttpResponse, HttpResponseBuilder, Responder};
pub use crate::route::Route;
pub use crate::scope::Scope;
pub use crate::server::HttpServer;
// TODO: is exposing the error directly really needed
pub use crate::types::{Either, EitherExtractError};
pub use crate::types::Either;
pub(crate) type BoxError = Box<dyn std::error::Error>;

View File

@@ -6,12 +6,15 @@ use std::{
task::{Context, Poll},
};
use actix_http::body::MessageBody;
use actix_service::{Service, Transform};
use futures_core::{future::LocalBoxFuture, ready};
use pin_project_lite::pin_project;
use crate::{error::Error, service::ServiceResponse};
use crate::{
body::{BoxBody, MessageBody},
dev::{Service, Transform},
error::Error,
service::ServiceResponse,
};
/// Middleware for enabling any middleware to be used in [`Resource::wrap`](crate::Resource::wrap),
/// [`Scope::wrap`](crate::Scope::wrap) and [`Condition`](super::Condition).
@@ -52,7 +55,7 @@ where
T::Response: MapServiceResponseBody,
T::Error: Into<Error>,
{
type Response = ServiceResponse;
type Response = ServiceResponse<BoxBody>;
type Error = Error;
type Transform = CompatMiddleware<T::Transform>;
type InitError = T::InitError;
@@ -77,7 +80,7 @@ where
S::Response: MapServiceResponseBody,
S::Error: Into<Error>,
{
type Response = ServiceResponse;
type Response = ServiceResponse<BoxBody>;
type Error = Error;
type Future = CompatMiddlewareFuture<S::Future>;
@@ -102,7 +105,7 @@ where
T: MapServiceResponseBody,
E: Into<Error>,
{
type Output = Result<ServiceResponse, Error>;
type Output = Result<ServiceResponse<BoxBody>, Error>;
fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
let res = match ready!(self.project().fut.poll(cx)) {
@@ -116,14 +119,15 @@ where
/// Convert `ServiceResponse`'s `ResponseBody<B>` generic type to `ResponseBody<Body>`.
pub trait MapServiceResponseBody {
fn map_body(self) -> ServiceResponse;
fn map_body(self) -> ServiceResponse<BoxBody>;
}
impl<B> MapServiceResponseBody for ServiceResponse<B>
where
B: MessageBody + Unpin + 'static,
B: MessageBody + 'static,
{
fn map_body(self) -> ServiceResponse {
#[inline]
fn map_body(self) -> ServiceResponse<BoxBody> {
self.map_into_boxed_body()
}
}

View File

@@ -106,7 +106,7 @@ mod tests {
header::{HeaderValue, CONTENT_TYPE},
StatusCode,
},
middleware::err_handlers::*,
middleware::{err_handlers::*, Compat},
test::{self, TestRequest},
HttpResponse,
};
@@ -116,7 +116,8 @@ mod tests {
res.response_mut()
.headers_mut()
.insert(CONTENT_TYPE, HeaderValue::from_static("0001"));
Ok(ErrorHandlerResponse::Response(res))
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}
#[actix_rt::test]
@@ -125,7 +126,9 @@ mod tests {
ok(req.into_response(HttpResponse::InternalServerError().finish()))
};
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mw = Compat::new(
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
);
let mw = Condition::new(true, mw)
.new_transform(srv.into_service())
@@ -141,7 +144,9 @@ mod tests {
ok(req.into_response(HttpResponse::InternalServerError().finish()))
};
let mw = ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500);
let mw = Compat::new(
ErrorHandlers::new().handler(StatusCode::INTERNAL_SERVER_ERROR, render_500),
);
let mw = Condition::new(false, mw)
.new_transform(srv.into_service())

View File

@@ -16,7 +16,7 @@ use pin_project_lite::pin_project;
use crate::{
dev::{Service, Transform},
http::header::{HeaderMap, HeaderName, HeaderValue, CONTENT_TYPE},
http::header::{HeaderMap, HeaderName, HeaderValue, TryIntoHeaderPair, CONTENT_TYPE},
service::{ServiceRequest, ServiceResponse},
Error,
};
@@ -29,79 +29,81 @@ use crate::{
/// ```
/// use actix_web::{web, http, middleware, App, HttpResponse};
///
/// 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()))
/// );
/// }
/// 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()))
/// );
/// ```
#[derive(Clone)]
#[derive(Debug, Clone, Default)]
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.
#[inline]
pub fn header<K, V>(mut self, key: K, value: V) -> Self
///
/// # 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
where
HeaderName: TryFrom<K>,
<HeaderName as TryFrom<K>>::Error: Into<HttpError>,
HeaderValue: TryFrom<V>,
<HeaderValue as TryFrom<V>>::Error: Into<HttpError>,
{
#[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
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"),
))
}
/// Adds a default *Content-Type* header if response does not contain one.
///
/// Default is `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
pub fn add_content_type(self) -> Self {
self.add((
CONTENT_TYPE,
HeaderValue::from_static("application/octet-stream"),
))
}
}
@@ -119,7 +121,7 @@ where
fn new_transform(&self, service: S) -> Self::Future {
ready(Ok(DefaultHeadersMiddleware {
service,
inner: self.inner.clone(),
inner: Rc::clone(&self.inner),
}))
}
}
@@ -192,22 +194,27 @@ mod tests {
use crate::{
dev::ServiceRequest,
http::header::CONTENT_TYPE,
test::{ok_service, TestRequest},
test::{self, TestRequest},
HttpResponse,
};
#[actix_rt::test]
async fn test_default_headers() {
async fn adding_default_headers() {
let mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.new_transform(ok_service())
.add(("X-TEST", "0001"))
.add(("X-TEST-TWO", HeaderValue::from_static("123")))
.new_transform(test::ok_service())
.await
.unwrap();
let req = TestRequest::default().to_srv_request();
let resp = mw.call(req).await.unwrap();
assert_eq!(resp.headers().get(CONTENT_TYPE).unwrap(), "0001");
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");
}
#[actix_rt::test]
async fn no_override_existing() {
let req = TestRequest::default().to_srv_request();
let srv = |req: ServiceRequest| {
ok(req.into_response(
@@ -217,7 +224,7 @@ mod tests {
))
};
let mw = DefaultHeaders::new()
.header(CONTENT_TYPE, "0001")
.add((CONTENT_TYPE, "0001"))
.new_transform(srv.into_service())
.await
.unwrap();
@@ -226,11 +233,10 @@ mod tests {
}
#[actix_rt::test]
async fn test_content_type() {
let srv = |req: ServiceRequest| ok(req.into_response(HttpResponse::Ok().finish()));
async fn adding_content_type() {
let mw = DefaultHeaders::new()
.add_content_type()
.new_transform(srv.into_service())
.new_transform(test::ok_service())
.await
.unwrap();
@@ -241,4 +247,16 @@ 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"));
}
}

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