1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-07-24 17:55:27 +02:00

Compare commits

..

30 Commits

Author SHA1 Message Date
Rob Ede
2ee953a118 remove either crate conversions 2021-12-14 19:01:14 +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
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
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
Rob Ede
07f2fe385b standardize crate level lints 2021-12-08 06:09:56 +00:00
Rob Ede
406f694095 standardize rustfmt max_width 2021-12-08 06:01:11 +00:00
Rob Ede
e49e559f47 fix some docs 2021-12-08 05:43:50 +00:00
Rob Ede
d35b7644dc add connection level data container (#2491) 2021-12-07 17:23:34 +00:00
fakeshadow
069cf2da07 enable scope middleware with generic res body. (#2492)
Co-authored-by: Rob Ede <robjtede@icloud.com>
2021-12-07 16:26:28 +00:00
fakeshadow
6460e67f84 remove generic body type in App. (#2493) 2021-12-07 15:53:04 +00:00
Rob Ede
9587261c20 add fakeshadow's actix-web in actix-http example 2021-12-07 15:31:15 +00:00
Rob Ede
606a371ec3 improve Data docs 2021-12-06 17:14:56 +00:00
142 changed files with 1900 additions and 1554 deletions

View File

@@ -2,35 +2,59 @@
## Unreleased - 2021-xx-xx
### Added
* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510]
* Implement `Debug` for `DefaultHeaders`. [#2510]
### Changed
* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510]
### Removed
* Top-level `EitherExtractError` export. [#2510]
* Conversion implementations for `either` crate. [#2516]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2516]: https://github.com/actix/actix-web/pull/2516
## 4.0.0-beta.14 - 2021-12-11
### Added
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
* `AcceptEncoding` typed header. [#2482]
* `Range` typed header. [#2485]
* `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]
* `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
[#2325]: https://github.com/actix/actix-web/pull/2325
[#2327]: https://github.com/actix/actix-web/pull/2327
* 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]
* Rename `Accept::{mime_preference => preference}`. [#2480]
* Un-deprecate `App::data_factory`. [#2484]
* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430]
* `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
* 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]
[#2327]: https://github.com/actix/actix-web/pull/2327
### 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.14"
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-http = "3.0.0-beta.15"
actix-router = "0.5.0-beta.2"
actix-web-codegen = "0.5.0-beta.5"
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.8", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.13", 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.14)](https://docs.rs/actix-web/4.0.0-beta.14)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
![MIT or Apache 2.0 licensed](https://img.shields.io/crates/l/actix-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.14/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.14)
<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.15"
actix-service = "2"
actix-utils = "3"
actix-web = { version = "4.0.0-beta.14", default-features = false }
askama_escape = "0.10"
bitflags = "1"
bytes = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
http-range = "0.1.4"
derive_more = "0.99.5"
log = "0.4"
mime = "0.3"
mime_guess = "2.0.1"
@@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true }
[dev-dependencies]
actix-rt = "2.2"
actix-web = "4.0.0-beta.11"
actix-test = "0.1.0-beta.7"
actix-test = "0.1.0-beta.8"
actix-web = "4.0.0-beta.14"

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

@@ -262,9 +262,9 @@ impl Files {
self
}
/// See [`Files::method_guard`].
#[doc(hidden)]
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
/// See [`Files::method_guard`].
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
self.method_guard(guard)
}

View File

@@ -11,8 +11,8 @@
//! .service(Files::new("/static", ".").prefer_utf8(true));
//! ```
#![deny(rust_2018_idioms)]
#![warn(missing_docs, missing_debug_implementations)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
use actix_service::boxed::{BoxService, BoxServiceFactory};
use actix_web::{

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

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

@@ -1,6 +1,7 @@
//! Various helpers for Actix applications to use during testing.
#![deny(rust_2018_idioms)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]

View File

@@ -1,6 +1,15 @@
# Changes
## Unreleased - 2021-xx-xx
### Changed
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
## 3.0.0-beta.15 - 2021-12-11
### Added
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
@@ -14,7 +23,11 @@
* `header::QualityItem::{max, min}`. [#2486]
* `header::Quality::{MAX, MIN}`. [#2486]
* `impl Display` for `header::Quality`. [#2486]
* `CloneableExtensions` object for use in `on_connect` handlers. [#2327]
* 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 optimisations on body types that are done in exactly one poll/chunk. [#2497]
### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468]
@@ -24,7 +37,6 @@
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
* `on_connect_ext` methods now receive a `CloneableExtensions` object. [#2327]
### Removed
* `ResponseBuilder::streaming`. [#2468]
@@ -36,12 +48,14 @@
* `impl TryFrom<u16>` for `header::Quality`. [#2486]
* `http` module. Most everything it contained is exported at the crate root. [#2488]
[#2327]: https://github.com/actix/actix-web/pull/2327
[#2483]: https://github.com/actix/actix-web/pull/2483
[#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
## 3.0.0-beta.14 - 2021-11-30
@@ -252,7 +266,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 +277,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.15"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"]
@@ -56,7 +56,7 @@ derive_more = "0.99.5"
encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.1"
h2 = "0.3.9"
http = "0.2.5"
httparse = "1.5.1"
httpdate = "1.0.1"
@@ -81,9 +81,11 @@ flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.9", optional = true }
[dev-dependencies]
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.1"
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
actix-web = "4.0.0-beta.14"
async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9"
@@ -95,7 +97,7 @@ serde_json = "1.0"
static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.20.0" }
tokio = { version = "1.2", features = ["net", "rt"] }
tokio = { version = "1.2", features = ["net", "rt", "macros"] }
[[example]]
name = "ws"

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

@@ -189,11 +189,7 @@ mod _original {
n /= 100;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(
lut_ptr.offset(d1 as isize),
buf_ptr.offset(curr),
2,
);
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
}
// decode last 1 or 2 chars
@@ -206,11 +202,7 @@ mod _original {
let d1 = n << 1;
curr -= 2;
unsafe {
ptr::copy_nonoverlapping(
lut_ptr.offset(d1 as isize),
buf_ptr.offset(curr),
2,
);
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
}
}

View File

@@ -54,15 +54,10 @@ const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
value: (0, 0),
};
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
[EMPTY_HEADER_INDEX; MAX_HEADERS];
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS];
impl HeaderIndex {
fn record(
bytes: &[u8],
headers: &[httparse::Header<'_>],
indices: &mut [HeaderIndex],
) {
fn record(bytes: &[u8], headers: &[httparse::Header<'_>], indices: &mut [HeaderIndex]) {
let bytes_ptr = bytes.as_ptr() as usize;
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
let name_start = header.name.as_ptr() as usize - bytes_ptr;

View File

@@ -0,0 +1,26 @@
use actix_http::HttpService;
use actix_server::Server;
use actix_service::map_config;
use actix_web::{dev::AppConfig, get, App};
#[get("/")]
async fn index() -> &'static str {
"Hello, world. From Actix Web!"
}
#[tokio::main(flavor = "current_thread")]
async fn main() -> std::io::Result<()> {
Server::build()
.bind("hello-world", "127.0.0.1:8080", || {
// construct actix-web app
let app = App::new().service(index);
HttpService::build()
// pass the app to service builder
// map_config is used to map App's configuration to ServiceBuilder
.finish(map_config(app, |_| AppConfig::default()))
.tcp()
})?
.run()
.await
}

View File

@@ -25,10 +25,7 @@ async fn main() -> io::Result<()> {
Ok::<_, Error>(
Response::build(StatusCode::OK)
.insert_header((
"x-head",
HeaderValue::from_static("dummy value!"),
))
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
.body(body),
)
})

View File

@@ -1,8 +1,7 @@
use std::io;
use actix_http::{
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response,
StatusCode,
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode,
};
use actix_server::Server;
use bytes::BytesMut;

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,13 +14,19 @@ 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-head",
HeaderValue::from_static("dummy value!"),
"x-forty-two",
HeaderValue::from_str(&forty_two).unwrap(),
));
Ok::<_, Infallible>(res.body("Hello world!"))

View File

@@ -60,10 +60,7 @@ impl Heartbeat {
impl Stream for Heartbeat {
type Item = Result<Bytes, Error>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
log::trace!("poll");
ready!(self.as_mut().interval.poll_tick(cx));

View File

@@ -1,5 +0,0 @@
max_width = 89
reorder_imports = true
#wrap_comments = true
#fn_args_density = "Compressed"
#use_small_heuristics = false

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
}
@@ -165,8 +167,7 @@ mod tests {
#[actix_rt::test]
async fn stream_delayed_error() {
let body =
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
let body = BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
pin_project! {

View File

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

View File

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

View File

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

View File

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

View File

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

@@ -68,9 +68,8 @@ mod test {
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123"[..]);
let stream =
stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
.map(Ok::<_, Error>);
let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
.map(Ok::<_, Error>);
let body = BodyStream::new(stream);
let bytes = to_bytes(body).await.unwrap();
assert_eq!(bytes, b"123abc"[..]);

View File

@@ -6,11 +6,10 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory};
use crate::{
body::{BoxBody, MessageBody},
config::{KeepAlive, ServiceConfig},
extensions::CloneableExtensions,
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
h2::H2Service,
service::HttpService,
ConnectCallback, Request, Response,
ConnectCallback, Extensions, Request, Response,
};
/// A HTTP service builder
@@ -168,7 +167,7 @@ where
/// and handlers.
pub fn on_connect_ext<F>(mut self, f: F) -> Self
where
F: Fn(&T, &mut CloneableExtensions) + 'static,
F: Fn(&T, &mut Extensions) + 'static,
{
self.on_connect_ext = Some(Rc::new(f));
self
@@ -215,8 +214,7 @@ where
self.local_addr,
);
H2Service::with_config(cfg, service.into_factory())
.on_connect_ext(self.on_connect_ext)
H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext)
}
/// Finish service configuration and create `HttpService` instance.

View File

@@ -44,17 +44,17 @@ where
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
let decoder = match encoding {
#[cfg(feature = "compress-brotli")]
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
BrotliDecoder::new(Writer::new()),
))),
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(BrotliDecoder::new(
Writer::new(),
)))),
#[cfg(feature = "compress-gzip")]
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
ZlibDecoder::new(Writer::new()),
))),
#[cfg(feature = "compress-gzip")]
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(
GzDecoder::new(Writer::new()),
))),
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
Writer::new(),
)))),
#[cfg(feature = "compress-zstd")]
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
ZstdDecoder::new(Writer::new()).expect(
@@ -93,10 +93,7 @@ where
{
type Item = Result<Bytes, PayloadError>;
fn poll_next(
mut self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
loop {
if let Some(ref mut fut) = self.fut {
let (chunk, decoder) =

View File

@@ -53,36 +53,32 @@ impl<B: MessageBody> Encoder<B> {
}
}
pub fn response(
encoding: ContentEncoding,
head: &mut ResponseHead,
body: B,
) -> Self {
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, mut body: B) -> Self {
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|| head.status == StatusCode::NO_CONTENT
|| encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto);
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 = if body.is_complete_body() {
let body = body.take_complete_body();
EncoderBody::Full { body }
} else {
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,
@@ -91,7 +87,7 @@ impl<B: MessageBody> Encoder<B> {
}
Encoder {
body: EncoderBody::Stream { body },
body,
encoder: None,
fut: None,
eof: false,
@@ -103,6 +99,7 @@ pin_project! {
#[project = EncoderBodyProj]
enum EncoderBody<B> {
None,
Full { body: Bytes },
Stream { #[pin] body: B },
}
}
@@ -116,6 +113,7 @@ where
fn size(&self) -> BodySize {
match self {
EncoderBody::None => BodySize::None,
EncoderBody::Full { body } => body.size(),
EncoderBody::Stream { body } => body.size(),
}
}
@@ -126,12 +124,32 @@ where
) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() {
EncoderBodyProj::None => Poll::Ready(None),
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())),
}
}
fn is_complete_body(&self) -> bool {
match self {
EncoderBody::None => true,
EncoderBody::Full { .. } => true,
EncoderBody::Stream { .. } => false,
}
}
fn take_complete_body(&mut self) -> Bytes {
match self {
EncoderBody::None => Bytes::new(),
EncoderBody::Full { body } => body.take_complete_body(),
EncoderBody::Stream { .. } => {
panic!("EncoderBody::Stream variant cannot be taken")
}
}
}
}
impl<B> MessageBody for Encoder<B>
@@ -141,10 +159,10 @@ where
type Error = EncoderError;
fn size(&self) -> BodySize {
if self.encoder.is_none() {
self.body.size()
} else {
if self.encoder.is_some() {
BodySize::Stream
} else {
self.body.size()
}
}
@@ -215,6 +233,22 @@ where
}
}
}
fn is_complete_body(&self) -> bool {
if self.encoder.is_some() {
false
} else {
self.body.is_complete_body()
}
}
fn take_complete_body(&mut self) -> Bytes {
if self.encoder.is_some() {
panic!("compressed body stream cannot be taken")
} else {
self.body.take_complete_body()
}
}
}
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
@@ -222,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

@@ -457,8 +457,7 @@ mod tests {
#[test]
fn test_payload_error() {
let err: PayloadError =
io::Error::new(io::ErrorKind::Other, "ParseError").into();
let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into();
assert!(err.to_string().contains("ParseError"));
let err = PayloadError::Incomplete(None);

View File

@@ -19,7 +19,7 @@ impl Extensions {
#[inline]
pub fn new() -> Extensions {
Extensions {
map: AHashMap::default(),
map: AHashMap::new(),
}
}
@@ -122,13 +122,6 @@ impl Extensions {
pub fn extend(&mut self, other: Extensions) {
self.map.extend(other.map);
}
/// Sets (or overrides) items from cloneable extensions map into this map.
pub(crate) fn clone_from(&mut self, other: &CloneableExtensions) {
for (k, val) in &other.map {
self.map.insert(*k, (**val).clone_to_any());
}
}
}
impl fmt::Debug for Extensions {
@@ -141,104 +134,6 @@ fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
boxed.downcast().ok().map(|boxed| *boxed)
}
#[doc(hidden)]
pub trait CloneToAny {
/// Cast `self` into an `Any` reference.
#[cfg(test)]
fn any_ref(&self) -> &dyn Any;
/// Clone `self` to a new `Box<Any>` object.
fn clone_to_any(&self) -> Box<dyn Any>;
/// Clone `self` to a new `Box<CloneAny>` object.
fn clone_to_clone_any(&self) -> Box<dyn CloneAny>;
}
impl<T: Clone + Any> CloneToAny for T {
#[cfg(test)]
fn any_ref(&self) -> &dyn Any {
&*self
}
#[inline]
fn clone_to_any(&self) -> Box<dyn Any> {
Box::new(self.clone())
}
#[inline]
fn clone_to_clone_any(&self) -> Box<dyn CloneAny> {
Box::new(self.clone())
}
}
/// An [`Any`] trait with an additional [`Clone`] requirement.
pub trait CloneAny: CloneToAny + Any {}
impl<T: Any + Clone> CloneAny for T {}
impl Clone for Box<dyn CloneAny> {
#[inline]
fn clone(&self) -> Self {
(**self).clone_to_clone_any()
}
}
trait UncheckedAnyExt {
/// # Safety
/// Caller must ensure type `T` is true type.
#[inline]
unsafe fn downcast_unchecked<T: 'static>(self: Box<Self>) -> Box<T> {
Box::from_raw(Box::into_raw(self) as *mut T)
}
}
impl UncheckedAnyExt for dyn CloneAny {}
/// A type map for `on_connect` extensions.
///
/// All entries into this map must be owned types and implement `Clone` trait.
///
/// Many requests can be processed for each connection but the `on_connect` will only be run once
/// when the connection is opened. Therefore, items added to this special map type need to be cloned
/// into the regular extensions map for each request. Most useful connection information types are
/// cloneable already but you can use reference counted wrappers if not.
#[derive(Default)]
pub struct CloneableExtensions {
/// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
map: AHashMap<TypeId, Box<dyn CloneAny>>,
}
impl CloneableExtensions {
/// Insert an item into the map.
///
/// If an item of this type was already stored, it will be replaced and returned.
///
/// # Examples
/// ```
/// # use actix_http::Extensions;
/// let mut map = Extensions::new();
/// assert_eq!(map.insert(""), None);
/// assert_eq!(map.insert(1u32), None);
/// assert_eq!(map.insert(2u32), Some(1u32));
/// assert_eq!(*map.get::<u32>().unwrap(), 2u32);
/// ```
pub fn insert<T: CloneAny>(&mut self, val: T) -> Option<T> {
self.map
.insert(TypeId::of::<T>(), Box::new(val))
.map(|boxed| {
// Safety:
// Box is owned and `T` is known to be true type from map.
*unsafe { UncheckedAnyExt::downcast_unchecked::<T>(boxed) }
})
}
#[cfg(test)]
fn get<T: CloneAny>(&self) -> Option<&T> {
self.map
.get(&TypeId::of::<T>())
.and_then(|boxed| boxed.as_ref().any_ref().downcast_ref())
}
}
#[cfg(test)]
mod tests {
use super::*;
@@ -382,43 +277,4 @@ mod tests {
assert_eq!(extensions.get(), Some(&20u8));
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
}
#[test]
fn test_clone_from() {
#[derive(Clone)]
struct NonCopy {
num: u8,
}
let mut ext = Extensions::new();
ext.insert(2isize);
assert_eq!(ext.get::<isize>(), Some(&2isize));
let mut more_ext = CloneableExtensions::default();
more_ext.insert(3isize);
more_ext.insert(3usize);
more_ext.insert(NonCopy { num: 8 });
ext.clone_from(&more_ext);
assert_eq!(ext.get::<isize>(), Some(&3isize));
assert_eq!(ext.get::<usize>(), Some(&3usize));
assert_eq!(more_ext.get::<isize>(), Some(&3isize));
assert_eq!(more_ext.get::<usize>(), Some(&3usize));
assert!(ext.get::<NonCopy>().is_some());
assert!(more_ext.get::<NonCopy>().is_some());
}
#[test]
fn boxes_not_aliased() {
let a: Box<dyn CloneAny> = Box::new(42);
let b = a.clone_to_clone_any();
assert_ne!(Box::into_raw(a) as *const (), Box::into_raw(b) as *const ());
let a: Box<dyn CloneAny> = Box::new(42);
let b = a.clone_to_any();
assert_ne!(Box::into_raw(a) as *const (), Box::into_raw(b) as *const ());
}
}

View File

@@ -50,10 +50,7 @@ impl ChunkedState {
}
}
fn read_size(
rdr: &mut BytesMut,
size: &mut u64,
) -> Poll<Result<ChunkedState, io::Error>> {
fn read_size(rdr: &mut BytesMut, size: &mut u64) -> Poll<Result<ChunkedState, io::Error>> {
let radix = 16;
let rem = match byte!(rdr) {
@@ -111,10 +108,7 @@ impl ChunkedState {
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
}
}
fn read_size_lf(
rdr: &mut BytesMut,
size: u64,
) -> Poll<Result<ChunkedState, io::Error>> {
fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll<Result<ChunkedState, io::Error>> {
match byte!(rdr) {
b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),

View File

@@ -74,8 +74,7 @@ pub(crate) trait MessageType: Sized {
let headers = self.headers_mut();
for idx in raw_headers.iter() {
let name =
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
let name = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
// SAFETY: httparse already checks header value is only visible ASCII bytes
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
@@ -605,8 +604,7 @@ mod tests {
#[test]
fn test_parse_body() {
let mut buf =
BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
@@ -622,8 +620,7 @@ mod tests {
#[test]
fn test_parse_body_crlf() {
let mut buf =
BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
let mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();

View File

@@ -22,11 +22,12 @@ use crate::{
config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError},
service::HttpFlow,
OnConnectData, Request, Response, StatusCode,
Extensions, OnConnectData, Request, Response, StatusCode,
};
use super::{
codec::Codec,
decoder::MAX_BUFFER_SIZE,
payload::{Payload, PayloadSender, PayloadStatus},
Message, MessageType,
};
@@ -100,9 +101,9 @@ where
U::Error: fmt::Display,
{
flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
flags: Flags,
peer_addr: Option<net::SocketAddr>,
conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>,
#[pin]
@@ -179,10 +180,10 @@ where
/// Create HTTP/1 dispatcher.
pub(crate) fn new(
io: T,
config: ServiceConfig,
flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
) -> Self {
let flags = if config.keep_alive_enabled() {
Flags::KEEPALIVE
@@ -198,20 +199,23 @@ where
Dispatcher {
inner: DispatcherState::Normal(InnerDispatcher {
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
payload: None,
state: State::None,
error: None,
messages: VecDeque::new(),
io: Some(io),
codec: Codec::new(config),
flow,
on_connect_data,
flags,
peer_addr,
conn_data: conn_data.0.map(Rc::new),
error: None,
state: State::None,
payload: None,
messages: VecDeque::new(),
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),
}),
#[cfg(test)]
@@ -256,10 +260,7 @@ where
}
}
fn poll_flush(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Result<(), io::Error>> {
fn poll_flush(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
let InnerDispatcherProj { io, write_buf, .. } = self.project();
let mut io = Pin::new(io.as_mut().unwrap());
@@ -269,10 +270,7 @@ where
while written < len {
match io.as_mut().poll_write(cx, &write_buf[written..])? {
Poll::Ready(0) => {
return Poll::Ready(Err(io::Error::new(
io::ErrorKind::WriteZero,
"",
)))
return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, "")))
}
Poll::Ready(n) => written += n,
Poll::Pending => {
@@ -415,15 +413,12 @@ where
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec.encode(
Message::Chunk(Some(item)),
this.write_buf,
)?;
this.codec
.encode(Message::Chunk(Some(item)), this.write_buf)?;
}
Poll::Ready(None) => {
this.codec
.encode(Message::Chunk(None), this.write_buf)?;
this.codec.encode(Message::Chunk(None), this.write_buf)?;
// payload stream finished.
// set state to None and handle next message
this.state.set(State::None);
@@ -450,15 +445,12 @@ where
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => {
this.codec.encode(
Message::Chunk(Some(item)),
this.write_buf,
)?;
this.codec
.encode(Message::Chunk(Some(item)), this.write_buf)?;
}
Poll::Ready(None) => {
this.codec
.encode(Message::Chunk(None), this.write_buf)?;
this.codec.encode(Message::Chunk(None), this.write_buf)?;
// payload stream finished.
// set state to None and handle next message
this.state.set(State::None);
@@ -564,9 +556,11 @@ where
}
};
}
_ => unreachable!(
"State must be set to ServiceCall or ExceptCall in handle_request"
),
_ => {
unreachable!(
"State must be set to ServiceCall or ExceptCall in handle_request"
)
}
}
}
}
@@ -593,16 +587,14 @@ where
Message::Item(mut req) => {
req.head_mut().peer_addr = *this.peer_addr;
// merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req);
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
match this.codec.message_type() {
// Request is upgradable. add upgrade message and break.
// everything remain in read buffer would be handed to
// upgraded Request.
MessageType::Stream if this.flow.upgrade.is_some() => {
this.messages
.push_back(DispatcherMessage::Upgrade(req));
this.messages.push_back(DispatcherMessage::Upgrade(req));
break;
}
@@ -617,8 +609,7 @@ where
where the state can be collected and consumed.
*/
let (ps, pl) = Payload::create(false);
let (req1, _) =
req.replace_payload(crate::Payload::H1(pl));
let (req1, _) = req.replace_payload(crate::Payload::H1(pl));
req = req1;
*this.payload = Some(ps);
}
@@ -639,9 +630,7 @@ where
if let Some(ref mut payload) = this.payload {
payload.feed_data(chunk);
} else {
error!(
"Internal server error: unexpected payload chunk"
);
error!("Internal server error: unexpected payload chunk");
this.flags.insert(Flags::READ_DISCONNECT);
this.messages.push_back(DispatcherMessage::Error(
Response::internal_server_error().drop_body(),
@@ -679,12 +668,11 @@ where
payload.set_error(PayloadError::Overflow);
}
// Requests overflow buffer size should be responded with 431
this.messages.push_back(DispatcherMessage::Error(
Response::with_body(
this.messages
.push_back(DispatcherMessage::Error(Response::with_body(
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
(),
),
));
)));
this.flags.insert(Flags::READ_DISCONNECT);
*this.error = Some(ParseError::TooLarge.into());
break;
@@ -726,8 +714,7 @@ where
None => {
// conditionally go into shutdown timeout
if this.flags.contains(Flags::SHUTDOWN) {
if let Some(deadline) = this.codec.config().client_disconnect_timer()
{
if let Some(deadline) = this.codec.config().client_disconnect_timer() {
// write client disconnect time out and poll again to
// go into Some<Pin<&mut Sleep>> branch
this.ka_timer.set(Some(sleep_until(deadline)));
@@ -770,9 +757,7 @@ where
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
}
// still have unfinished task. try to reset and register keep-alive.
} else if let Some(deadline) =
this.codec.config().keep_alive_expire()
{
} else if let Some(deadline) = this.codec.config().keep_alive_expire() {
timer.as_mut().reset(deadline);
let _ = timer.poll(cx);
}
@@ -791,7 +776,6 @@ where
/// Returns true when io stream can be disconnected after write to it.
///
/// It covers these conditions:
///
/// - `std::io::ErrorKind::ConnectionReset` after partial read.
/// - all data read done.
#[inline(always)]
@@ -811,46 +795,39 @@ where
loop {
// Return early when read buf exceed decoder's max buffer size.
if this.read_buf.len() >= super::decoder::MAX_BUFFER_SIZE {
/*
At this point it's not known IO stream is still scheduled
to be waked up. so force wake up dispatcher just in case.
if this.read_buf.len() >= MAX_BUFFER_SIZE {
// At this point it's not known IO stream is still scheduled to be waked up so
// force wake up dispatcher just in case.
//
// Reason:
// AsyncRead mostly would only have guarantee wake up when the poll_read
// return Poll::Pending.
//
// Case:
// When read_buf is beyond max buffer size the early return could be successfully
// be parsed as a new Request. This case would not generate ParseError::TooLarge and
// at this point IO stream is not fully read to Pending and would result in
// dispatcher stuck until timeout (KA)
//
// Note:
// This is a perf choice to reduce branch on <Request as MessageType>::decode.
//
// A Request head too large to parse is only checked on
// `httparse::Status::Partial` condition.
Reason:
AsyncRead mostly would only have guarantee wake up
when the poll_read return Poll::Pending.
Case:
When read_buf is beyond max buffer size the early return
could be successfully be parsed as a new Request.
This case would not generate ParseError::TooLarge
and at this point IO stream is not fully read to Pending
and would result in dispatcher stuck until timeout (KA)
Note:
This is a perf choice to reduce branch on
<Request as MessageType>::decode.
A Request head too large to parse is only checked on
httparse::Status::Partial condition.
*/
if this.payload.is_none() {
/*
When dispatcher has a payload the responsibility of
wake up it would be shift to h1::payload::Payload.
Reason:
Self wake up when there is payload would waste poll
and/or result in over read.
Case:
When payload is (partial) dropped by user there is
no need to do read anymore.
At this case read_buf could always remain beyond
MAX_BUFFER_SIZE and self wake up would be busy poll
dispatcher and waste resource.
*/
// When dispatcher has a payload the responsibility of wake up it would be shift
// to h1::payload::Payload.
//
// Reason:
// Self wake up when there is payload would waste poll and/or result in
// over read.
//
// Case:
// When payload is (partial) dropped by user there is no need to do
// read anymore. At this case read_buf could always remain beyond
// MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and
// waste resources.
cx.waker().wake_by_ref();
}
@@ -1058,14 +1035,12 @@ mod tests {
}
fn ok_service(
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
{
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
}
fn echo_path_service(
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
{
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
fn_service(|req: Request| {
let path = req.path().as_bytes();
ready(Ok::<_, Error>(
@@ -1074,8 +1049,8 @@ mod tests {
})
}
fn echo_payload_service(
) -> impl Service<Request, Response = Response<Bytes>, Error = Error> {
fn echo_payload_service() -> impl Service<Request, Response = Response<Bytes>, Error = Error>
{
fn_service(|mut req: Request| {
Box::pin(async move {
use futures_util::stream::StreamExt as _;
@@ -1100,10 +1075,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
ServiceConfig::default(),
services,
OnConnectData::default(),
ServiceConfig::default(),
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
@@ -1140,10 +1115,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
@@ -1194,10 +1169,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf,
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
actix_rt::pin!(h1);
@@ -1244,10 +1219,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
@@ -1316,10 +1291,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(),
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(
@@ -1393,10 +1368,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
buf.clone(),
cfg,
services,
OnConnectData::default(),
cfg,
None,
OnConnectData::default(),
);
buf.extend_read_buf(

View File

@@ -103,9 +103,7 @@ pub(crate) trait MessageType: Sized {
dst.put_slice(b"\r\n");
}
}
BodySize::Sized(0) if camel_case => {
dst.put_slice(b"\r\nContent-Length: 0\r\n")
}
BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"),
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
BodySize::Sized(len) => helpers::write_content_length(len, dst),
BodySize::None => dst.put_slice(b"\r\n"),
@@ -307,11 +305,7 @@ impl MessageType for RequestHeadType {
Version::HTTP_11 => "HTTP/1.1",
Version::HTTP_2 => "HTTP/2.0",
Version::HTTP_3 => "HTTP/3.0",
_ =>
return Err(io::Error::new(
io::ErrorKind::Other,
"unsupported version"
)),
_ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")),
}
)
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
@@ -568,8 +562,7 @@ mod tests {
ConnectionType::Close,
&ServiceConfig::default(),
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Content-Length: 0\r\n"));
assert!(data.contains("Connection: close\r\n"));
@@ -583,8 +576,7 @@ mod tests {
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("Transfer-Encoding: chunked\r\n"));
assert!(data.contains("Content-Type: plain/text\r\n"));
assert!(data.contains("Date: date\r\n"));
@@ -605,8 +597,7 @@ mod tests {
ConnectionType::KeepAlive,
&ServiceConfig::default(),
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("transfer-encoding: chunked\r\n"));
assert!(data.contains("content-type: xml\r\n"));
assert!(data.contains("content-type: plain/text\r\n"));
@@ -639,8 +630,7 @@ mod tests {
ConnectionType::Close,
&ServiceConfig::default(),
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(data.contains("content-length: 0\r\n"));
assert!(data.contains("connection: close\r\n"));
assert!(data.contains("authorization: another authorization\r\n"));
@@ -663,8 +653,7 @@ mod tests {
ConnectionType::Upgrade,
&ServiceConfig::default(),
);
let data =
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
assert!(!data.contains("content-length: 0\r\n"));
assert!(!data.contains("transfer-encoding: chunked\r\n"));
}

View File

@@ -227,10 +227,7 @@ impl Inner {
self.len
}
fn readany(
&mut self,
cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, PayloadError>>> {
fn readany(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, PayloadError>>> {
if let Some(data) = self.items.pop_front() {
self.len -= data.len();
self.need_read = self.len < MAX_BUFFER_SIZE;

View File

@@ -266,8 +266,7 @@ where
}
}
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)>
for H1Service<T, S, B, X, U>
impl<T, S, B, X, U> ServiceFactory<(T, Option<net::SocketAddr>)> for H1Service<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin + 'static,
@@ -310,9 +309,9 @@ where
let upgrade = match upgrade {
Some(upgrade) => {
let upgrade = upgrade.await.map_err(|e| {
log::error!("Init http upgrade service error: {:?}", e)
})?;
let upgrade = upgrade
.await
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
Some(upgrade)
}
None => None,
@@ -336,8 +335,7 @@ where
/// `Service` implementation for HTTP/1 transport
pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>;
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
for HttpServiceHandler<T, S, B, X, U>
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)> for HttpServiceHandler<T, S, B, X, U>
where
T: AsyncRead + AsyncWrite + Unpin,
@@ -365,15 +363,7 @@ where
}
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(
io,
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
addr,
)
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data)
}
}

View File

@@ -70,15 +70,12 @@ where
.unwrap()
.is_write_buf_full()
{
let next =
match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
Poll::Ready(Some(Err(err))) => {
return Poll::Ready(Err(err.into()))
}
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
};
let next = match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
Poll::Ready(None) => Poll::Ready(None),
Poll::Pending => Poll::Pending,
};
match next {
Poll::Ready(item) => {
@@ -88,9 +85,9 @@ where
let _ = this.body.take();
}
let framed = this.framed.as_mut().as_pin_mut().unwrap();
framed.write(Message::Chunk(item)).map_err(|err| {
Error::new_send_response().with_cause(err)
})?;
framed
.write(Message::Chunk(item))
.map_err(|err| Error::new_send_response().with_cause(err))?;
}
Poll::Pending => body_ready = false,
}

View File

@@ -19,15 +19,15 @@ 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,
OnConnectData, Payload, Request, Response, ResponseHead,
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
};
const CHUNK_SIZE: usize = 16_384;
@@ -37,7 +37,7 @@ pin_project! {
pub struct Dispatcher<T, S, B, X, U> {
flow: Rc<HttpFlow<S, X, U>>,
connection: Connection<T, Bytes>,
on_connect_data: OnConnectData,
conn_data: Option<Rc<Extensions>>,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
ping_pong: Option<H2PingPong>,
@@ -50,11 +50,11 @@ where
T: AsyncRead + AsyncWrite + Unpin,
{
pub(crate) fn new(
flow: Rc<HttpFlow<S, X, U>>,
mut conn: Connection<T, Bytes>,
on_connect_data: OnConnectData,
flow: Rc<HttpFlow<S, X, U>>,
config: ServiceConfig,
peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
timer: Option<Pin<Box<Sleep>>>,
) -> Self {
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
@@ -74,7 +74,7 @@ where
config,
peer_addr,
connection: conn,
on_connect_data,
conn_data: conn_data.0.map(Rc::new),
ping_pong,
_phantom: PhantomData,
}
@@ -109,7 +109,7 @@ where
Poll::Ready(Some((req, tx))) => {
let (parts, body) = req.into_parts();
let pl = crate::h2::Payload::new(body);
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
let pl = Payload::H2(pl);
let mut req = Request::with_payload(pl);
let head = req.head_mut();
@@ -119,8 +119,7 @@ where
head.headers = parts.headers.into();
head.peer_addr = this.peer_addr;
// merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req);
req.conn_data = this.conn_data.as_ref().map(Rc::clone);
let fut = this.flow.service.call(req);
let config = this.config.clone();
@@ -161,16 +160,11 @@ where
Poll::Ready(_) => {
ping_pong.on_flight = false;
let dead_line =
this.config.keep_alive_expire().unwrap();
let dead_line = this.config.keep_alive_expire().unwrap();
ping_pong.timer.as_mut().reset(dead_line);
}
Poll::Pending => {
return ping_pong
.timer
.as_mut()
.poll(cx)
.map(|_| Ok(()))
return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(()))
}
}
} else {
@@ -223,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

@@ -40,10 +40,7 @@ impl Payload {
impl Stream for Payload {
type Item = Result<Bytes, PayloadError>;
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
let this = self.get_mut();
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {

View File

@@ -1,7 +1,7 @@
use std::{
future::Future,
marker::PhantomData,
net,
mem, net,
pin::Pin,
rc::Rc,
task::{Context, Poll},
@@ -10,8 +10,7 @@ use std::{
use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream;
use actix_service::{
fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
ServiceFactoryExt as _,
fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
};
use actix_utils::future::ready;
use futures_core::{future::LocalBoxFuture, ready};
@@ -271,16 +270,15 @@ 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)
})
}
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
H2ServiceHandlerResponse {
state: State::Handshake(
@@ -299,7 +297,6 @@ where
T: AsyncRead + AsyncWrite + Unpin,
S::Future: 'static,
{
Incoming(Dispatcher<T, S, B, (), ()>),
Handshake(
Option<Rc<HttpFlow<S, (), ()>>>,
Option<ServiceConfig>,
@@ -307,6 +304,7 @@ where
OnConnectData,
HandshakeWithTimeout<T>,
),
Established(Dispatcher<T, S, B, (), ()>),
}
pub struct H2ServiceHandlerResponse<T, S, B>
@@ -334,31 +332,35 @@ 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,
ref peer_addr,
ref mut on_connect_data,
ref mut conn_data,
ref mut handshake,
) => match ready!(Pin::new(handshake).poll(cx)) {
Ok((conn, timer)) => {
let on_connect_data = std::mem::take(on_connect_data);
self.state = State::Incoming(Dispatcher::new(
srv.take().unwrap(),
let on_connect_data = mem::take(conn_data);
self.state = State::Established(Dispatcher::new(
conn,
on_connect_data,
srv.take().unwrap(),
config.take().unwrap(),
*peer_addr,
on_connect_data,
timer,
));
self.poll(cx)
}
Err(err) => {
trace!("H2 handshake error: {}", err);
Poll::Ready(Err(err))
}
},
State::Established(ref mut disp) => Pin::new(disp).poll(cx),
}
}
}

View File

@@ -6,7 +6,7 @@ use http::header::{HeaderName, InvalidHeaderName};
/// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`].
///
/// [`HeaderValue`]: crate::http::HeaderValue
/// [`HeaderValue`]: super::HeaderValue
pub trait AsHeaderName: Sealed {}
pub struct Seal;
@@ -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`]: crate::http::HeaderMap
pub trait IntoHeaderPair: Sized {
/// [`HeaderMap`]: super::HeaderMap
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

@@ -123,12 +123,11 @@ impl HeaderMap {
let mut map = HeaderMap::with_capacity(capacity);
map.append(first_name.clone(), first_value);
let (map, _) =
drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
let name = name.unwrap_or(prev_name);
map.append(name.clone(), value);
(map, name)
});
let (map, _) = drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
let name = name.unwrap_or(prev_name);
map.append(name.clone(), value);
(map, name)
});
map
}
@@ -334,7 +333,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;
@@ -373,7 +372,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

View File

@@ -11,22 +11,20 @@ pub use http::header::{
pub use http::header::{
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE,
DATE, DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH,
IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED,
LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA, PROXY_AUTHENTICATE,
PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER,
REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT,
SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE,
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION,
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE,
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS,
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS,
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER,
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA,
WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL,
X_FRAME_OPTIONS, X_XSS_PROTECTION,
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING,
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS,
X_XSS_PROTECTION,
};
use crate::{error::ParseError, HttpMessage};
@@ -39,19 +37,19 @@ 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, Quality, QualityItem,
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
Quality, QualityItem,
};
pub use self::utils::{
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
};
/// 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

@@ -63,9 +63,7 @@ pub struct ExtendedValue {
/// [RFC 2231 §7]: https://datatracker.ietf.org/doc/html/rfc2231#section-7
/// [RFC 2978 §2.3]: https://datatracker.ietf.org/doc/html/rfc2978#section-2.3
/// [RFC 3986 §2.1]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
pub fn parse_extended_value(
val: &str,
) -> Result<ExtendedValue, crate::error::ParseError> {
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, crate::error::ParseError> {
// Break into three pieces separated by the single-quote character
let mut parts = val.splitn(3, '\'');
@@ -100,8 +98,7 @@ pub fn parse_extended_value(
impl fmt::Display for ExtendedValue {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let encoded_value =
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
let encoded_value = percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
if let Some(ref lang) = self.language_tag {
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
} else {
@@ -143,8 +140,8 @@ mod tests {
assert!(extended_value.language_tag.is_none());
assert_eq!(
vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
b'e', b's',
],
extended_value.value
);
@@ -185,8 +182,8 @@ mod tests {
charset: Charset::Ext("UTF-8".to_string()),
language_tag: None,
value: vec![
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
b't', b'e', b's',
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
b'e', b's',
],
};
assert_eq!(

View File

@@ -4,7 +4,7 @@ use bytes::BytesMut;
use http::header::{HeaderValue, InvalidHeaderValue};
use crate::{
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue,
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue,
helpers::MutWriter,
};
@@ -30,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

@@ -120,8 +120,7 @@ impl<T: str::FromStr> str::FromStr for QualityItem<T> {
}
let q_value = q_val.parse::<f32>().map_err(|_| ParseError::Header)?;
let q_value =
Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
let q_value = Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
quality = q_value;
raw_item = val;

View File

@@ -14,7 +14,8 @@
//! [rustls]: https://crates.io/crates/rustls
//! [trust-dns]: https://crates.io/crates/trust-dns
#![deny(rust_2018_idioms, nonstandard_style, clippy::uninit_assumed_init)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![allow(
clippy::type_complexity,
clippy::too_many_arguments,
@@ -53,7 +54,7 @@ pub mod ws;
pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::Error;
pub use self::extensions::{CloneableExtensions, Extensions};
pub use self::extensions::Extensions;
pub use self::header::ContentEncoding;
pub use self::http_message::HttpMessage;
pub use self::message::ConnectionType;
@@ -76,35 +77,24 @@ pub enum Protocol {
Http3,
}
type ConnectCallback<IO> = dyn Fn(&IO, &mut CloneableExtensions);
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
/// Container for data that extract with ConnectCallback.
///
/// # Implementation Details
/// Uses Option to reduce necessary allocations when merging with request extensions.
#[derive(Default)]
pub(crate) struct OnConnectData(Option<CloneableExtensions>);
pub(crate) struct OnConnectData(Option<Extensions>);
impl OnConnectData {
/// Construct by calling the on-connect callback with the underlying transport I/O.
pub(crate) fn from_io<T>(
io: &T,
on_connect_ext: Option<&ConnectCallback<T>>,
) -> Self {
pub(crate) fn from_io<T>(io: &T, on_connect_ext: Option<&ConnectCallback<T>>) -> Self {
let ext = on_connect_ext.map(|handler| {
let mut extensions = CloneableExtensions::default();
let mut extensions = Extensions::default();
handler(io, &mut extensions);
extensions
});
Self(ext)
}
/// Merge self into given request's extensions.
#[inline]
pub(crate) fn merge_into(&mut self, req: &mut Request) {
if let Some(ref ext) = self.0 {
req.head.extensions.get_mut().clone_from(ext);
}
}
}

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),
@@ -56,10 +56,7 @@ where
type Item = Result<Bytes, PayloadError>;
#[inline]
fn poll_next(
self: Pin<&mut Self>,
cx: &mut Context<'_>,
) -> Poll<Option<Self::Item>> {
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
match self.get_mut() {
Payload::None => Poll::Ready(None),
Payload::H1(ref mut pl) => pl.readany(cx),

View File

@@ -1,8 +1,10 @@
//! HTTP requests.
use std::{
cell::{Ref, RefMut},
fmt, net, str,
cell::{Ref, RefCell, RefMut},
fmt, mem, net,
rc::Rc,
str,
};
use http::{header, Method, Uri, Version};
@@ -19,6 +21,8 @@ use crate::{
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> {
@@ -30,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()
}
}
@@ -51,6 +55,8 @@ impl From<Message<RequestHead>> for Request<PayloadStream> {
Request {
head,
payload: Payload::None,
req_data: RefCell::new(Extensions::default()),
conn_data: None,
}
}
}
@@ -61,6 +67,8 @@ impl Request<PayloadStream> {
Request {
head: Message::new(),
payload: Payload::None,
req_data: RefCell::new(Extensions::default()),
conn_data: None,
}
}
}
@@ -71,16 +79,21 @@ impl<P> Request<P> {
Request {
payload,
head: Message::new(),
req_data: RefCell::new(Extensions::default()),
conn_data: None,
}
}
/// Create new Request instance
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
let pl = self.payload;
(
Request {
payload,
head: self.head,
req_data: self.req_data,
conn_data: self.conn_data,
},
pl,
)
@@ -93,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
@@ -116,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.
@@ -128,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.
@@ -170,6 +183,31 @@ impl<P> Request<P> {
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr
}
/// Returns a reference a piece of connection data set in an [on-connect] callback.
///
/// ```ignore
/// let opt_t = req.conn_data::<PeerCertificate>();
/// ```
///
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
self.conn_data
.as_deref()
.and_then(|container| container.get::<T>())
}
/// Returns the connection data container if an [on-connect] callback was registered.
///
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
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,
};
@@ -231,9 +231,7 @@ impl<B: Default> Default for Response<B> {
}
}
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>>
for Response<BoxBody>
{
impl<I: Into<Response<BoxBody>>, E: Into<Error>> From<Result<I, E>> for Response<BoxBody> {
fn from(res: Result<I, E>) -> Self {
match res {
Ok(val) => val.into(),

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,
};
@@ -47,7 +47,8 @@ impl ResponseBuilder {
/// Create response builder
///
/// # Examples
// /// use actix_http::{Response, ResponseBuilder, StatusCode};, / ``
/// ```
/// use actix_http::{Response, ResponseBuilder, StatusCode};
/// let res: Response<_> = ResponseBuilder::default().finish();
/// assert_eq!(res.status(), StatusCode::OK);
/// ```
@@ -62,7 +63,8 @@ impl ResponseBuilder {
/// Set HTTP status code of this response.
///
/// # Examples
// /// use actix_http::{ResponseBuilder, StatusCode};, / ``
/// ```
/// use actix_http::{ResponseBuilder, StatusCode};
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
/// ```
@@ -88,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);
}
@@ -119,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()),
};
@@ -155,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);
@@ -193,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

@@ -161,11 +161,7 @@ where
X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug,
U: ServiceFactory<
(Request, Framed<TcpStream, h1::Codec>),
Config = (),
Response = (),
>,
U: ServiceFactory<(Request, Framed<TcpStream, h1::Codec>), Config = (), Response = ()>,
U::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug,
@@ -381,9 +377,9 @@ where
let upgrade = match upgrade {
Some(upgrade) => {
let upgrade = upgrade.await.map_err(|e| {
log::error!("Init http upgrade service error: {:?}", e)
})?;
let upgrade = upgrade
.await
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
Some(upgrade)
}
None => None,
@@ -507,8 +503,7 @@ where
&self,
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
) -> Self::Future {
let on_connect_data =
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
match proto {
Protocol::Http2 => HttpServiceHandlerResponse {
@@ -517,7 +512,7 @@ where
h2::handshake_with_timeout(io, &self.cfg),
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
conn_data,
peer_addr,
)),
},
@@ -527,10 +522,10 @@ where
state: State::H1 {
dispatcher: h1::Dispatcher::new(
io,
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
self.cfg.clone(),
peer_addr,
conn_data,
),
},
},
@@ -627,17 +622,11 @@ where
StateProj::H2Handshake { handshake: data } => {
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
Ok((conn, timer)) => {
let (_, config, flow, on_connect_data, peer_addr) =
data.take().unwrap();
let (_, config, flow, conn_data, peer_addr) = data.take().unwrap();
self.as_mut().project().state.set(State::H2 {
dispatcher: h2::Dispatcher::new(
flow,
conn,
on_connect_data,
config,
peer_addr,
timer,
conn, flow, config, peer_addr, conn_data, timer,
),
});
self.poll(cx)

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

View File

@@ -224,9 +224,7 @@ impl Decoder for Codec {
OpCode::Continue => {
if self.flags.contains(Flags::CONTINUATION) {
Ok(Some(Frame::Continuation(Item::Continue(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationNotStarted)
@@ -236,9 +234,7 @@ impl Decoder for Codec {
if !self.flags.contains(Flags::CONTINUATION) {
self.flags.insert(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::FirstBinary(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationStarted)
@@ -248,9 +244,7 @@ impl Decoder for Codec {
if !self.flags.contains(Flags::CONTINUATION) {
self.flags.insert(Flags::CONTINUATION);
Ok(Some(Frame::Continuation(Item::FirstText(
payload
.map(|pl| pl.freeze())
.unwrap_or_else(Bytes::new),
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
))))
} else {
Err(ProtocolError::ContinuationStarted)

View File

@@ -304,8 +304,7 @@ mod inner {
let item = match this.framed.next_item(cx) {
Poll::Ready(Some(Ok(el))) => el,
Poll::Ready(Some(Err(err))) => {
*this.state =
State::FramedError(DispatcherError::Decoder(err));
*this.state = State::FramedError(DispatcherError::Decoder(err));
return true;
}
Poll::Pending => return false,
@@ -348,8 +347,7 @@ mod inner {
match Pin::new(&mut this.rx).poll_next(cx) {
Poll::Ready(Some(Ok(Message::Item(msg)))) => {
if let Err(err) = this.framed.as_mut().write(msg) {
*this.state =
State::FramedError(DispatcherError::Encoder(err));
*this.state = State::FramedError(DispatcherError::Encoder(err));
return true;
}
}
@@ -371,8 +369,7 @@ mod inner {
Poll::Ready(Ok(_)) => {}
Poll::Ready(Err(err)) => {
debug!("Error sending data: {:?}", err);
*this.state =
State::FramedError(DispatcherError::Encoder(err));
*this.state = State::FramedError(DispatcherError::Encoder(err));
return true;
}
}
@@ -432,9 +429,7 @@ mod inner {
Poll::Ready(Ok(()))
}
}
State::FramedError(_) => {
Poll::Ready(Err(this.state.take_framed_error()))
}
State::FramedError(_) => Poll::Ready(Err(this.state.take_framed_error())),
State::Stopping => Poll::Ready(Ok(())),
};
}

View File

@@ -16,8 +16,7 @@ impl Parser {
src: &[u8],
server: bool,
max_size: usize,
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError>
{
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> {
let chunk_len = src.len();
let mut idx = 2;
@@ -228,15 +227,11 @@ mod tests {
payload: Bytes,
}
fn is_none(
frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
) -> bool {
fn is_none(frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> bool {
matches!(*frm, Ok(None))
}
fn extract(
frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
) -> F {
fn extract(frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> F {
match frm {
Ok(Some((finished, opcode, payload))) => F {
finished,

View File

@@ -54,8 +54,8 @@ mod tests {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
let unmasked = vec![
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
0x74, 0xf9, 0x12, 0x03,
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
0x12, 0x03,
];
// Check masking with proper alignment.
@@ -85,8 +85,8 @@ mod tests {
fn test_apply_mask() {
let mask = [0x6d, 0xb6, 0xb2, 0x80];
let unmasked = vec![
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
0x74, 0xf9, 0x12, 0x03,
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
0x12, 0x03,
];
for data_len in 0..=unmasked.len() {

View File

@@ -9,9 +9,7 @@ use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode};
use crate::body::BoxBody;
use crate::{
header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder,
};
use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder};
mod codec;
mod dispatcher;

View File

@@ -1,8 +1,6 @@
use std::convert::Infallible;
use actix_http::{
body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode,
};
use actix_http::{body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode};
use actix_http_test::test_server;
use actix_service::ServiceFactoryExt;
use actix_utils::future;

View File

@@ -8,7 +8,7 @@ use actix_http::{
body::{BodyStream, BoxBody, SizedStream},
error::PayloadError,
header::{self, HeaderValue},
Error, HttpMessage, HttpService, Method, Request, Response, StatusCode, Version,
Error, HttpService, Method, Request, Response, StatusCode, Version,
};
use actix_http_test::test_server;
use actix_service::{fn_service, ServiceFactoryExt};
@@ -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 {
@@ -170,10 +170,11 @@ async fn test_h2_headers() {
let mut srv = test_server(move || {
let data = data.clone();
HttpService::build().h2(move |_| {
let mut builder = Response::build(StatusCode::OK);
for idx in 0..90 {
builder.insert_header(
HttpService::build()
.h2(move |_| {
let mut builder = Response::build(StatusCode::OK);
for idx in 0..90 {
builder.insert_header(
(format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
@@ -189,12 +190,13 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
));
}
ok::<_, Infallible>(builder.body(data.clone()))
})
}
ok::<_, Infallible>(builder.body(data.clone()))
})
.openssl(tls_config())
.map_err(|_| ())
}).await;
.map_err(|_| ())
})
.await;
let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success());
@@ -315,9 +317,8 @@ async fn test_h2_body_length() {
let mut srv = test_server(move || {
HttpService::build()
.h2(|_| async {
let body = once(async {
Ok::<_, Infallible>(Bytes::from_static(STR.as_ref()))
});
let body =
once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
Ok::<_, Infallible>(
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
@@ -430,7 +431,7 @@ async fn test_h2_on_connect() {
data.insert(20isize);
})
.h2(|req: Request| {
assert!(req.extensions().contains::<isize>());
assert!(req.conn_data::<isize>().is_some());
ok::<_, Infallible>(Response::ok())
})
.openssl(tls_config())

View File

@@ -238,10 +238,11 @@ async fn test_h2_headers() {
let mut srv = test_server(move || {
let data = data.clone();
HttpService::build().h2(move |_| {
let mut config = Response::build(StatusCode::OK);
for idx in 0..90 {
config.insert_header((
HttpService::build()
.h2(move |_| {
let mut config = Response::build(StatusCode::OK);
for idx in 0..90 {
config.insert_header((
format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
@@ -257,11 +258,12 @@ async fn test_h2_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
));
}
ok::<_, Infallible>(config.body(data.clone()))
})
}
ok::<_, Infallible>(config.body(data.clone()))
})
.rustls(tls_config())
}).await;
})
.await;
let response = srv.sget("/").send().await.unwrap();
assert!(response.status().is_success());

View File

@@ -7,7 +7,7 @@ use std::{
use actix_http::{
body::{self, BodyStream, BoxBody, SizedStream},
header, Error, HttpMessage, HttpService, KeepAlive, Request, Response, StatusCode,
header, Error, HttpService, KeepAlive, Request, Response, StatusCode,
};
use actix_http_test::test_server;
use actix_rt::time::sleep;
@@ -154,9 +154,7 @@ async fn test_chunked_payload() {
})
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
.map(|req_size| {
Ok::<_, Error>(
Response::ok().set_body(format!("size={}", req_size)),
)
Ok::<_, Error>(Response::ok().set_body(format!("size={}", req_size)))
})
}))
.tcp()
@@ -165,8 +163,7 @@ async fn test_chunked_payload() {
let returned_size = {
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream
.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
let _ = stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
for chunk_size in chunk_sizes.iter() {
let mut bytes = Vec::new();
@@ -293,8 +290,7 @@ async fn test_http1_keepalive_close() {
.await;
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ =
stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n");
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n");
let mut data = vec![0; 1024];
let _ = stream.read(&mut data);
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
@@ -338,8 +334,8 @@ async fn test_http10_keepalive() {
.await;
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
let _ = stream
.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
let _ =
stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
let mut data = vec![0; 1024];
let _ = stream.read(&mut data);
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
@@ -436,10 +432,11 @@ async fn test_h1_headers() {
let mut srv = test_server(move || {
let data = data.clone();
HttpService::build().h1(move |_| {
let mut builder = Response::build(StatusCode::OK);
for idx in 0..90 {
builder.insert_header((
HttpService::build()
.h1(move |_| {
let mut builder = Response::build(StatusCode::OK);
for idx in 0..90 {
builder.insert_header((
format!("X-TEST-{}", idx).as_str(),
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
@@ -455,10 +452,12 @@ async fn test_h1_headers() {
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST ",
));
}
ok::<_, Infallible>(builder.body(data.clone()))
}).tcp()
}).await;
}
ok::<_, Infallible>(builder.body(data.clone()))
})
.tcp()
})
.await;
let response = srv.get("/").send().await.unwrap();
assert!(response.status().is_success());
@@ -655,9 +654,7 @@ async fn test_h1_body_chunked_implicit() {
HttpService::build()
.h1(|_| {
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
ok::<_, Infallible>(
Response::build(StatusCode::OK).body(BodyStream::new(body)),
)
ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(body)))
})
.tcp()
})
@@ -748,7 +745,7 @@ async fn test_h1_on_connect() {
data.insert(20isize);
})
.h1(|req: Request| {
assert!(req.extensions().contains::<isize>());
assert!(req.conn_data::<isize>().is_some());
ok::<_, Infallible>(Response::ok())
})
.tcp()
@@ -776,10 +773,8 @@ async fn test_not_modified_spec_h1() {
.h1(|req: Request| {
let res: Response<BoxBody> = match req.path() {
// with no content-length
"/none" => {
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
.map_into_boxed_body()
}
"/none" => Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
.map_into_boxed_body(),
// with no content-length
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
@@ -787,10 +782,8 @@ async fn test_not_modified_spec_h1() {
// with manual content-length header and specific None body
"/cl-none" => {
let mut res = Response::with_body(
StatusCode::NOT_MODIFIED,
body::None::new(),
);
let mut res =
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new());
res.headers_mut()
.insert(CL.clone(), header::HeaderValue::from_static("24"));
res.map_into_boxed_body()
@@ -798,8 +791,7 @@ async fn test_not_modified_spec_h1() {
// with manual content-length header and ignore-able body
"/cl-body" => {
let mut res =
Response::with_body(StatusCode::NOT_MODIFIED, "1234");
let mut res = Response::with_body(StatusCode::NOT_MODIFIED, "1234");
res.headers_mut()
.insert(CL.clone(), header::HeaderValue::from_static("4"));
res.map_into_boxed_body()

View File

@@ -56,8 +56,9 @@ impl From<WsServiceError> for Response<BoxBody> {
WsServiceError::Http(err) => err.into(),
WsServiceError::Ws(err) => err.into(),
WsServiceError::Io(_err) => unreachable!(),
WsServiceError::Dispatcher => Response::internal_server_error()
.set_body(BoxBody::new(format!("{}", err))),
WsServiceError::Dispatcher => {
Response::internal_server_error().set_body(BoxBody::new(format!("{}", err)))
}
}
}
}
@@ -97,9 +98,7 @@ where
async fn service(msg: Frame) -> Result<Message, Error> {
let msg = match msg {
Frame::Ping(msg) => Message::Pong(msg),
Frame::Text(text) => {
Message::Text(String::from_utf8_lossy(&text).into_owned().into())
}
Frame::Text(text) => Message::Text(String::from_utf8_lossy(&text).into_owned().into()),
Frame::Binary(bin) => Message::Binary(bin),
Frame::Continuation(item) => Message::Continuation(item),
Frame::Close(reason) => Message::Close(reason),

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.14", default-features = false }
bytes = "1"
derive_more = "0.99.5"
@@ -28,7 +28,7 @@ twoway = "0.2"
[dev-dependencies]
actix-rt = "2.2"
actix-http = "3.0.0-beta.14"
actix-http = "3.0.0-beta.15"
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,7 @@
//! Multipart form support for Actix Web.
#![deny(rust_2018_idioms)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![allow(clippy::borrow_interior_mutable_const)]
mod error;

View File

@@ -1,149 +1,26 @@
//! Resource path matching and router.
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
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

@@ -168,7 +168,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// extracted in the same way as non-tail dynamic segments.
///
/// ## Examples
/// ```rust
/// ```
/// # use actix_router::{Path, ResourceDef};
/// let resource = ResourceDef::new("/blob/{tail}*");
/// assert!(resource.is_match("/blob/HEAD/Cargo.toml"));
@@ -191,7 +191,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// expectations in the router using these definitions and cause runtime panics.
///
/// ## Examples
/// ```rust
/// ```
/// # use actix_router::ResourceDef;
/// let resource = ResourceDef::new(["/home", "/index"]);
/// assert!(resource.is_match("/home"));
@@ -206,7 +206,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
/// resource-path pairs that would not be compatible.
///
/// ## Examples
/// ```rust
/// ```
/// # use actix_router::ResourceDef;
/// assert!(!ResourceDef::new("/root").is_match("/root/"));
/// assert!(!ResourceDef::new("/root/").is_match("/root"));

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

@@ -3,6 +3,10 @@
## Unreleased - 2021-xx-xx
## 0.1.0-beta.8 - 2021-12-11
* No significant changes since `0.1.0-beta.7`.
## 0.1.0-beta.7 - 2021-11-22
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]

View File

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

@@ -26,6 +26,9 @@
//! }
//! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#[cfg(feature = "openssl")]
extern crate tls_openssl as openssl;
#[cfg(feature = "rustls")]

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

@@ -1,7 +1,7 @@
//! Actix actors support for Actix Web.
#![deny(rust_2018_idioms)]
#![allow(clippy::borrow_interior_mutable_const)]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
mod context;
pub mod ws;

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"
@@ -21,11 +21,11 @@ proc-macro2 = "1"
actix-router = "0.5.0-beta.2"
[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.8"
actix-utils = "3.0.0"
actix-web = "4.0.0-beta.11"
actix-web = "4.0.0-beta.14"
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

@@ -57,6 +57,8 @@
//! [DELETE]: macro@delete
#![recursion_limit = "512"]
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
use proc_macro::TokenStream;
use quote::quote;

View File

@@ -1,7 +1,4 @@
extern crate proc_macro;
use std::collections::HashSet;
use std::convert::TryFrom;
use std::{collections::HashSet, convert::TryFrom};
use actix_router::ResourceDef;
use proc_macro::TokenStream;

View File

@@ -1,6 +1,13 @@
# Changes
## Unreleased - 2021-xx-xx
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
## 3.0.0-beta.13 - 2021-12-11
* No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2021-11-30
@@ -56,7 +63,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.13"
authors = [
"Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>",
@@ -60,7 +60,7 @@ dangerous-h2c = []
[dependencies]
actix-codec = "0.4.1"
actix-service = "2.0.0"
actix-http = "3.0.0-beta.14"
actix-http = "3.0.0-beta.15"
actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
actix-utils = "3.0.0"
@@ -72,7 +72,7 @@ cfg-if = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false }
h2 = "0.3"
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.15", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.1"
actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.14", 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.13)](https://docs.rs/awc/3.0.0-beta.13)
![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.13/status.svg)](https://deps.rs/crate/awc/3.0.0-beta.13)
[![Chat on Discord](https://img.shields.io/discord/771444961383153695?label=chat&logo=discord)](https://discord.gg/NWpN5mmg3x)
## Documentation & Resources

View File

@@ -2,7 +2,7 @@ use std::{convert::TryFrom, fmt, net::IpAddr, rc::Rc, time::Duration};
use actix_http::{
error::HttpError,
header::{self, HeaderMap, HeaderName},
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

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

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