1
0
mirror of https://github.com/fafhrd91/actix-web synced 2025-08-22 05:35:08 +02:00

Compare commits

..

14 Commits

Author SHA1 Message Date
Rob Ede
1aee8a1a58 Merge remote-tracking branch 'origin/master' into on-connect-fix 2021-12-05 23:24:44 +00:00
Rob Ede
cca0593df1 Merge branch 'master' into on-connect-fix 2021-12-05 21:26:31 +00:00
Rob Ede
efa68ec453 fix docs 2021-12-05 04:57:17 +00:00
Rob Ede
01885f9954 inline unsafe 2021-12-05 04:49:52 +00:00
Rob Ede
a86c831b89 Merge remote-tracking branch 'origin/master' into on-connect-fix 2021-12-05 04:46:19 +00:00
Rob Ede
999c003aa8 clippy 2021-07-14 23:54:55 +01:00
Rob Ede
2bc7102e37 update changelog 2021-07-14 23:50:29 +01:00
Rob Ede
20752fd82e document unsafe 2021-07-14 23:45:58 +01:00
Rob Ede
e6290dfd09 fix test 2021-07-14 00:36:00 +01:00
Rob Ede
9e685fc5fb fix doc references 2021-07-14 00:27:33 +01:00
Rob Ede
cf63f5c755 remove dead code 2021-07-14 00:23:22 +01:00
Rob Ede
694cfc94c9 fmt 2021-07-14 00:21:05 +01:00
Rob Ede
6bb33ec5db use custom cloneany trait 2021-07-14 00:20:45 +01:00
Rob Ede
3b2e2acb6c fix connection data on keep alive connections 2021-07-12 18:37:41 +01:00
157 changed files with 2736 additions and 3295 deletions

View File

@@ -1,72 +1,36 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.0.0-beta.15 - 2021-12-17
### Added
* Method on `Responder` trait (`customize`) for customizing responders and `CustomizeResponder` struct. [#2510]
* Implement `Debug` for `DefaultHeaders`. [#2510]
### Changed
* Align `DefaultHeader` method terminology, deprecating previous methods. [#2510]
* Response service types in `ErrorHandlers` middleware now use `ServiceResponse<EitherBody<B>>` to allow changing the body type. [#2515]
* Both variants in `ErrorHandlerResponse` now use `ServiceResponse<EitherBody<B>>`. [#2515]
* Rename `test::{default_service => simple_service}`. Old name is deprecated. [#2518]
* Rename `test::{read_response_json => call_and_read_body_json}`. Old name is deprecated. [#2518]
* Rename `test::{read_response => call_and_read_body}`. Old name is deprecated. [#2518]
* Relax body type and error bounds on test utilities.
### Removed
* Top-level `EitherExtractError` export. [#2510]
* Conversion implementations for `either` crate. [#2516]
* `test::load_stream` and `test::load_body`; replace usage with `body::to_bytes`. [#2518]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2515]: https://github.com/actix/actix-web/pull/2515
[#2516]: https://github.com/actix/actix-web/pull/2516
[#2518]: https://github.com/actix/actix-web/pull/2518
## 4.0.0-beta.14 - 2021-12-11
### Added ### Added
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480] * Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
* `AcceptEncoding` typed header. [#2482] * `AcceptEncoding` typed header. [#2482]
* `Range` typed header. [#2485] * `Range` typed header. [#2485]
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468] * `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] * `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491] * `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
* `HttpRequest::{req_data,req_data_mut}`. [#2487]
* `ServiceResponse::into_parts`. [#2499] [#2325]: https://github.com/actix/actix-web/pull/2325
[#2327]: https://github.com/actix/actix-web/pull/2327
### Changed ### Changed
* Rename `Accept::{mime_precedence => ranked}`. [#2480] * Rename `Accept::{mime_precedence => ranked}`. [#2480]
* Rename `Accept::{mime_preference => preference}`. [#2480] * Rename `Accept::{mime_preference => preference}`. [#2480]
* Un-deprecate `App::data_factory`. [#2484] * Un-deprecate `App::data_factory`. [#2484]
* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430] * `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430]
* Remove `B` (body) type parameter on `App`. [#2493] * `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
* 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 ### Fixed
* Accept wildcard `*` items in `AcceptLanguage`. [#2480] * Accept wildcard `*` items in `AcceptLanguage`. [#2480]
* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468] * 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] * Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
### Removed [#2327]: https://github.com/actix/actix-web/pull/2327
* `ConnectionInfo::get`. [#2487]
[#2430]: https://github.com/actix/actix-web/pull/2430 [#2430]: https://github.com/actix/actix-web/pull/2430
[#2468]: https://github.com/actix/actix-web/pull/2468 [#2468]: https://github.com/actix/actix-web/pull/2468
[#2480]: https://github.com/actix/actix-web/pull/2480 [#2480]: https://github.com/actix/actix-web/pull/2480
[#2482]: https://github.com/actix/actix-web/pull/2482 [#2482]: https://github.com/actix/actix-web/pull/2482
[#2484]: https://github.com/actix/actix-web/pull/2484 [#2484]: https://github.com/actix/actix-web/pull/2484
[#2485]: https://github.com/actix/actix-web/pull/2485 [#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 ## 4.0.0-beta.13 - 2021-11-30

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web" name = "actix-web"
version = "4.0.0-beta.15" version = "4.0.0-beta.13"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust" description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
@@ -77,15 +77,16 @@ actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true } actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
actix-http = "3.0.0-beta.16" actix-http = "3.0.0-beta.14"
actix-router = "0.5.0-beta.3" actix-router = "0.5.0-beta.2"
actix-web-codegen = "0.5.0-beta.6" actix-web-codegen = "0.5.0-beta.5"
ahash = "0.7" ahash = "0.7"
bytes = "1" bytes = "1"
cfg-if = "1" cfg-if = "1"
cookie = { version = "0.15", features = ["percent-encode"], optional = true } cookie = { version = "0.15", features = ["percent-encode"], optional = true }
derive_more = "0.99.5" derive_more = "0.99.5"
either = "1.5.3"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false } futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }
@@ -106,8 +107,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
url = "2.1" url = "2.1"
[dev-dependencies] [dev-dependencies]
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] } actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
awc = { version = "3.0.0-beta.14", features = ["openssl"] } awc = { version = "3.0.0-beta.11", features = ["openssl"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }

View File

@@ -6,10 +6,10 @@
<p> <p>
[![crates.io](https://img.shields.io/crates/v/actix-web?label=latest)](https://crates.io/crates/actix-web) [![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.15)](https://docs.rs/actix-web/4.0.0-beta.15) [![Documentation](https://docs.rs/actix-web/badge.svg?version=4.0.0-beta.13)](https://docs.rs/actix-web/4.0.0-beta.13)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) [![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) ![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.15/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.15) [![Dependency Status](https://deps.rs/crate/actix-web/4.0.0-beta.13/status.svg)](https://deps.rs/crate/actix-web/4.0.0-beta.13)
<br /> <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) [![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) [![codecov](https://codecov.io/gh/actix/actix-web/branch/master/graph/badge.svg)](https://codecov.io/gh/actix/actix-web)

View File

@@ -3,10 +3,6 @@
## Unreleased - 2021-xx-xx ## 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 ## 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 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] * Add `NamedFile::open_async`. [#2408]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-files" name = "actix-files"
version = "0.6.0-beta.10" version = "0.6.0-beta.9"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@@ -22,17 +22,17 @@ path = "src/lib.rs"
experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"] experimental-io-uring = ["actix-web/experimental-io-uring", "tokio-uring"]
[dependencies] [dependencies]
actix-http = "3.0.0-beta.16" actix-web = { version = "4.0.0-beta.11", default-features = false }
actix-http = "3.0.0-beta.14"
actix-service = "2" actix-service = "2"
actix-utils = "3" actix-utils = "3"
actix-web = { version = "4.0.0-beta.15", default-features = false }
askama_escape = "0.10" askama_escape = "0.10"
bitflags = "1" bitflags = "1"
bytes = "1" bytes = "1"
derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
http-range = "0.1.4" http-range = "0.1.4"
derive_more = "0.99.5"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
mime_guess = "2.0.1" mime_guess = "2.0.1"
@@ -43,5 +43,5 @@ tokio-uring = { version = "0.1", optional = true }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.9" actix-web = "4.0.0-beta.11"
actix-web = "4.0.0-beta.15" actix-test = "0.1.0-beta.7"

View File

@@ -3,11 +3,11 @@
> Static file serving for Actix Web > Static file serving for Actix Web
[![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files) [![crates.io](https://img.shields.io/crates/v/actix-files?label=latest)](https://crates.io/crates/actix-files)
[![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.10)](https://docs.rs/actix-files/0.6.0-beta.10) [![Documentation](https://docs.rs/actix-files/badge.svg?version=0.6.0-beta.9)](https://docs.rs/actix-files/0.6.0-beta.9)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) [![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) ![License](https://img.shields.io/crates/l/actix-files.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.10/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.10) [![dependency status](https://deps.rs/crate/actix-files/0.6.0-beta.9/status.svg)](https://deps.rs/crate/actix-files/0.6.0-beta.9)
[![Download](https://img.shields.io/crates/d/actix-files.svg)](https://crates.io/crates/actix-files) [![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) [![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 self
} }
/// See [`Files::method_guard`].
#[doc(hidden)] #[doc(hidden)]
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")] #[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 { pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
self.method_guard(guard) self.method_guard(guard)
} }

View File

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

View File

@@ -2,10 +2,14 @@ use std::{
fmt, fmt,
fs::Metadata, fs::Metadata,
io, io,
ops::{Deref, DerefMut},
path::{Path, PathBuf}, path::{Path, PathBuf},
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
#[cfg(unix)]
use std::os::unix::fs::MetadataExt;
use actix_service::{Service, ServiceFactory}; use actix_service::{Service, ServiceFactory};
use actix_web::{ use actix_web::{
body::{self, BoxBody, SizedStream}, body::{self, BoxBody, SizedStream},
@@ -23,7 +27,6 @@ use actix_web::{
Error, HttpMessage, HttpRequest, HttpResponse, Responder, Error, HttpMessage, HttpRequest, HttpResponse, Responder,
}; };
use bitflags::bitflags; use bitflags::bitflags;
use derive_more::{Deref, DerefMut};
use futures_core::future::LocalBoxFuture; use futures_core::future::LocalBoxFuture;
use mime_guess::from_path; use mime_guess::from_path;
@@ -68,11 +71,8 @@ impl Default for Flags {
/// NamedFile::open_async("./static/index.html").await /// NamedFile::open_async("./static/index.html").await
/// } /// }
/// ``` /// ```
#[derive(Deref, DerefMut)]
pub struct NamedFile { pub struct NamedFile {
path: PathBuf, path: PathBuf,
#[deref]
#[deref_mut]
file: File, file: File,
modified: Option<SystemTime>, modified: Option<SystemTime>,
pub(crate) md: Metadata, pub(crate) md: Metadata,
@@ -364,18 +364,14 @@ impl NamedFile {
self self
} }
/// Creates a etag in a format is similar to Apache's.
pub(crate) fn etag(&self) -> Option<header::EntityTag> { pub(crate) fn etag(&self) -> Option<header::EntityTag> {
// This etag format is similar to Apache's.
self.modified.as_ref().map(|mtime| { self.modified.as_ref().map(|mtime| {
let ino = { let ino = {
#[cfg(unix)] #[cfg(unix)]
{ {
#[cfg(unix)]
use std::os::unix::fs::MetadataExt as _;
self.md.ino() self.md.ino()
} }
#[cfg(not(unix))] #[cfg(not(unix))]
{ {
0 0
@@ -476,17 +472,17 @@ impl NamedFile {
false false
}; };
let mut res = HttpResponse::build(self.status_code); let mut resp = HttpResponse::build(self.status_code);
if self.flags.contains(Flags::PREFER_UTF8) { if self.flags.contains(Flags::PREFER_UTF8) {
let ct = equiv_utf8_text(self.content_type.clone()); let ct = equiv_utf8_text(self.content_type.clone());
res.insert_header((header::CONTENT_TYPE, ct.to_string())); resp.insert_header((header::CONTENT_TYPE, ct.to_string()));
} else { } else {
res.insert_header((header::CONTENT_TYPE, self.content_type.to_string())); resp.insert_header((header::CONTENT_TYPE, self.content_type.to_string()));
} }
if self.flags.contains(Flags::CONTENT_DISPOSITION) { if self.flags.contains(Flags::CONTENT_DISPOSITION) {
res.insert_header(( resp.insert_header((
header::CONTENT_DISPOSITION, header::CONTENT_DISPOSITION,
self.content_disposition.to_string(), self.content_disposition.to_string(),
)); ));
@@ -494,18 +490,18 @@ impl NamedFile {
// default compressing // default compressing
if let Some(current_encoding) = self.encoding { if let Some(current_encoding) = self.encoding {
res.encoding(current_encoding); resp.encoding(current_encoding);
} }
if let Some(lm) = last_modified { if let Some(lm) = last_modified {
res.insert_header((header::LAST_MODIFIED, lm.to_string())); resp.insert_header((header::LAST_MODIFIED, lm.to_string()));
} }
if let Some(etag) = etag { if let Some(etag) = etag {
res.insert_header((header::ETAG, etag.to_string())); resp.insert_header((header::ETAG, etag.to_string()));
} }
res.insert_header((header::ACCEPT_RANGES, "bytes")); resp.insert_header((header::ACCEPT_RANGES, "bytes"));
let mut length = self.md.len(); let mut length = self.md.len();
let mut offset = 0; let mut offset = 0;
@@ -517,24 +513,24 @@ impl NamedFile {
length = ranges[0].length; length = ranges[0].length;
offset = ranges[0].start; offset = ranges[0].start;
res.encoding(ContentEncoding::Identity); resp.encoding(ContentEncoding::Identity);
res.insert_header(( resp.insert_header((
header::CONTENT_RANGE, header::CONTENT_RANGE,
format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()), format!("bytes {}-{}/{}", offset, offset + length - 1, self.md.len()),
)); ));
} else { } else {
res.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length))); resp.insert_header((header::CONTENT_RANGE, format!("bytes */{}", length)));
return res.status(StatusCode::RANGE_NOT_SATISFIABLE).finish(); return resp.status(StatusCode::RANGE_NOT_SATISFIABLE).finish();
}; };
} else { } else {
return res.status(StatusCode::BAD_REQUEST).finish(); return resp.status(StatusCode::BAD_REQUEST).finish();
}; };
}; };
if precondition_failed { if precondition_failed {
return res.status(StatusCode::PRECONDITION_FAILED).finish(); return resp.status(StatusCode::PRECONDITION_FAILED).finish();
} else if not_modified { } else if not_modified {
return res return resp
.status(StatusCode::NOT_MODIFIED) .status(StatusCode::NOT_MODIFIED)
.body(body::None::new()) .body(body::None::new())
.map_into_boxed_body(); .map_into_boxed_body();
@@ -543,10 +539,10 @@ impl NamedFile {
let reader = chunked::new_chunked_read(length, offset, self.file); let reader = chunked::new_chunked_read(length, offset, self.file);
if offset != 0 || length != self.md.len() { if offset != 0 || length != self.md.len() {
res.status(StatusCode::PARTIAL_CONTENT); resp.status(StatusCode::PARTIAL_CONTENT);
} }
res.body(SizedStream::new(length, reader)) resp.body(SizedStream::new(length, reader))
} }
} }
@@ -590,6 +586,20 @@ fn none_match(etag: Option<&header::EntityTag>, req: &HttpRequest) -> bool {
} }
} }
impl Deref for NamedFile {
type Target = File;
fn deref(&self) -> &Self::Target {
&self.file
}
}
impl DerefMut for NamedFile {
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.file
}
}
impl Responder for NamedFile { impl Responder for NamedFile {
type Body = BoxBody; type Body = BoxBody;

View File

@@ -3,10 +3,6 @@
## Unreleased - 2021-xx-xx ## 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 ## 3.0.0-beta.8 - 2021-11-30
* Update `actix-tls` to `3.0.0-rc.1`. [#2474] * Update `actix-tls` to `3.0.0-rc.1`. [#2474]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-http-test" name = "actix-http-test"
version = "3.0.0-beta.9" version = "3.0.0-beta.8"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Various helpers for Actix applications to use during testing" description = "Various helpers for Actix applications to use during testing"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
@@ -35,7 +35,7 @@ actix-tls = "3.0.0-rc.1"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-rt = "2.2" actix-rt = "2.2"
actix-server = "2.0.0-rc.1" actix-server = "2.0.0-rc.1"
awc = { version = "3.0.0-beta.14", default-features = false } awc = { version = "3.0.0-beta.11", default-features = false }
base64 = "0.13" base64 = "0.13"
bytes = "1" bytes = "1"
@@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
tokio = { version = "1.2", features = ["sync"] } tokio = { version = "1.2", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
actix-http = "3.0.0-beta.16" actix-http = "3.0.0-beta.14"

View File

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

View File

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

View File

@@ -1,25 +1,6 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.16 - 2021-12-17
### Added
* New method on `MessageBody` trait, `try_into_bytes`, with default implementation, for optimizations on body types that complete in exactly one poll. Replaces `is_complete_body` and `take_complete_body`. [#2522]
### Changed
* Rename trait `IntoHeaderPair => TryIntoHeaderPair`. [#2510]
* Rename `TryIntoHeaderPair::{try_into_header_pair => try_into_pair}`. [#2510]
* Rename trait `IntoHeaderValue => TryIntoHeaderValue`. [#2510]
### Removed
* `MessageBody::{is_complete_body,take_complete_body}`. [#2522]
[#2510]: https://github.com/actix/actix-web/pull/2510
[#2522]: https://github.com/actix/actix-web/pull/2522
## 3.0.0-beta.15 - 2021-12-11
### Added ### Added
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483] * 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] * HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
@@ -33,12 +14,7 @@
* `header::QualityItem::{max, min}`. [#2486] * `header::QualityItem::{max, min}`. [#2486]
* `header::Quality::{MAX, MIN}`. [#2486] * `header::Quality::{MAX, MIN}`. [#2486]
* `impl Display` for `header::Quality`. [#2486] * `impl Display` for `header::Quality`. [#2486]
* Connection data set through the `on_connect_ext` callbacks is now accessible only from the new `Request::conn_data()` method. [#2491] * `CloneableExtensions` object for use in `on_connect` handlers. [#2327]
* `Request::take_conn_data()`. [#2491]
* `Request::take_req_data()`. [#2487]
* `impl Clone` for `RequestHead`. [#2487]
* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimizations on body types that are done in exactly one poll/chunk. [#2497]
* New `boxed` method on `MessageBody` trait for wrapping body type. [#2520]
### Changed ### Changed
* Rename `body::BoxBody::{from_body => new}`. [#2468] * Rename `body::BoxBody::{from_body => new}`. [#2468]
@@ -48,6 +24,7 @@
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468] * `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468] * `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468] * `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
* `on_connect_ext` methods now receive a `CloneableExtensions` object. [#2327]
### Removed ### Removed
* `ResponseBuilder::streaming`. [#2468] * `ResponseBuilder::streaming`. [#2468]
@@ -59,15 +36,12 @@
* `impl TryFrom<u16>` for `header::Quality`. [#2486] * `impl TryFrom<u16>` for `header::Quality`. [#2486]
* `http` module. Most everything it contained is exported at the crate root. [#2488] * `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 [#2483]: https://github.com/actix/actix-web/pull/2483
[#2468]: https://github.com/actix/actix-web/pull/2468 [#2468]: https://github.com/actix/actix-web/pull/2468
[#1920]: https://github.com/actix/actix-web/pull/1920 [#1920]: https://github.com/actix/actix-web/pull/1920
[#2486]: https://github.com/actix/actix-web/pull/2486 [#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 [#2488]: https://github.com/actix/actix-web/pull/2488
[#2491]: https://github.com/actix/actix-web/pull/2491
[#2497]: https://github.com/actix/actix-web/pull/2497
[#2520]: https://github.com/actix/actix-web/pull/2520
## 3.0.0-beta.14 - 2021-11-30 ## 3.0.0-beta.14 - 2021-11-30
@@ -278,7 +252,7 @@
## 3.0.0-beta.2 - 2021-02-10 ## 3.0.0-beta.2 - 2021-02-10
### Added ### Added
* `TryIntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869] * `IntoHeaderPair` trait that allows using typed and untyped headers in the same methods. [#1869]
* `ResponseBuilder::insert_header` method which allows using typed headers. [#1869] * `ResponseBuilder::insert_header` method which allows using typed headers. [#1869]
* `ResponseBuilder::append_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] * `TestRequest::insert_header` method which allows using typed headers. [#1869]
@@ -289,9 +263,9 @@
* `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969] * `trust-dns` optional feature to enable `trust-dns-resolver` as client dns resolver. [#1969]
### Changed ### Changed
* `ResponseBuilder::content_type` now takes an `impl TryIntoHeaderValue` to support using typed * `ResponseBuilder::content_type` now takes an `impl IntoHeaderValue` to support using typed
`mime` types. [#1894] `mime` types. [#1894]
* Renamed `TryIntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std * Renamed `IntoHeaderValue::{try_into => try_into_value}` to avoid ambiguity with std
`TryInto` trait. [#1894] `TryInto` trait. [#1894]
* `Extensions::insert` returns Option of replaced item. [#1904] * `Extensions::insert` returns Option of replaced item. [#1904]
* Remove `HttpResponseBuilder::json2()`. [#1903] * Remove `HttpResponseBuilder::json2()`. [#1903]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-http" name = "actix-http"
version = "3.0.0-beta.16" version = "3.0.0-beta.14"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "HTTP primitives for the Actix ecosystem" description = "HTTP primitives for the Actix ecosystem"
keywords = ["actix", "http", "framework", "async", "futures"] keywords = ["actix", "http", "framework", "async", "futures"]
@@ -45,7 +45,7 @@ __compress = []
actix-service = "2.0.0" actix-service = "2.0.0"
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-rt = { version = "2.2", default-features = false } actix-rt = "2.2"
ahash = "0.7" ahash = "0.7"
base64 = "0.13" base64 = "0.13"
@@ -55,8 +55,8 @@ bytestring = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
encoding_rs = "0.8" encoding_rs = "0.8"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
futures-task = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
h2 = "0.3.9" h2 = "0.3.1"
http = "0.2.5" http = "0.2.5"
httparse = "1.5.1" httparse = "1.5.1"
httpdate = "1.0.1" httpdate = "1.0.1"
@@ -66,6 +66,7 @@ local-channel = "0.1"
log = "0.4" log = "0.4"
mime = "0.3" mime = "0.3"
percent-encoding = "2.1" percent-encoding = "2.1"
pin-project = "1.0.0"
pin-project-lite = "0.2" pin-project-lite = "0.2"
rand = "0.8" rand = "0.8"
sha-1 = "0.9" sha-1 = "0.9"
@@ -80,15 +81,12 @@ flate2 = { version = "1.0.13", optional = true }
zstd = { version = "0.9", optional = true } zstd = { version = "0.9", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
actix-server = "2.0.0-rc.1" 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-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
actix-web = "4.0.0-beta.15"
async-stream = "0.3" async-stream = "0.3"
criterion = { version = "0.3", features = ["html_reports"] } criterion = { version = "0.3", features = ["html_reports"] }
env_logger = "0.9" env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
rcgen = "0.8" rcgen = "0.8"
regex = "1.3" regex = "1.3"
rustls-pemfile = "0.2" rustls-pemfile = "0.2"
@@ -97,7 +95,7 @@ serde_json = "1.0"
static_assertions = "1" static_assertions = "1"
tls-openssl = { package = "openssl", version = "0.10.9" } tls-openssl = { package = "openssl", version = "0.10.9" }
tls-rustls = { package = "rustls", version = "0.20.0" } tls-rustls = { package = "rustls", version = "0.20.0" }
tokio = { version = "1.2", features = ["net", "rt", "macros"] } tokio = { version = "1.2", features = ["net", "rt"] }
[[example]] [[example]]
name = "ws" name = "ws"

View File

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

View File

@@ -189,7 +189,11 @@ mod _original {
n /= 100; n /= 100;
curr -= 2; curr -= 2;
unsafe { 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 // decode last 1 or 2 chars
@@ -202,7 +206,11 @@ mod _original {
let d1 = n << 1; let d1 = n << 1;
curr -= 2; curr -= 2;
unsafe { 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,10 +54,15 @@ const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
value: (0, 0), 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 { 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; let bytes_ptr = bytes.as_ptr() as usize;
for (header, indices) in headers.iter().zip(indices.iter_mut()) { for (header, indices) in headers.iter().zip(indices.iter_mut()) {
let name_start = header.name.as_ptr() as usize - bytes_ptr; let name_start = header.name.as_ptr() as usize - bytes_ptr;

View File

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

View File

@@ -1,7 +1,8 @@
use std::io; use std::io;
use actix_http::{ 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 actix_server::Server;
use bytes::BytesMut; use bytes::BytesMut;

View File

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

View File

@@ -60,7 +60,10 @@ impl Heartbeat {
impl Stream for Heartbeat { impl Stream for Heartbeat {
type Item = Result<Bytes, Error>; 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"); log::trace!("poll");
ready!(self.as_mut().interval.poll_tick(cx)); ready!(self.as_mut().interval.poll_tick(cx));

5
actix-http/rustfmt.toml Normal file
View File

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

View File

@@ -27,7 +27,6 @@ where
S: Stream<Item = Result<Bytes, E>>, S: Stream<Item = Result<Bytes, E>>,
E: Into<Box<dyn StdError>> + 'static, E: Into<Box<dyn StdError>> + 'static,
{ {
#[inline]
pub fn new(stream: S) -> Self { pub fn new(stream: S) -> Self {
BodyStream { stream } BodyStream { stream }
} }
@@ -40,7 +39,6 @@ where
{ {
type Error = E; type Error = E;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
BodySize::Stream BodySize::Stream
} }
@@ -167,7 +165,8 @@ mod tests {
#[actix_rt::test] #[actix_rt::test]
async fn stream_delayed_error() { 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))); assert!(matches!(to_bytes(body).await, Err(StreamErr)));
pin_project! { pin_project! {

View File

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

View File

@@ -23,7 +23,6 @@ pin_project! {
impl<L> EitherBody<L, BoxBody> { impl<L> EitherBody<L, BoxBody> {
/// Creates new `EitherBody` using left variant and boxed right variant. /// Creates new `EitherBody` using left variant and boxed right variant.
#[inline]
pub fn new(body: L) -> Self { pub fn new(body: L) -> Self {
Self::Left { body } Self::Left { body }
} }
@@ -31,13 +30,11 @@ impl<L> EitherBody<L, BoxBody> {
impl<L, R> EitherBody<L, R> { impl<L, R> EitherBody<L, R> {
/// Creates new `EitherBody` using left variant. /// Creates new `EitherBody` using left variant.
#[inline]
pub fn left(body: L) -> Self { pub fn left(body: L) -> Self {
Self::Left { body } Self::Left { body }
} }
/// Creates new `EitherBody` using right variant. /// Creates new `EitherBody` using right variant.
#[inline]
pub fn right(body: R) -> Self { pub fn right(body: R) -> Self {
Self::Right { body } Self::Right { body }
} }
@@ -50,7 +47,6 @@ where
{ {
type Error = Error; type Error = Error;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
match self { match self {
EitherBody::Left { body } => body.size(), EitherBody::Left { body } => body.size(),
@@ -58,7 +54,6 @@ where
} }
} }
#[inline]
fn poll_next( fn poll_next(
self: Pin<&mut Self>, self: Pin<&mut Self>,
cx: &mut Context<'_>, cx: &mut Context<'_>,
@@ -72,26 +67,6 @@ where
.map_err(|err| Error::new_body().with_cause(err)), .map_err(|err| Error::new_body().with_cause(err)),
} }
} }
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self> {
match self {
EitherBody::Left { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Left { body }),
EitherBody::Right { body } => body
.try_into_bytes()
.map_err(|body| EitherBody::Right { body }),
}
}
#[inline]
fn boxed(self) -> BoxBody {
match self {
EitherBody::Left { body } => body.boxed(),
EitherBody::Right { body } => body.boxed(),
}
}
} }
#[cfg(test)] #[cfg(test)]

View File

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

View File

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

View File

@@ -1,11 +1,9 @@
/// Body size hint. /// Body size hint.
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq)]
pub enum BodySize { pub enum BodySize {
/// Implicitly empty body. /// Absence of body can be assumed from method or status code.
/// ///
/// Will omit the Content-Length header. Used for responses to certain methods (e.g., `HEAD`) or /// Will skip writing Content-Length header.
/// 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, None,
/// Known size body. /// Known size body.
@@ -20,9 +18,6 @@ pub enum BodySize {
} }
impl 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. /// Returns true if size hint indicates omitted or empty body.
/// ///
/// Streams will return false because it cannot be known without reading the stream. /// Streams will return false because it cannot be known without reading the stream.

View File

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

View File

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

View File

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

View File

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

View File

@@ -25,7 +25,7 @@ use zstd::stream::write::Encoder as ZstdEncoder;
use super::Writer; use super::Writer;
use crate::{ use crate::{
body::{self, BodySize, MessageBody}, body::{BodySize, MessageBody},
error::BlockingError, error::BlockingError,
header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING}, header::{self, ContentEncoding, HeaderValue, CONTENT_ENCODING},
ResponseHead, StatusCode, ResponseHead, StatusCode,
@@ -46,39 +46,43 @@ pin_project! {
impl<B: MessageBody> Encoder<B> { impl<B: MessageBody> Encoder<B> {
fn none() -> Self { fn none() -> Self {
Encoder { Encoder {
body: EncoderBody::None { body: EncoderBody::None,
body: body::None::new(),
},
encoder: None, encoder: None,
fut: None, fut: None,
eof: true, eof: true,
} }
} }
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, body: B) -> Self { pub fn response(
encoding: ContentEncoding,
head: &mut ResponseHead,
body: B,
) -> Self {
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING) let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|| head.status == StatusCode::SWITCHING_PROTOCOLS || head.status == StatusCode::SWITCHING_PROTOCOLS
|| head.status == StatusCode::NO_CONTENT || head.status == StatusCode::NO_CONTENT
|| encoding == ContentEncoding::Identity || encoding == ContentEncoding::Identity
|| encoding == ContentEncoding::Auto); || encoding == ContentEncoding::Auto);
// no need to compress an empty body match body.size() {
if matches!(body.size(), BodySize::None) { // no need to compress an empty body
return Self::none(); BodySize::None => return Self::none(),
// we cannot assume that Sized is not a stream
BodySize::Sized(_) | BodySize::Stream => {}
} }
let body = match body.try_into_bytes() { // TODO potentially some optimisation for single-chunk responses here by trying to read the
Ok(body) => EncoderBody::Full { body }, // payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
Err(body) => EncoderBody::Stream { body },
};
if can_encode { if can_encode {
// Modify response body only if encoder is set // Modify response body only if encoder is set
if let Some(enc) = ContentEncoder::encoder(encoding) { if let Some(enc) = ContentEncoder::encoder(encoding) {
update_head(encoding, head); update_head(encoding, head);
head.no_chunking(false);
return Encoder { return Encoder {
body, body: EncoderBody::Stream { body },
encoder: Some(enc), encoder: Some(enc),
fut: None, fut: None,
eof: false, eof: false,
@@ -87,7 +91,7 @@ impl<B: MessageBody> Encoder<B> {
} }
Encoder { Encoder {
body, body: EncoderBody::Stream { body },
encoder: None, encoder: None,
fut: None, fut: None,
eof: false, eof: false,
@@ -98,8 +102,7 @@ impl<B: MessageBody> Encoder<B> {
pin_project! { pin_project! {
#[project = EncoderBodyProj] #[project = EncoderBodyProj]
enum EncoderBody<B> { enum EncoderBody<B> {
None { body: body::None }, None,
Full { body: Bytes },
Stream { #[pin] body: B }, Stream { #[pin] body: B },
} }
} }
@@ -110,11 +113,9 @@ where
{ {
type Error = EncoderError; type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
match self { match self {
EncoderBody::None { body } => body.size(), EncoderBody::None => BodySize::None,
EncoderBody::Full { body } => body.size(),
EncoderBody::Stream { body } => body.size(), EncoderBody::Stream { body } => body.size(),
} }
} }
@@ -124,29 +125,13 @@ where
cx: &mut Context<'_>, cx: &mut Context<'_>,
) -> Poll<Option<Result<Bytes, Self::Error>>> { ) -> Poll<Option<Result<Bytes, Self::Error>>> {
match self.project() { match self.project() {
EncoderBodyProj::None { body } => { EncoderBodyProj::None => Poll::Ready(None),
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Full { body } => {
Pin::new(body).poll_next(cx).map_err(|err| match err {})
}
EncoderBodyProj::Stream { body } => body EncoderBodyProj::Stream { body } => body
.poll_next(cx) .poll_next(cx)
.map_err(|err| EncoderError::Body(err.into())), .map_err(|err| EncoderError::Body(err.into())),
} }
} }
#[inline]
fn try_into_bytes(self) -> Result<Bytes, Self>
where
Self: Sized,
{
match self {
EncoderBody::None { body } => Ok(body.try_into_bytes().unwrap()),
EncoderBody::Full { body } => Ok(body.try_into_bytes().unwrap()),
_ => Err(self),
}
}
} }
impl<B> MessageBody for Encoder<B> impl<B> MessageBody for Encoder<B>
@@ -155,12 +140,11 @@ where
{ {
type Error = EncoderError; type Error = EncoderError;
#[inline]
fn size(&self) -> BodySize { fn size(&self) -> BodySize {
if self.encoder.is_some() { if self.encoder.is_none() {
BodySize::Stream
} else {
self.body.size() self.body.size()
} else {
BodySize::Stream
} }
} }
@@ -231,24 +215,6 @@ where
} }
} }
} }
#[inline]
fn try_into_bytes(mut self) -> Result<Bytes, Self>
where
Self: Sized,
{
if self.encoder.is_some() {
Err(self)
} else {
match self.body.try_into_bytes() {
Ok(body) => Ok(body),
Err(body) => {
self.body = body;
Err(self)
}
}
}
}
} }
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) { fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
@@ -256,8 +222,6 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
header::CONTENT_ENCODING, header::CONTENT_ENCODING,
HeaderValue::from_static(encoding.as_str()), HeaderValue::from_static(encoding.as_str()),
); );
head.no_chunking(false);
} }
enum ContentEncoder { enum ContentEncoder {

View File

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

View File

@@ -19,7 +19,7 @@ impl Extensions {
#[inline] #[inline]
pub fn new() -> Extensions { pub fn new() -> Extensions {
Extensions { Extensions {
map: AHashMap::new(), map: AHashMap::default(),
} }
} }
@@ -122,6 +122,13 @@ impl Extensions {
pub fn extend(&mut self, other: Extensions) { pub fn extend(&mut self, other: Extensions) {
self.map.extend(other.map); 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 { impl fmt::Debug for Extensions {
@@ -134,6 +141,104 @@ fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
boxed.downcast().ok().map(|boxed| *boxed) 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)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
@@ -277,4 +382,43 @@ mod tests {
assert_eq!(extensions.get(), Some(&20u8)); assert_eq!(extensions.get(), Some(&20u8));
assert_eq!(extensions.get_mut(), Some(&mut 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,7 +50,10 @@ 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 radix = 16;
let rem = match byte!(rdr) { let rem = match byte!(rdr) {
@@ -108,7 +111,10 @@ impl ChunkedState {
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions _ => 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) { match byte!(rdr) {
b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)), b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)), b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),

View File

@@ -74,7 +74,8 @@ pub(crate) trait MessageType: Sized {
let headers = self.headers_mut(); let headers = self.headers_mut();
for idx in raw_headers.iter() { 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 // SAFETY: httparse already checks header value is only visible ASCII bytes
// from_maybe_shared_unchecked contains debug assertions so they are omitted here // from_maybe_shared_unchecked contains debug assertions so they are omitted here
@@ -604,7 +605,8 @@ mod tests {
#[test] #[test]
fn test_parse_body() { 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 mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
@@ -620,7 +622,8 @@ mod tests {
#[test] #[test]
fn test_parse_body_crlf() { 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 mut reader = MessageDecoder::<Request>::default();
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap(); let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();

View File

@@ -15,19 +15,18 @@ use bitflags::bitflags;
use bytes::{Buf, BytesMut}; use bytes::{Buf, BytesMut};
use futures_core::ready; use futures_core::ready;
use log::{error, trace}; use log::{error, trace};
use pin_project_lite::pin_project; use pin_project::pin_project;
use crate::{ use crate::{
body::{BodySize, BoxBody, MessageBody}, body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig, config::ServiceConfig,
error::{DispatchError, ParseError, PayloadError}, error::{DispatchError, ParseError, PayloadError},
service::HttpFlow, service::HttpFlow,
Error, Extensions, OnConnectData, Request, Response, StatusCode, OnConnectData, Request, Response, StatusCode,
}; };
use super::{ use super::{
codec::Codec, codec::Codec,
decoder::MAX_BUFFER_SIZE,
payload::{Payload, PayloadSender, PayloadStatus}, payload::{Payload, PayloadSender, PayloadStatus},
Message, MessageType, Message, MessageType,
}; };
@@ -46,111 +45,79 @@ bitflags! {
} }
} }
// there's 2 versions of Dispatcher state because of: #[pin_project]
// https://github.com/taiki-e/pin-project-lite/issues/3 /// Dispatcher for HTTP/1.1 protocol
// pub struct Dispatcher<T, S, B, X, U>
// tl;dr: pin-project-lite doesn't play well with other attribute macros where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
#[cfg(not(test))] B: MessageBody,
pin_project! {
/// Dispatcher for HTTP/1.1 protocol
pub struct Dispatcher<T, S, B, X, U>
where
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody, X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>,
X: Service<Request, Response = Request>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
X::Error: Into<Response<BoxBody>>, U::Error: fmt::Display,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, #[cfg(test)]
U::Error: fmt::Display, poll_count: u64,
{
#[pin]
inner: DispatcherState<T, S, B, X, U>,
}
} }
#[cfg(test)] #[pin_project(project = DispatcherStateProj)]
pin_project! { enum DispatcherState<T, S, B, X, U>
/// Dispatcher for HTTP/1.1 protocol where
pub struct Dispatcher<T, S, B, X, U> S: Service<Request>,
where S::Error: Into<Response<BoxBody>>,
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody, B: MessageBody,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>, X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
#[pin] Normal(#[pin] InnerDispatcher<T, S, B, X, U>),
inner: DispatcherState<T, S, B, X, U>, Upgrade(#[pin] U::Future),
// used in tests
poll_count: u64,
}
} }
pin_project! { #[pin_project(project = InnerDispatcherProj)]
#[project = DispatcherStateProj] struct InnerDispatcher<T, S, B, X, U>
enum DispatcherState<T, S, B, X, U> where
where S: Service<Request>,
S: Service<Request>, S::Error: Into<Response<BoxBody>>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody, B: MessageBody,
X: Service<Request, Response = Request>, X: Service<Request, Response = Request>,
X::Error: Into<Response<BoxBody>>, X::Error: Into<Response<BoxBody>>,
U: Service<(Request, Framed<T, Codec>), Response = ()>, U: Service<(Request, Framed<T, Codec>), Response = ()>,
U::Error: fmt::Display, U::Error: fmt::Display,
{ {
Normal { #[pin] inner: InnerDispatcher<T, S, B, X, U> }, flow: Rc<HttpFlow<S, X, U>>,
Upgrade { #[pin] fut: U::Future }, on_connect_data: OnConnectData,
} flags: Flags,
} peer_addr: Option<net::SocketAddr>,
error: Option<DispatchError>,
pin_project! { #[pin]
#[project = InnerDispatcherProj] state: State<S, B, X>,
struct InnerDispatcher<T, S, B, X, U> payload: Option<PayloadSender>,
where messages: VecDeque<DispatcherMessage>,
S: Service<Request>,
S::Error: Into<Response<BoxBody>>,
B: MessageBody, ka_expire: Instant,
#[pin]
ka_timer: Option<Sleep>,
X: Service<Request, Response = Request>, io: Option<T>,
X::Error: Into<Response<BoxBody>>, read_buf: BytesMut,
write_buf: BytesMut,
U: Service<(Request, Framed<T, Codec>), Response = ()>, codec: Codec,
U::Error: fmt::Display,
{
flow: Rc<HttpFlow<S, X, U>>,
flags: Flags,
peer_addr: Option<net::SocketAddr>,
conn_data: Option<Rc<Extensions>>,
error: Option<DispatchError>,
#[pin]
state: State<S, B, X>,
payload: Option<PayloadSender>,
messages: VecDeque<DispatcherMessage>,
ka_expire: Instant,
#[pin]
ka_timer: Option<Sleep>,
io: Option<T>,
read_buf: BytesMut,
write_buf: BytesMut,
codec: Codec,
}
} }
enum DispatcherMessage { enum DispatcherMessage {
@@ -159,21 +126,19 @@ enum DispatcherMessage {
Error(Response<()>), Error(Response<()>),
} }
pin_project! { #[pin_project(project = StateProj)]
#[project = StateProj] enum State<S, B, X>
enum State<S, B, X> where
where S: Service<Request>,
S: Service<Request>, X: Service<Request, Response = Request>,
X: Service<Request, Response = Request>,
B: MessageBody, B: MessageBody,
{ {
None, None,
ExpectCall { #[pin] fut: X::Future }, ExpectCall(#[pin] X::Future),
ServiceCall { #[pin] fut: S::Future }, ServiceCall(#[pin] S::Future),
SendPayload { #[pin] body: B }, SendPayload(#[pin] B),
SendErrorPayload { #[pin] body: BoxBody }, SendErrorPayload(#[pin] BoxBody),
}
} }
impl<S, B, X> State<S, B, X> impl<S, B, X> State<S, B, X>
@@ -214,10 +179,10 @@ where
/// Create HTTP/1 dispatcher. /// Create HTTP/1 dispatcher.
pub(crate) fn new( pub(crate) fn new(
io: T, io: T,
flow: Rc<HttpFlow<S, X, U>>,
config: ServiceConfig, config: ServiceConfig,
flow: Rc<HttpFlow<S, X, U>>,
on_connect_data: OnConnectData,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
) -> Self { ) -> Self {
let flags = if config.keep_alive_enabled() { let flags = if config.keep_alive_enabled() {
Flags::KEEPALIVE Flags::KEEPALIVE
@@ -232,27 +197,22 @@ where
}; };
Dispatcher { Dispatcher {
inner: DispatcherState::Normal { inner: DispatcherState::Normal(InnerDispatcher {
inner: InnerDispatcher { read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
flow, write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
flags, payload: None,
peer_addr, state: State::None,
conn_data: conn_data.0.map(Rc::new), error: None,
error: None, messages: VecDeque::new(),
io: Some(io),
state: State::None, codec: Codec::new(config),
payload: None, flow,
messages: VecDeque::new(), on_connect_data,
flags,
ka_expire, peer_addr,
ka_timer, ka_expire,
ka_timer,
io: Some(io), }),
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
codec: Codec::new(config),
},
},
#[cfg(test)] #[cfg(test)]
poll_count: 0, poll_count: 0,
@@ -296,7 +256,10 @@ 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 InnerDispatcherProj { io, write_buf, .. } = self.project();
let mut io = Pin::new(io.as_mut().unwrap()); let mut io = Pin::new(io.as_mut().unwrap());
@@ -306,7 +269,10 @@ where
while written < len { while written < len {
match io.as_mut().poll_write(cx, &write_buf[written..])? { match io.as_mut().poll_write(cx, &write_buf[written..])? {
Poll::Ready(0) => { 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::Ready(n) => written += n,
Poll::Pending => { Poll::Pending => {
@@ -352,7 +318,7 @@ where
let size = self.as_mut().send_response_inner(message, &body)?; let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size { let state = match size {
BodySize::None | BodySize::Sized(0) => State::None, BodySize::None | BodySize::Sized(0) => State::None,
_ => State::SendPayload { body }, _ => State::SendPayload(body),
}; };
self.project().state.set(state); self.project().state.set(state);
Ok(()) Ok(())
@@ -366,7 +332,7 @@ where
let size = self.as_mut().send_response_inner(message, &body)?; let size = self.as_mut().send_response_inner(message, &body)?;
let state = match size { let state = match size {
BodySize::None | BodySize::Sized(0) => State::None, BodySize::None | BodySize::Sized(0) => State::None,
_ => State::SendErrorPayload { body }, _ => State::SendErrorPayload(body),
}; };
self.project().state.set(state); self.project().state.set(state);
Ok(()) Ok(())
@@ -392,12 +358,12 @@ where
// Handle `EXPECT: 100-Continue` header // Handle `EXPECT: 100-Continue` header
if req.head().expect() { if req.head().expect() {
// set InnerDispatcher state and continue loop to poll it. // set InnerDispatcher state and continue loop to poll it.
let fut = this.flow.expect.call(req); let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall { fut }); this.state.set(State::ExpectCall(task));
} else { } else {
// the same as expect call. // the same as expect call.
let fut = this.flow.service.call(req); let task = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(task));
}; };
} }
@@ -417,7 +383,7 @@ where
// all messages are dealt with. // all messages are dealt with.
None => return Ok(PollResponse::DoNothing), None => return Ok(PollResponse::DoNothing),
}, },
StateProj::ServiceCall { fut } => match fut.poll(cx) { StateProj::ServiceCall(fut) => match fut.poll(cx) {
// service call resolved. send response. // service call resolved. send response.
Poll::Ready(Ok(res)) => { Poll::Ready(Ok(res)) => {
let (res, body) = res.into().replace_body(()); let (res, body) = res.into().replace_body(());
@@ -443,18 +409,21 @@ where
} }
}, },
StateProj::SendPayload { mut body } => { StateProj::SendPayload(mut stream) => {
// keep populate writer buffer until buffer size limit hit, // keep populate writer buffer until buffer size limit hit,
// get blocked or finished. // get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match body.as_mut().poll_next(cx) { match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => { Poll::Ready(Some(Ok(item))) => {
this.codec this.codec.encode(
.encode(Message::Chunk(Some(item)), this.write_buf)?; Message::Chunk(Some(item)),
this.write_buf,
)?;
} }
Poll::Ready(None) => { Poll::Ready(None) => {
this.codec.encode(Message::Chunk(None), this.write_buf)?; this.codec
.encode(Message::Chunk(None), this.write_buf)?;
// payload stream finished. // payload stream finished.
// set state to None and handle next message // set state to None and handle next message
this.state.set(State::None); this.state.set(State::None);
@@ -473,20 +442,23 @@ where
return Ok(PollResponse::DrainWriteBuf); return Ok(PollResponse::DrainWriteBuf);
} }
StateProj::SendErrorPayload { mut body } => { StateProj::SendErrorPayload(mut stream) => {
// TODO: de-dupe impl with SendPayload // TODO: de-dupe impl with SendPayload
// keep populate writer buffer until buffer size limit hit, // keep populate writer buffer until buffer size limit hit,
// get blocked or finished. // get blocked or finished.
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE { while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
match body.as_mut().poll_next(cx) { match stream.as_mut().poll_next(cx) {
Poll::Ready(Some(Ok(item))) => { Poll::Ready(Some(Ok(item))) => {
this.codec this.codec.encode(
.encode(Message::Chunk(Some(item)), this.write_buf)?; Message::Chunk(Some(item)),
this.write_buf,
)?;
} }
Poll::Ready(None) => { Poll::Ready(None) => {
this.codec.encode(Message::Chunk(None), this.write_buf)?; this.codec
.encode(Message::Chunk(None), this.write_buf)?;
// payload stream finished. // payload stream finished.
// set state to None and handle next message // set state to None and handle next message
this.state.set(State::None); this.state.set(State::None);
@@ -494,9 +466,7 @@ where
} }
Poll::Ready(Some(Err(err))) => { Poll::Ready(Some(Err(err))) => {
return Err(DispatchError::Body( return Err(DispatchError::Service(err.into()))
Error::new_body().with_cause(err).into(),
))
} }
Poll::Pending => return Ok(PollResponse::DoNothing), Poll::Pending => return Ok(PollResponse::DoNothing),
@@ -507,14 +477,14 @@ where
return Ok(PollResponse::DrainWriteBuf); return Ok(PollResponse::DrainWriteBuf);
} }
StateProj::ExpectCall { fut } => match fut.poll(cx) { StateProj::ExpectCall(fut) => match fut.poll(cx) {
// expect resolved. write continue to buffer and set InnerDispatcher state // expect resolved. write continue to buffer and set InnerDispatcher state
// to service call. // to service call.
Poll::Ready(Ok(req)) => { Poll::Ready(Ok(req)) => {
this.write_buf this.write_buf
.extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n"); .extend_from_slice(b"HTTP/1.1 100 Continue\r\n\r\n");
let fut = this.flow.service.call(req); let fut = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(fut));
} }
// send expect error as response // send expect error as response
@@ -540,25 +510,25 @@ where
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
if req.head().expect() { if req.head().expect() {
// set dispatcher state so the future is pinned. // set dispatcher state so the future is pinned.
let fut = this.flow.expect.call(req); let task = this.flow.expect.call(req);
this.state.set(State::ExpectCall { fut }); this.state.set(State::ExpectCall(task));
} else { } else {
// the same as above. // the same as above.
let fut = this.flow.service.call(req); let task = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(task));
}; };
// eagerly poll the future for once(or twice if expect is resolved immediately). // eagerly poll the future for once(or twice if expect is resolved immediately).
loop { loop {
match self.as_mut().project().state.project() { match self.as_mut().project().state.project() {
StateProj::ExpectCall { fut } => { StateProj::ExpectCall(fut) => {
match fut.poll(cx) { match fut.poll(cx) {
// expect is resolved. continue loop and poll the service call branch. // expect is resolved. continue loop and poll the service call branch.
Poll::Ready(Ok(req)) => { Poll::Ready(Ok(req)) => {
self.as_mut().send_continue(); self.as_mut().send_continue();
let mut this = self.as_mut().project(); let mut this = self.as_mut().project();
let fut = this.flow.service.call(req); let task = this.flow.service.call(req);
this.state.set(State::ServiceCall { fut }); this.state.set(State::ServiceCall(task));
continue; continue;
} }
// future is pending. return Ok(()) to notify that a new state is // future is pending. return Ok(()) to notify that a new state is
@@ -574,7 +544,7 @@ where
} }
} }
} }
StateProj::ServiceCall { fut } => { StateProj::ServiceCall(fut) => {
// return no matter the service call future's result. // return no matter the service call future's result.
return match fut.poll(cx) { return match fut.poll(cx) {
// future is resolved. send response and return a result. On success // future is resolved. send response and return a result. On success
@@ -594,11 +564,9 @@ where
} }
}; };
} }
_ => { _ => unreachable!(
unreachable!( "State must be set to ServiceCall or ExceptCall in handle_request"
"State must be set to ServiceCall or ExceptCall in handle_request" ),
)
}
} }
} }
} }
@@ -625,14 +593,16 @@ where
Message::Item(mut req) => { Message::Item(mut req) => {
req.head_mut().peer_addr = *this.peer_addr; req.head_mut().peer_addr = *this.peer_addr;
req.conn_data = this.conn_data.as_ref().map(Rc::clone); // merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req);
match this.codec.message_type() { match this.codec.message_type() {
// Request is upgradable. add upgrade message and break. // Request is upgradable. add upgrade message and break.
// everything remain in read buffer would be handed to // everything remain in read buffer would be handed to
// upgraded Request. // upgraded Request.
MessageType::Stream if this.flow.upgrade.is_some() => { MessageType::Stream if this.flow.upgrade.is_some() => {
this.messages.push_back(DispatcherMessage::Upgrade(req)); this.messages
.push_back(DispatcherMessage::Upgrade(req));
break; break;
} }
@@ -647,7 +617,8 @@ where
where the state can be collected and consumed. where the state can be collected and consumed.
*/ */
let (ps, pl) = Payload::create(false); 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; req = req1;
*this.payload = Some(ps); *this.payload = Some(ps);
} }
@@ -668,7 +639,9 @@ where
if let Some(ref mut payload) = this.payload { if let Some(ref mut payload) = this.payload {
payload.feed_data(chunk); payload.feed_data(chunk);
} else { } else {
error!("Internal server error: unexpected payload chunk"); error!(
"Internal server error: unexpected payload chunk"
);
this.flags.insert(Flags::READ_DISCONNECT); this.flags.insert(Flags::READ_DISCONNECT);
this.messages.push_back(DispatcherMessage::Error( this.messages.push_back(DispatcherMessage::Error(
Response::internal_server_error().drop_body(), Response::internal_server_error().drop_body(),
@@ -706,11 +679,12 @@ where
payload.set_error(PayloadError::Overflow); payload.set_error(PayloadError::Overflow);
} }
// Requests overflow buffer size should be responded with 431 // Requests overflow buffer size should be responded with 431
this.messages this.messages.push_back(DispatcherMessage::Error(
.push_back(DispatcherMessage::Error(Response::with_body( Response::with_body(
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE, StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
(), (),
))); ),
));
this.flags.insert(Flags::READ_DISCONNECT); this.flags.insert(Flags::READ_DISCONNECT);
*this.error = Some(ParseError::TooLarge.into()); *this.error = Some(ParseError::TooLarge.into());
break; break;
@@ -752,7 +726,8 @@ where
None => { None => {
// conditionally go into shutdown timeout // conditionally go into shutdown timeout
if this.flags.contains(Flags::SHUTDOWN) { 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 // write client disconnect time out and poll again to
// go into Some<Pin<&mut Sleep>> branch // go into Some<Pin<&mut Sleep>> branch
this.ka_timer.set(Some(sleep_until(deadline))); this.ka_timer.set(Some(sleep_until(deadline)));
@@ -795,7 +770,9 @@ where
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN); this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
} }
// still have unfinished task. try to reset and register keep-alive. // 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); timer.as_mut().reset(deadline);
let _ = timer.poll(cx); let _ = timer.poll(cx);
} }
@@ -814,6 +791,7 @@ where
/// Returns true when io stream can be disconnected after write to it. /// Returns true when io stream can be disconnected after write to it.
/// ///
/// It covers these conditions: /// It covers these conditions:
///
/// - `std::io::ErrorKind::ConnectionReset` after partial read. /// - `std::io::ErrorKind::ConnectionReset` after partial read.
/// - all data read done. /// - all data read done.
#[inline(always)] #[inline(always)]
@@ -833,39 +811,46 @@ where
loop { loop {
// Return early when read buf exceed decoder's max buffer size. // Return early when read buf exceed decoder's max buffer size.
if this.read_buf.len() >= 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. 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() { if this.payload.is_none() {
// When dispatcher has a payload the responsibility of wake up it would be shift /*
// to h1::payload::Payload. 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 Reason:
// over read. 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 Case:
// read anymore. At this case read_buf could always remain beyond When payload is (partial) dropped by user there is
// MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and no need to do read anymore.
// waste resources. At this case read_buf could always remain beyond
MAX_BUFFER_SIZE and self wake up would be busy poll
dispatcher and waste resource.
*/
cx.waker().wake_by_ref(); cx.waker().wake_by_ref();
} }
@@ -939,7 +924,7 @@ where
} }
match this.inner.project() { match this.inner.project() {
DispatcherStateProj::Normal { mut inner } => { DispatcherStateProj::Normal(mut inner) => {
inner.as_mut().poll_keepalive(cx)?; inner.as_mut().poll_keepalive(cx)?;
if inner.flags.contains(Flags::SHUTDOWN) { if inner.flags.contains(Flags::SHUTDOWN) {
@@ -979,7 +964,7 @@ where
self.as_mut() self.as_mut()
.project() .project()
.inner .inner
.set(DispatcherState::Upgrade { fut: upgrade }); .set(DispatcherState::Upgrade(upgrade));
return self.poll(cx); return self.poll(cx);
} }
}; };
@@ -1031,8 +1016,8 @@ where
} }
} }
} }
DispatcherStateProj::Upgrade { fut: upgrade } => upgrade.poll(cx).map_err(|err| { DispatcherStateProj::Upgrade(fut) => fut.poll(cx).map_err(|e| {
error!("Upgrade handler error: {}", err); error!("Upgrade handler error: {}", e);
DispatchError::Upgrade DispatchError::Upgrade
}), }),
} }
@@ -1073,12 +1058,14 @@ mod tests {
} }
fn ok_service( 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_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
} }
fn echo_path_service( 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| { fn_service(|req: Request| {
let path = req.path().as_bytes(); let path = req.path().as_bytes();
ready(Ok::<_, Error>( ready(Ok::<_, Error>(
@@ -1087,8 +1074,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| { fn_service(|mut req: Request| {
Box::pin(async move { Box::pin(async move {
use futures_util::stream::StreamExt as _; use futures_util::stream::StreamExt as _;
@@ -1113,10 +1100,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf, buf,
services,
ServiceConfig::default(), ServiceConfig::default(),
None, services,
OnConnectData::default(), OnConnectData::default(),
None,
); );
actix_rt::pin!(h1); actix_rt::pin!(h1);
@@ -1126,7 +1113,7 @@ mod tests {
Poll::Ready(res) => assert!(res.is_err()), Poll::Ready(res) => assert!(res.is_err()),
} }
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
assert!(inner.flags.contains(Flags::READ_DISCONNECT)); assert!(inner.flags.contains(Flags::READ_DISCONNECT));
assert_eq!( assert_eq!(
&inner.project().io.take().unwrap().write_buf[..26], &inner.project().io.take().unwrap().write_buf[..26],
@@ -1153,15 +1140,15 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf, buf,
services,
cfg, cfg,
None, services,
OnConnectData::default(), OnConnectData::default(),
None,
); );
actix_rt::pin!(h1); actix_rt::pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
match h1.as_mut().poll(cx) { match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"), Poll::Pending => panic!("first poll should not be pending"),
@@ -1171,7 +1158,7 @@ mod tests {
// polls: initial => shutdown // polls: initial => shutdown
assert_eq!(h1.poll_count, 2); assert_eq!(h1.poll_count, 2);
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
let res = &mut inner.project().io.take().unwrap().write_buf[..]; let res = &mut inner.project().io.take().unwrap().write_buf[..];
stabilize_date_header(res); stabilize_date_header(res);
@@ -1207,15 +1194,15 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf, buf,
services,
cfg, cfg,
None, services,
OnConnectData::default(), OnConnectData::default(),
None,
); );
actix_rt::pin!(h1); actix_rt::pin!(h1);
assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
match h1.as_mut().poll(cx) { match h1.as_mut().poll(cx) {
Poll::Pending => panic!("first poll should not be pending"), Poll::Pending => panic!("first poll should not be pending"),
@@ -1225,7 +1212,7 @@ mod tests {
// polls: initial => shutdown // polls: initial => shutdown
assert_eq!(h1.poll_count, 1); assert_eq!(h1.poll_count, 1);
if let DispatcherStateProj::Normal { inner } = h1.project().inner.project() { if let DispatcherStateProj::Normal(inner) = h1.project().inner.project() {
let res = &mut inner.project().io.take().unwrap().write_buf[..]; let res = &mut inner.project().io.take().unwrap().write_buf[..];
stabilize_date_header(res); stabilize_date_header(res);
@@ -1257,10 +1244,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(), buf.clone(),
services,
cfg, cfg,
None, services,
OnConnectData::default(), OnConnectData::default(),
None,
); );
buf.extend_read_buf( buf.extend_read_buf(
@@ -1275,13 +1262,13 @@ mod tests {
actix_rt::pin!(h1); actix_rt::pin!(h1);
assert!(h1.as_mut().poll(cx).is_pending()); assert!(h1.as_mut().poll(cx).is_pending());
assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
// polls: manual // polls: manual
assert_eq!(h1.poll_count, 1); assert_eq!(h1.poll_count, 1);
eprintln!("poll count: {}", h1.poll_count); eprintln!("poll count: {}", h1.poll_count);
if let DispatcherState::Normal { ref inner } = h1.inner { if let DispatcherState::Normal(ref inner) = h1.inner {
let io = inner.io.as_ref().unwrap(); let io = inner.io.as_ref().unwrap();
let res = &io.write_buf()[..]; let res = &io.write_buf()[..];
assert_eq!( assert_eq!(
@@ -1296,7 +1283,7 @@ mod tests {
// polls: manual manual shutdown // polls: manual manual shutdown
assert_eq!(h1.poll_count, 3); assert_eq!(h1.poll_count, 3);
if let DispatcherState::Normal { ref inner } = h1.inner { if let DispatcherState::Normal(ref inner) = h1.inner {
let io = inner.io.as_ref().unwrap(); let io = inner.io.as_ref().unwrap();
let mut res = (&io.write_buf()[..]).to_owned(); let mut res = (&io.write_buf()[..]).to_owned();
stabilize_date_header(&mut res); stabilize_date_header(&mut res);
@@ -1329,10 +1316,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new( let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
buf.clone(), buf.clone(),
services,
cfg, cfg,
None, services,
OnConnectData::default(), OnConnectData::default(),
None,
); );
buf.extend_read_buf( buf.extend_read_buf(
@@ -1347,12 +1334,12 @@ mod tests {
actix_rt::pin!(h1); actix_rt::pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready()); assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Normal { .. })); assert!(matches!(&h1.inner, DispatcherState::Normal(_)));
// polls: manual shutdown // polls: manual shutdown
assert_eq!(h1.poll_count, 2); assert_eq!(h1.poll_count, 2);
if let DispatcherState::Normal { ref inner } = h1.inner { if let DispatcherState::Normal(ref inner) = h1.inner {
let io = inner.io.as_ref().unwrap(); let io = inner.io.as_ref().unwrap();
let mut res = (&io.write_buf()[..]).to_owned(); let mut res = (&io.write_buf()[..]).to_owned();
stabilize_date_header(&mut res); stabilize_date_header(&mut res);
@@ -1406,10 +1393,10 @@ mod tests {
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new( let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
buf.clone(), buf.clone(),
services,
cfg, cfg,
None, services,
OnConnectData::default(), OnConnectData::default(),
None,
); );
buf.extend_read_buf( buf.extend_read_buf(
@@ -1424,7 +1411,7 @@ mod tests {
actix_rt::pin!(h1); actix_rt::pin!(h1);
assert!(h1.as_mut().poll(cx).is_ready()); assert!(h1.as_mut().poll(cx).is_ready());
assert!(matches!(&h1.inner, DispatcherState::Upgrade { .. })); assert!(matches!(&h1.inner, DispatcherState::Upgrade(_)));
// polls: manual shutdown // polls: manual shutdown
assert_eq!(h1.poll_count, 2); assert_eq!(h1.poll_count, 2);

View File

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

View File

@@ -227,7 +227,10 @@ impl Inner {
self.len 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() { if let Some(data) = self.items.pop_front() {
self.len -= data.len(); self.len -= data.len();
self.need_read = self.len < MAX_BUFFER_SIZE; self.need_read = self.len < MAX_BUFFER_SIZE;

View File

@@ -266,7 +266,8 @@ 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 where
T: AsyncRead + AsyncWrite + Unpin + 'static, T: AsyncRead + AsyncWrite + Unpin + 'static,
@@ -309,9 +310,9 @@ where
let upgrade = match upgrade { let upgrade = match upgrade {
Some(upgrade) => { Some(upgrade) => {
let upgrade = upgrade let upgrade = upgrade.await.map_err(|e| {
.await log::error!("Init http upgrade service error: {:?}", e)
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; })?;
Some(upgrade) Some(upgrade)
} }
None => None, None => None,
@@ -335,7 +336,8 @@ where
/// `Service` implementation for HTTP/1 transport /// `Service` implementation for HTTP/1 transport
pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>; 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 where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
@@ -356,14 +358,22 @@ where
type Future = Dispatcher<T, S, B, X, U>; type Future = Dispatcher<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self._poll_ready(cx).map_err(|err| { self._poll_ready(cx).map_err(|e| {
log::error!("HTTP/1 service readiness error: {:?}", err); log::error!("HTTP/1 service readiness error: {:?}", e);
DispatchError::Service(err) DispatchError::Service(e)
}) })
} }
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future { fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref()); let on_connect_data =
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data) OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
Dispatcher::new(
io,
self.cfg.clone(),
self.flow.clone(),
on_connect_data,
addr,
)
} }
} }

View File

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

View File

@@ -19,15 +19,15 @@ use h2::{
server::{Connection, SendResponse}, server::{Connection, SendResponse},
Ping, PingPong, Ping, PingPong,
}; };
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
use log::{error, trace}; use log::{error, trace};
use pin_project_lite::pin_project; use pin_project_lite::pin_project;
use crate::{ use crate::{
body::{BodySize, BoxBody, MessageBody}, body::{BodySize, BoxBody, MessageBody},
config::ServiceConfig, config::ServiceConfig,
header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
service::HttpFlow, service::HttpFlow,
Extensions, OnConnectData, Payload, Request, Response, ResponseHead, OnConnectData, Payload, Request, Response, ResponseHead,
}; };
const CHUNK_SIZE: usize = 16_384; const CHUNK_SIZE: usize = 16_384;
@@ -37,7 +37,7 @@ pin_project! {
pub struct Dispatcher<T, S, B, X, U> { pub struct Dispatcher<T, S, B, X, U> {
flow: Rc<HttpFlow<S, X, U>>, flow: Rc<HttpFlow<S, X, U>>,
connection: Connection<T, Bytes>, connection: Connection<T, Bytes>,
conn_data: Option<Rc<Extensions>>, on_connect_data: OnConnectData,
config: ServiceConfig, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
ping_pong: Option<H2PingPong>, ping_pong: Option<H2PingPong>,
@@ -50,11 +50,11 @@ where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
{ {
pub(crate) fn new( pub(crate) fn new(
mut conn: Connection<T, Bytes>,
flow: Rc<HttpFlow<S, X, U>>, flow: Rc<HttpFlow<S, X, U>>,
mut conn: Connection<T, Bytes>,
on_connect_data: OnConnectData,
config: ServiceConfig, config: ServiceConfig,
peer_addr: Option<net::SocketAddr>, peer_addr: Option<net::SocketAddr>,
conn_data: OnConnectData,
timer: Option<Pin<Box<Sleep>>>, timer: Option<Pin<Box<Sleep>>>,
) -> Self { ) -> Self {
let ping_pong = config.keep_alive().map(|dur| H2PingPong { let ping_pong = config.keep_alive().map(|dur| H2PingPong {
@@ -74,7 +74,7 @@ where
config, config,
peer_addr, peer_addr,
connection: conn, connection: conn,
conn_data: conn_data.0.map(Rc::new), on_connect_data,
ping_pong, ping_pong,
_phantom: PhantomData, _phantom: PhantomData,
} }
@@ -109,7 +109,7 @@ where
Poll::Ready(Some((req, tx))) => { Poll::Ready(Some((req, tx))) => {
let (parts, body) = req.into_parts(); let (parts, body) = req.into_parts();
let pl = crate::h2::Payload::new(body); let pl = crate::h2::Payload::new(body);
let pl = Payload::H2(pl); let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
let mut req = Request::with_payload(pl); let mut req = Request::with_payload(pl);
let head = req.head_mut(); let head = req.head_mut();
@@ -119,7 +119,8 @@ where
head.headers = parts.headers.into(); head.headers = parts.headers.into();
head.peer_addr = this.peer_addr; head.peer_addr = this.peer_addr;
req.conn_data = this.conn_data.as_ref().map(Rc::clone); // merge on_connect_ext data into request extensions
this.on_connect_data.merge_into(&mut req);
let fut = this.flow.service.call(req); let fut = this.flow.service.call(req);
let config = this.config.clone(); let config = this.config.clone();
@@ -160,11 +161,16 @@ where
Poll::Ready(_) => { Poll::Ready(_) => {
ping_pong.on_flight = false; 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); ping_pong.timer.as_mut().reset(dead_line);
} }
Poll::Pending => { Poll::Pending => {
return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(())) return ping_pong
.timer
.as_mut()
.poll(cx)
.map(|_| Ok(()))
} }
} }
} else { } else {
@@ -217,28 +223,25 @@ where
return Ok(()); return Ok(());
} }
// poll response body and send chunks to client // poll response body and send chunks to client.
actix_rt::pin!(body); actix_rt::pin!(body);
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await { 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()))?; let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;
'send: loop { 'send: loop {
let chunk_size = cmp::min(chunk.len(), CHUNK_SIZE);
// reserve enough space and wait for stream ready. // reserve enough space and wait for stream ready.
stream.reserve_capacity(chunk_size); stream.reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE));
match poll_fn(|cx| stream.poll_capacity(cx)).await { match poll_fn(|cx| stream.poll_capacity(cx)).await {
// No capacity left. drop body and return. // No capacity left. drop body and return.
None => return Ok(()), 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 len = chunk.len();
let bytes = chunk.split_to(cmp::min(len, cap)); let bytes = chunk.split_to(cmp::min(cap, len));
stream stream
.send_data(bytes, false) .send_data(bytes, false)

View File

@@ -40,7 +40,10 @@ impl Payload {
impl Stream for Payload { impl Stream for Payload {
type Item = Result<Bytes, PayloadError>; 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(); let this = self.get_mut();
match ready!(Pin::new(&mut this.stream).poll_data(cx)) { match ready!(Pin::new(&mut this.stream).poll_data(cx)) {

View File

@@ -1,7 +1,7 @@
use std::{ use std::{
future::Future, future::Future,
marker::PhantomData, marker::PhantomData,
mem, net, net,
pin::Pin, pin::Pin,
rc::Rc, rc::Rc,
task::{Context, Poll}, task::{Context, Poll},
@@ -10,7 +10,8 @@ use std::{
use actix_codec::{AsyncRead, AsyncWrite}; use actix_codec::{AsyncRead, AsyncWrite};
use actix_rt::net::TcpStream; use actix_rt::net::TcpStream;
use actix_service::{ 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 actix_utils::future::ready;
use futures_core::{future::LocalBoxFuture, ready}; use futures_core::{future::LocalBoxFuture, ready};
@@ -270,15 +271,16 @@ where
type Future = H2ServiceHandlerResponse<T, S, B>; type Future = H2ServiceHandlerResponse<T, S, B>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self.flow.service.poll_ready(cx).map_err(|err| { self.flow.service.poll_ready(cx).map_err(|e| {
let err = err.into(); let e = e.into();
error!("Service readiness error: {:?}", err); error!("Service readiness error: {:?}", e);
DispatchError::Service(err) DispatchError::Service(e)
}) })
} }
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future { 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 { H2ServiceHandlerResponse {
state: State::Handshake( state: State::Handshake(
@@ -297,6 +299,7 @@ where
T: AsyncRead + AsyncWrite + Unpin, T: AsyncRead + AsyncWrite + Unpin,
S::Future: 'static, S::Future: 'static,
{ {
Incoming(Dispatcher<T, S, B, (), ()>),
Handshake( Handshake(
Option<Rc<HttpFlow<S, (), ()>>>, Option<Rc<HttpFlow<S, (), ()>>>,
Option<ServiceConfig>, Option<ServiceConfig>,
@@ -304,7 +307,6 @@ where
OnConnectData, OnConnectData,
HandshakeWithTimeout<T>, HandshakeWithTimeout<T>,
), ),
Established(Dispatcher<T, S, B, (), ()>),
} }
pub struct H2ServiceHandlerResponse<T, S, B> pub struct H2ServiceHandlerResponse<T, S, B>
@@ -332,35 +334,31 @@ where
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> { fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
match self.state { match self.state {
State::Incoming(ref mut disp) => Pin::new(disp).poll(cx),
State::Handshake( State::Handshake(
ref mut srv, ref mut srv,
ref mut config, ref mut config,
ref peer_addr, ref peer_addr,
ref mut conn_data, ref mut on_connect_data,
ref mut handshake, ref mut handshake,
) => match ready!(Pin::new(handshake).poll(cx)) { ) => match ready!(Pin::new(handshake).poll(cx)) {
Ok((conn, timer)) => { Ok((conn, timer)) => {
let on_connect_data = mem::take(conn_data); let on_connect_data = std::mem::take(on_connect_data);
self.state = State::Incoming(Dispatcher::new(
self.state = State::Established(Dispatcher::new(
conn,
srv.take().unwrap(), srv.take().unwrap(),
conn,
on_connect_data,
config.take().unwrap(), config.take().unwrap(),
*peer_addr, *peer_addr,
on_connect_data,
timer, timer,
)); ));
self.poll(cx) self.poll(cx)
} }
Err(err) => { Err(err) => {
trace!("H2 handshake error: {}", err); trace!("H2 handshake error: {}", err);
Poll::Ready(Err(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`]. /// Sealed trait implemented for types that can be effectively borrowed as a [`HeaderValue`].
/// ///
/// [`HeaderValue`]: super::HeaderValue /// [`HeaderValue`]: crate::http::HeaderValue
pub trait AsHeaderName: Sealed {} pub trait AsHeaderName: Sealed {}
pub struct Seal; pub struct Seal;
@@ -16,7 +16,6 @@ pub trait Sealed {
} }
impl Sealed for HeaderName { impl Sealed for HeaderName {
#[inline]
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> { fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(self)) Ok(Cow::Borrowed(self))
} }
@@ -24,7 +23,6 @@ impl Sealed for HeaderName {
impl AsHeaderName for HeaderName {} impl AsHeaderName for HeaderName {}
impl Sealed for &HeaderName { impl Sealed for &HeaderName {
#[inline]
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> { fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
Ok(Cow::Borrowed(*self)) Ok(Cow::Borrowed(*self))
} }
@@ -32,7 +30,6 @@ impl Sealed for &HeaderName {
impl AsHeaderName for &HeaderName {} impl AsHeaderName for &HeaderName {}
impl Sealed for &str { impl Sealed for &str {
#[inline]
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> { fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned) HeaderName::from_str(self).map(Cow::Owned)
} }
@@ -40,7 +37,6 @@ impl Sealed for &str {
impl AsHeaderName for &str {} impl AsHeaderName for &str {}
impl Sealed for String { impl Sealed for String {
#[inline]
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> { fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned) HeaderName::from_str(self).map(Cow::Owned)
} }
@@ -48,7 +44,6 @@ impl Sealed for String {
impl AsHeaderName for String {} impl AsHeaderName for String {}
impl Sealed for &String { impl Sealed for &String {
#[inline]
fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> { fn try_as_name(&self, _: Seal) -> Result<Cow<'_, HeaderName>, InvalidHeaderName> {
HeaderName::from_str(self).map(Cow::Owned) HeaderName::from_str(self).map(Cow::Owned)
} }

View File

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

View File

@@ -1,4 +1,4 @@
//! [`TryIntoHeaderValue`] trait and implementations. //! [`IntoHeaderValue`] trait and implementations.
use std::convert::TryFrom as _; use std::convert::TryFrom as _;
@@ -7,7 +7,7 @@ use http::{header::InvalidHeaderValue, Error as HttpError, HeaderValue};
use mime::Mime; use mime::Mime;
/// An interface for types that can be converted into a [`HeaderValue`]. /// An interface for types that can be converted into a [`HeaderValue`].
pub trait TryIntoHeaderValue: Sized { pub trait IntoHeaderValue: Sized {
/// The type returned in the event of a conversion error. /// The type returned in the event of a conversion error.
type Error: Into<HttpError>; type Error: Into<HttpError>;
@@ -15,7 +15,7 @@ pub trait TryIntoHeaderValue: Sized {
fn try_into_value(self) -> Result<HeaderValue, Self::Error>; fn try_into_value(self) -> Result<HeaderValue, Self::Error>;
} }
impl TryIntoHeaderValue for HeaderValue { impl IntoHeaderValue for HeaderValue {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -24,7 +24,7 @@ impl TryIntoHeaderValue for HeaderValue {
} }
} }
impl TryIntoHeaderValue for &HeaderValue { impl IntoHeaderValue for &HeaderValue {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -33,7 +33,7 @@ impl TryIntoHeaderValue for &HeaderValue {
} }
} }
impl TryIntoHeaderValue for &str { impl IntoHeaderValue for &str {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -42,7 +42,7 @@ impl TryIntoHeaderValue for &str {
} }
} }
impl TryIntoHeaderValue for &[u8] { impl IntoHeaderValue for &[u8] {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -51,7 +51,7 @@ impl TryIntoHeaderValue for &[u8] {
} }
} }
impl TryIntoHeaderValue for Bytes { impl IntoHeaderValue for Bytes {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -60,7 +60,7 @@ impl TryIntoHeaderValue for Bytes {
} }
} }
impl TryIntoHeaderValue for Vec<u8> { impl IntoHeaderValue for Vec<u8> {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -69,7 +69,7 @@ impl TryIntoHeaderValue for Vec<u8> {
} }
} }
impl TryIntoHeaderValue for String { impl IntoHeaderValue for String {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -78,7 +78,7 @@ impl TryIntoHeaderValue for String {
} }
} }
impl TryIntoHeaderValue for usize { impl IntoHeaderValue for usize {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -87,7 +87,7 @@ impl TryIntoHeaderValue for usize {
} }
} }
impl TryIntoHeaderValue for i64 { impl IntoHeaderValue for i64 {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -96,7 +96,7 @@ impl TryIntoHeaderValue for i64 {
} }
} }
impl TryIntoHeaderValue for u64 { impl IntoHeaderValue for u64 {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -105,7 +105,7 @@ impl TryIntoHeaderValue for u64 {
} }
} }
impl TryIntoHeaderValue for i32 { impl IntoHeaderValue for i32 {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -114,7 +114,7 @@ impl TryIntoHeaderValue for i32 {
} }
} }
impl TryIntoHeaderValue for u32 { impl IntoHeaderValue for u32 {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]
@@ -123,7 +123,7 @@ impl TryIntoHeaderValue for u32 {
} }
} }
impl TryIntoHeaderValue for Mime { impl IntoHeaderValue for Mime {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
#[inline] #[inline]

View File

@@ -123,11 +123,12 @@ impl HeaderMap {
let mut map = HeaderMap::with_capacity(capacity); let mut map = HeaderMap::with_capacity(capacity);
map.append(first_name.clone(), first_value); map.append(first_name.clone(), first_value);
let (map, _) = drain.fold((map, first_name), |(mut map, prev_name), (name, value)| { let (map, _) =
let name = name.unwrap_or(prev_name); drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
map.append(name.clone(), value); let name = name.unwrap_or(prev_name);
(map, name) map.append(name.clone(), value);
}); (map, name)
});
map map
} }
@@ -333,7 +334,7 @@ impl HeaderMap {
} }
} }
/// Inserts (overrides) a name-value pair in the map. /// Inserts a name-value pair into the map.
/// ///
/// If the map already contained this key, the new value is associated with the key and all /// 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; /// previous values are removed and returned as a `Removed` iterator. The key is not updated;
@@ -372,7 +373,7 @@ impl HeaderMap {
Removed::new(value) Removed::new(value)
} }
/// Appends a name-value pair to the map. /// Inserts a name-value pair into the map.
/// ///
/// If the map already contained this key, the new value is added to the list of values /// 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 /// currently associated with the key. The key is not updated; this matters for types that can

View File

@@ -11,20 +11,22 @@ pub use http::header::{
pub use http::header::{ pub use http::header::{
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES, ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS, ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE, CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE,
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS, DATE, DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH,
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED,
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER, LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA, PROXY_AUTHENTICATE,
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL, 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, SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING, TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA,
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS, WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL,
X_XSS_PROTECTION, X_FRAME_OPTIONS, X_XSS_PROTECTION,
}; };
use crate::{error::ParseError, HttpMessage}; use crate::{error::ParseError, HttpMessage};
@@ -37,19 +39,19 @@ mod shared;
mod utils; mod utils;
pub use self::as_name::AsHeaderName; pub use self::as_name::AsHeaderName;
pub use self::into_pair::TryIntoHeaderPair; pub use self::into_pair::IntoHeaderPair;
pub use self::into_value::TryIntoHeaderValue; pub use self::into_value::IntoHeaderValue;
pub use self::map::HeaderMap; pub use self::map::HeaderMap;
pub use self::shared::{ pub use self::shared::{
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag, parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
Quality, QualityItem, LanguageTag, Quality, QualityItem,
}; };
pub use self::utils::{ pub use self::utils::{
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode, fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
}; };
/// An interface for types that already represent a valid header. /// An interface for types that already represent a valid header.
pub trait Header: TryIntoHeaderValue { pub trait Header: IntoHeaderValue {
/// Returns the name of the header field /// Returns the name of the header field
fn name() -> HeaderName; fn name() -> HeaderName;

View File

@@ -5,7 +5,7 @@ use http::header::InvalidHeaderValue;
use crate::{ use crate::{
error::ParseError, error::ParseError,
header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, TryIntoHeaderValue}, header::{self, from_one_raw_str, Header, HeaderName, HeaderValue, IntoHeaderValue},
HttpMessage, HttpMessage,
}; };
@@ -96,7 +96,7 @@ impl TryFrom<&str> for ContentEncoding {
} }
} }
impl TryIntoHeaderValue for ContentEncoding { impl IntoHeaderValue for ContentEncoding {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> { fn try_into_value(self) -> Result<http::HeaderValue, Self::Error> {

View File

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

View File

@@ -4,7 +4,7 @@ use bytes::BytesMut;
use http::header::{HeaderValue, InvalidHeaderValue}; use http::header::{HeaderValue, InvalidHeaderValue};
use crate::{ use crate::{
config::DATE_VALUE_LENGTH, error::ParseError, header::TryIntoHeaderValue, config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue,
helpers::MutWriter, helpers::MutWriter,
}; };
@@ -30,7 +30,7 @@ impl fmt::Display for HttpDate {
} }
} }
impl TryIntoHeaderValue for HttpDate { impl IntoHeaderValue for HttpDate {
type Error = InvalidHeaderValue; type Error = InvalidHeaderValue;
fn try_into_value(self) -> Result<HeaderValue, Self::Error> { fn try_into_value(self) -> Result<HeaderValue, Self::Error> {

View File

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

View File

@@ -14,8 +14,7 @@
//! [rustls]: https://crates.io/crates/rustls //! [rustls]: https://crates.io/crates/rustls
//! [trust-dns]: https://crates.io/crates/trust-dns //! [trust-dns]: https://crates.io/crates/trust-dns
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style, clippy::uninit_assumed_init)]
#![warn(future_incompatible)]
#![allow( #![allow(
clippy::type_complexity, clippy::type_complexity,
clippy::too_many_arguments, clippy::too_many_arguments,
@@ -54,7 +53,7 @@ pub mod ws;
pub use self::builder::HttpServiceBuilder; pub use self::builder::HttpServiceBuilder;
pub use self::config::{KeepAlive, ServiceConfig}; pub use self::config::{KeepAlive, ServiceConfig};
pub use self::error::Error; pub use self::error::Error;
pub use self::extensions::Extensions; pub use self::extensions::{CloneableExtensions, Extensions};
pub use self::header::ContentEncoding; pub use self::header::ContentEncoding;
pub use self::http_message::HttpMessage; pub use self::http_message::HttpMessage;
pub use self::message::ConnectionType; pub use self::message::ConnectionType;
@@ -77,24 +76,35 @@ pub enum Protocol {
Http3, Http3,
} }
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions); type ConnectCallback<IO> = dyn Fn(&IO, &mut CloneableExtensions);
/// Container for data that extract with ConnectCallback. /// Container for data that extract with ConnectCallback.
/// ///
/// # Implementation Details /// # Implementation Details
/// Uses Option to reduce necessary allocations when merging with request extensions. /// Uses Option to reduce necessary allocations when merging with request extensions.
#[derive(Default)] #[derive(Default)]
pub(crate) struct OnConnectData(Option<Extensions>); pub(crate) struct OnConnectData(Option<CloneableExtensions>);
impl OnConnectData { impl OnConnectData {
/// Construct by calling the on-connect callback with the underlying transport I/O. /// 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 ext = on_connect_ext.map(|handler| {
let mut extensions = Extensions::default(); let mut extensions = CloneableExtensions::default();
handler(io, &mut extensions); handler(io, &mut extensions);
extensions extensions
}); });
Self(ext) 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,12 +44,13 @@ pub trait Head: Default + 'static {
F: FnOnce(&MessagePool<Self>) -> R; F: FnOnce(&MessagePool<Self>) -> R;
} }
#[derive(Debug, Clone)] #[derive(Debug)]
pub struct RequestHead { pub struct RequestHead {
pub method: Method, pub method: Method,
pub uri: Uri, pub uri: Uri,
pub version: Version, pub version: Version,
pub headers: HeaderMap, pub headers: HeaderMap,
pub extensions: RefCell<Extensions>,
pub peer_addr: Option<net::SocketAddr>, pub peer_addr: Option<net::SocketAddr>,
flags: Flags, flags: Flags,
} }
@@ -61,6 +62,7 @@ impl Default for RequestHead {
uri: Uri::default(), uri: Uri::default(),
version: Version::HTTP_11, version: Version::HTTP_11,
headers: HeaderMap::with_capacity(16), headers: HeaderMap::with_capacity(16),
extensions: RefCell::new(Extensions::new()),
peer_addr: None, peer_addr: None,
flags: Flags::empty(), flags: Flags::empty(),
} }
@@ -71,6 +73,7 @@ impl Head for RequestHead {
fn clear(&mut self) { fn clear(&mut self) {
self.flags = Flags::empty(); self.flags = Flags::empty();
self.headers.clear(); self.headers.clear();
self.extensions.get_mut().clear();
} }
fn with_pool<F, R>(f: F) -> R fn with_pool<F, R>(f: F) -> R
@@ -82,6 +85,18 @@ impl Head for RequestHead {
} }
impl 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. /// Read the message headers.
pub fn headers(&self) -> &HeaderMap { pub fn headers(&self) -> &HeaderMap {
&self.headers &self.headers

View File

@@ -7,10 +7,10 @@ use h2::RecvStream;
use crate::error::PayloadError; use crate::error::PayloadError;
/// A boxed payload. /// Type represent boxed payload
pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>; pub type PayloadStream = Pin<Box<dyn Stream<Item = Result<Bytes, PayloadError>>>>;
/// A streaming payload. /// Type represent streaming payload
pub enum Payload<S = PayloadStream> { pub enum Payload<S = PayloadStream> {
None, None,
H1(crate::h1::Payload), H1(crate::h1::Payload),
@@ -56,7 +56,10 @@ where
type Item = Result<Bytes, PayloadError>; type Item = Result<Bytes, PayloadError>;
#[inline] #[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() { match self.get_mut() {
Payload::None => Poll::Ready(None), Payload::None => Poll::Ready(None),
Payload::H1(ref mut pl) => pl.readany(cx), Payload::H1(ref mut pl) => pl.readany(cx),

View File

@@ -1,10 +1,8 @@
//! HTTP requests. //! HTTP requests.
use std::{ use std::{
cell::{Ref, RefCell, RefMut}, cell::{Ref, RefMut},
fmt, mem, net, fmt, net, str,
rc::Rc,
str,
}; };
use http::{header, Method, Uri, Version}; use http::{header, Method, Uri, Version};
@@ -21,8 +19,6 @@ use crate::{
pub struct Request<P = PayloadStream> { pub struct Request<P = PayloadStream> {
pub(crate) payload: Payload<P>, pub(crate) payload: Payload<P>,
pub(crate) head: Message<RequestHead>, pub(crate) head: Message<RequestHead>,
pub(crate) conn_data: Option<Rc<Extensions>>,
pub(crate) req_data: RefCell<Extensions>,
} }
impl<P> HttpMessage for Request<P> { impl<P> HttpMessage for Request<P> {
@@ -34,19 +30,19 @@ impl<P> HttpMessage for Request<P> {
} }
fn take_payload(&mut self) -> Payload<P> { fn take_payload(&mut self) -> Payload<P> {
mem::replace(&mut self.payload, Payload::None) std::mem::replace(&mut self.payload, Payload::None)
} }
/// Request extensions /// Request extensions
#[inline] #[inline]
fn extensions(&self) -> Ref<'_, Extensions> { fn extensions(&self) -> Ref<'_, Extensions> {
self.req_data.borrow() self.head.extensions()
} }
/// Mutable reference to a the request's extensions /// Mutable reference to a the request's extensions
#[inline] #[inline]
fn extensions_mut(&self) -> RefMut<'_, Extensions> { fn extensions_mut(&self) -> RefMut<'_, Extensions> {
self.req_data.borrow_mut() self.head.extensions_mut()
} }
} }
@@ -55,8 +51,6 @@ impl From<Message<RequestHead>> for Request<PayloadStream> {
Request { Request {
head, head,
payload: Payload::None, payload: Payload::None,
req_data: RefCell::new(Extensions::default()),
conn_data: None,
} }
} }
} }
@@ -67,8 +61,6 @@ impl Request<PayloadStream> {
Request { Request {
head: Message::new(), head: Message::new(),
payload: Payload::None, payload: Payload::None,
req_data: RefCell::new(Extensions::default()),
conn_data: None,
} }
} }
} }
@@ -79,21 +71,16 @@ impl<P> Request<P> {
Request { Request {
payload, payload,
head: Message::new(), head: Message::new(),
req_data: RefCell::new(Extensions::default()),
conn_data: None,
} }
} }
/// Create new Request instance /// Create new Request instance
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) { pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
let pl = self.payload; let pl = self.payload;
( (
Request { Request {
payload, payload,
head: self.head, head: self.head,
req_data: self.req_data,
conn_data: self.conn_data,
}, },
pl, pl,
) )
@@ -106,7 +93,7 @@ impl<P> Request<P> {
/// Get request's payload /// Get request's payload
pub fn take_payload(&mut self) -> Payload<P> { pub fn take_payload(&mut self) -> Payload<P> {
mem::replace(&mut self.payload, Payload::None) std::mem::replace(&mut self.payload, Payload::None)
} }
/// Split request into request head and payload /// Split request into request head and payload
@@ -129,7 +116,7 @@ impl<P> Request<P> {
/// Mutable reference to the message's headers. /// Mutable reference to the message's headers.
pub fn headers_mut(&mut self) -> &mut HeaderMap { pub fn headers_mut(&mut self) -> &mut HeaderMap {
&mut self.head.headers &mut self.head_mut().headers
} }
/// Request's uri. /// Request's uri.
@@ -141,7 +128,7 @@ impl<P> Request<P> {
/// Mutable reference to the request's uri. /// Mutable reference to the request's uri.
#[inline] #[inline]
pub fn uri_mut(&mut self) -> &mut Uri { pub fn uri_mut(&mut self) -> &mut Uri {
&mut self.head.uri &mut self.head_mut().uri
} }
/// Read the Request method. /// Read the Request method.
@@ -183,31 +170,6 @@ impl<P> Request<P> {
pub fn peer_addr(&self) -> Option<net::SocketAddr> { pub fn peer_addr(&self) -> Option<net::SocketAddr> {
self.head().peer_addr 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> { impl<P> fmt::Debug for Request<P> {

View File

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

View File

@@ -8,7 +8,7 @@ use std::{
use crate::{ use crate::{
body::{EitherBody, MessageBody}, body::{EitherBody, MessageBody},
error::{Error, HttpError}, error::{Error, HttpError},
header::{self, TryIntoHeaderPair, TryIntoHeaderValue}, header::{self, IntoHeaderPair, IntoHeaderValue},
message::{BoxedResponseHead, ConnectionType, ResponseHead}, message::{BoxedResponseHead, ConnectionType, ResponseHead},
Extensions, Response, StatusCode, Extensions, Response, StatusCode,
}; };
@@ -47,8 +47,7 @@ impl ResponseBuilder {
/// Create response builder /// Create response builder
/// ///
/// # Examples /// # Examples
/// ``` // /// use actix_http::{Response, ResponseBuilder, StatusCode};, / ``
/// use actix_http::{Response, ResponseBuilder, StatusCode};
/// let res: Response<_> = ResponseBuilder::default().finish(); /// let res: Response<_> = ResponseBuilder::default().finish();
/// assert_eq!(res.status(), StatusCode::OK); /// assert_eq!(res.status(), StatusCode::OK);
/// ``` /// ```
@@ -63,8 +62,7 @@ impl ResponseBuilder {
/// Set HTTP status code of this response. /// Set HTTP status code of this response.
/// ///
/// # Examples /// # Examples
/// ``` // /// use actix_http::{ResponseBuilder, StatusCode};, / ``
/// use actix_http::{ResponseBuilder, StatusCode};
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish(); /// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
/// assert_eq!(res.status(), StatusCode::NOT_FOUND); /// assert_eq!(res.status(), StatusCode::NOT_FOUND);
/// ``` /// ```
@@ -90,9 +88,12 @@ impl ResponseBuilder {
/// assert!(res.headers().contains_key("content-type")); /// assert!(res.headers().contains_key("content-type"));
/// assert!(res.headers().contains_key("x-test")); /// assert!(res.headers().contains_key("x-test"));
/// ``` /// ```
pub fn insert_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { pub fn insert_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
match header.try_into_pair() { match header.try_into_header_pair() {
Ok((key, value)) => { Ok((key, value)) => {
parts.headers.insert(key, value); parts.headers.insert(key, value);
} }
@@ -118,9 +119,12 @@ impl ResponseBuilder {
/// assert_eq!(res.headers().get_all("content-type").count(), 1); /// assert_eq!(res.headers().get_all("content-type").count(), 1);
/// assert_eq!(res.headers().get_all("x-test").count(), 2); /// assert_eq!(res.headers().get_all("x-test").count(), 2);
/// ``` /// ```
pub fn append_header(&mut self, header: impl TryIntoHeaderPair) -> &mut Self { pub fn append_header<H>(&mut self, header: H) -> &mut Self
where
H: IntoHeaderPair,
{
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
match header.try_into_pair() { match header.try_into_header_pair() {
Ok((key, value)) => parts.headers.append(key, value), Ok((key, value)) => parts.headers.append(key, value),
Err(e) => self.err = Some(e.into()), Err(e) => self.err = Some(e.into()),
}; };
@@ -151,7 +155,7 @@ impl ResponseBuilder {
#[inline] #[inline]
pub fn upgrade<V>(&mut self, value: V) -> &mut Self pub fn upgrade<V>(&mut self, value: V) -> &mut Self
where where
V: TryIntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
parts.set_connection_type(ConnectionType::Upgrade); parts.set_connection_type(ConnectionType::Upgrade);
@@ -189,7 +193,7 @@ impl ResponseBuilder {
#[inline] #[inline]
pub fn content_type<V>(&mut self, value: V) -> &mut Self pub fn content_type<V>(&mut self, value: V) -> &mut Self
where where
V: TryIntoHeaderValue, V: IntoHeaderValue,
{ {
if let Some(parts) = self.inner() { if let Some(parts) = self.inner() {
match value.try_into_value() { match value.try_into_value() {

View File

@@ -161,7 +161,11 @@ where
X::Error: Into<Response<BoxBody>>, X::Error: Into<Response<BoxBody>>,
X::InitError: fmt::Debug, 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::Future: 'static,
U::Error: fmt::Display + Into<Response<BoxBody>>, U::Error: fmt::Display + Into<Response<BoxBody>>,
U::InitError: fmt::Debug, U::InitError: fmt::Debug,
@@ -377,9 +381,9 @@ where
let upgrade = match upgrade { let upgrade = match upgrade {
Some(upgrade) => { Some(upgrade) => {
let upgrade = upgrade let upgrade = upgrade.await.map_err(|e| {
.await log::error!("Init http upgrade service error: {:?}", e)
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?; })?;
Some(upgrade) Some(upgrade)
} }
None => None, None => None,
@@ -493,9 +497,9 @@ where
type Future = HttpServiceHandlerResponse<T, S, B, X, U>; type Future = HttpServiceHandlerResponse<T, S, B, X, U>;
fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { fn poll_ready(&self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
self._poll_ready(cx).map_err(|err| { self._poll_ready(cx).map_err(|e| {
log::error!("HTTP service readiness error: {:?}", err); log::error!("HTTP service readiness error: {:?}", e);
DispatchError::Service(err) DispatchError::Service(e)
}) })
} }
@@ -503,7 +507,8 @@ where
&self, &self,
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>), (io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
) -> Self::Future { ) -> Self::Future {
let conn_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());
match proto { match proto {
Protocol::Http2 => HttpServiceHandlerResponse { Protocol::Http2 => HttpServiceHandlerResponse {
@@ -512,7 +517,7 @@ where
h2::handshake_with_timeout(io, &self.cfg), h2::handshake_with_timeout(io, &self.cfg),
self.cfg.clone(), self.cfg.clone(),
self.flow.clone(), self.flow.clone(),
conn_data, on_connect_data,
peer_addr, peer_addr,
)), )),
}, },
@@ -522,10 +527,10 @@ where
state: State::H1 { state: State::H1 {
dispatcher: h1::Dispatcher::new( dispatcher: h1::Dispatcher::new(
io, io,
self.flow.clone(),
self.cfg.clone(), self.cfg.clone(),
self.flow.clone(),
on_connect_data,
peer_addr, peer_addr,
conn_data,
), ),
}, },
}, },
@@ -622,11 +627,17 @@ where
StateProj::H2Handshake { handshake: data } => { StateProj::H2Handshake { handshake: data } => {
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) { match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
Ok((conn, timer)) => { Ok((conn, timer)) => {
let (_, config, flow, conn_data, peer_addr) = data.take().unwrap(); let (_, config, flow, on_connect_data, peer_addr) =
data.take().unwrap();
self.as_mut().project().state.set(State::H2 { self.as_mut().project().state.set(State::H2 {
dispatcher: h2::Dispatcher::new( dispatcher: h2::Dispatcher::new(
conn, flow, config, peer_addr, conn_data, timer, flow,
conn,
on_connect_data,
config,
peer_addr,
timer,
), ),
}); });
self.poll(cx) self.poll(cx)

View File

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

View File

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

View File

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

View File

@@ -16,7 +16,8 @@ impl Parser {
src: &[u8], src: &[u8],
server: bool, server: bool,
max_size: usize, 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 chunk_len = src.len();
let mut idx = 2; let mut idx = 2;
@@ -227,11 +228,15 @@ mod tests {
payload: Bytes, 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)) 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 { match frm {
Ok(Some((finished, opcode, payload))) => F { Ok(Some((finished, opcode, payload))) => F {
finished, finished,

View File

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

View File

@@ -9,7 +9,9 @@ use derive_more::{Display, Error, From};
use http::{header, Method, StatusCode}; use http::{header, Method, StatusCode};
use crate::body::BoxBody; 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 codec;
mod dispatcher; mod dispatcher;

View File

@@ -1,6 +1,8 @@
use std::convert::Infallible; 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_http_test::test_server;
use actix_service::ServiceFactoryExt; use actix_service::ServiceFactoryExt;
use actix_utils::future; use actix_utils::future;

View File

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

View File

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

View File

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

View File

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

View File

@@ -3,10 +3,6 @@
## Unreleased - 2021-xx-xx ## 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 ## 0.4.0-beta.9 - 2021-12-01
* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463] * Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-multipart" name = "actix-multipart"
version = "0.4.0-beta.10" version = "0.4.0-beta.9"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Multipart form support for Actix Web" description = "Multipart form support for Actix Web"
keywords = ["http", "web", "framework", "async", "futures"] keywords = ["http", "web", "framework", "async", "futures"]
@@ -14,8 +14,8 @@ name = "actix_multipart"
path = "src/lib.rs" path = "src/lib.rs"
[dependencies] [dependencies]
actix-web = { version = "4.0.0-beta.11", default-features = false }
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", default-features = false }
bytes = "1" bytes = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
@@ -28,7 +28,7 @@ twoway = "0.2"
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-http = "3.0.0-beta.16" actix-http = "3.0.0-beta.14"
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
tokio = { version = "1", features = ["sync"] } tokio = { version = "1", features = ["sync"] }
tokio-stream = "0.1" tokio-stream = "0.1"

View File

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

View File

@@ -1,7 +1,6 @@
//! Multipart form support for Actix Web. //! Multipart form support for Actix Web.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms)]
#![warn(future_incompatible)]
#![allow(clippy::borrow_interior_mutable_const)] #![allow(clippy::borrow_interior_mutable_const)]
mod error; mod error;

View File

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

View File

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

View File

@@ -1,26 +1,149 @@
//! Resource path matching and router. //! Resource path matching and router.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#![doc(html_logo_url = "https://actix.rs/img/logo.png")] #![doc(html_logo_url = "https://actix.rs/img/logo.png")]
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")] #![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
mod de; mod de;
mod path; mod path;
mod pattern;
mod resource; mod resource;
mod resource_path;
mod router; 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")] #[cfg(feature = "http")]
mod url; 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")] #[cfg(feature = "http")]
pub use self::url::{Quoter, Url}; pub use self::url::{Quoter, Url};
#[cfg(feature = "http")]
mod http_impls {
use http::Uri;
use super::ResourcePath;
impl ResourcePath for Uri {
fn path(&self) -> &str {
self.path()
}
}
}

View File

@@ -1,92 +0,0 @@
/// One or many patterns.
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Patterns {
Single(String),
List(Vec<String>),
}
impl Patterns {
pub fn is_empty(&self) -> bool {
match self {
Patterns::Single(_) => false,
Patterns::List(pats) => pats.is_empty(),
}
}
}
/// Helper trait for type that could be converted to one or more path patterns.
pub trait IntoPatterns {
fn patterns(&self) -> Patterns;
}
impl IntoPatterns for String {
fn patterns(&self) -> Patterns {
Patterns::Single(self.clone())
}
}
impl IntoPatterns for &String {
fn patterns(&self) -> Patterns {
(*self).patterns()
}
}
impl IntoPatterns for str {
fn patterns(&self) -> Patterns {
Patterns::Single(self.to_owned())
}
}
impl IntoPatterns for &str {
fn patterns(&self) -> Patterns {
(*self).patterns()
}
}
impl IntoPatterns for bytestring::ByteString {
fn patterns(&self) -> Patterns {
Patterns::Single(self.to_string())
}
}
impl IntoPatterns for Patterns {
fn patterns(&self) -> Patterns {
self.clone()
}
}
impl<T: AsRef<str>> IntoPatterns for Vec<T> {
fn patterns(&self) -> Patterns {
let mut patterns = self.iter().map(|v| v.as_ref().to_owned());
match patterns.size_hint() {
(1, _) => Patterns::Single(patterns.next().unwrap()),
_ => Patterns::List(patterns.collect()),
}
}
}
macro_rules! array_patterns_single (($tp:ty) => {
impl IntoPatterns for [$tp; 1] {
fn patterns(&self) -> Patterns {
Patterns::Single(self[0].to_owned())
}
}
});
macro_rules! array_patterns_multiple (($tp:ty, $str_fn:expr, $($num:tt) +) => {
// for each array length specified in space-separated $num
$(
impl IntoPatterns for [$tp; $num] {
fn patterns(&self) -> Patterns {
Patterns::List(self.iter().map($str_fn).collect())
}
}
)+
});
array_patterns_single!(&str);
array_patterns_multiple!(&str, |&v| v.to_owned(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);
array_patterns_single!(String);
array_patterns_multiple!(String, |v| v.clone(), 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16);

View File

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

View File

@@ -1,36 +0,0 @@
use crate::Path;
// TODO: this trait is necessary, document it
// see impl Resource for ServiceRequest
pub trait Resource<T: ResourcePath> {
fn resource_path(&mut self) -> &mut Path<T>;
}
pub trait ResourcePath {
fn path(&self) -> &str;
}
impl ResourcePath for String {
fn path(&self) -> &str {
self.as_str()
}
}
impl<'a> ResourcePath for &'a str {
fn path(&self) -> &str {
self
}
}
impl ResourcePath for bytestring::ByteString {
fn path(&self) -> &str {
&*self
}
}
#[cfg(feature = "http")]
impl ResourcePath for http::Uri {
fn path(&self) -> &str {
self.path()
}
}

View File

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

View File

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

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-test" name = "actix-test"
version = "0.1.0-beta.9" version = "0.1.0-beta.7"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"Rob Ede <robjtede@icloud.com>", "Rob Ede <robjtede@icloud.com>",
@@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
[dependencies] [dependencies]
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-http = "3.0.0-beta.16" actix-http = "3.0.0-beta.14"
actix-http-test = "3.0.0-beta.9" actix-http-test = "3.0.0-beta.7"
actix-rt = "2.1"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", default-features = false, features = ["cookies"] } actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
awc = { version = "3.0.0-beta.14", default-features = false, features = ["cookies"] } actix-rt = "2.1"
awc = { version = "3.0.0-beta.11", default-features = false, features = ["cookies"] }
futures-core = { version = "0.3.7", default-features = false, features = ["std"] } futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
futures-util = { version = "0.3.7", default-features = false, features = [] } futures-util = { version = "0.3.7", default-features = false, features = [] }

View File

@@ -26,9 +26,6 @@
//! } //! }
//! ``` //! ```
#![deny(rust_2018_idioms, nonstandard_style)]
#![warn(future_incompatible)]
#[cfg(feature = "openssl")] #[cfg(feature = "openssl")]
extern crate tls_openssl as openssl; extern crate tls_openssl as openssl;
#[cfg(feature = "rustls")] #[cfg(feature = "rustls")]
@@ -37,14 +34,9 @@ extern crate tls_rustls as rustls;
use std::{fmt, net, thread, time::Duration}; use std::{fmt, net, thread, time::Duration};
use actix_codec::{AsyncRead, AsyncWrite, Framed}; use actix_codec::{AsyncRead, AsyncWrite, Framed};
pub use actix_http::{body::to_bytes, test::TestBuffer}; pub use actix_http::test::TestBuffer;
use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response}; use actix_http::{header::HeaderMap, ws, HttpService, Method, Request, Response};
pub use actix_http_test::unused_addr;
use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _}; use actix_service::{map_config, IntoServiceFactory, ServiceFactory, ServiceFactoryExt as _};
pub use actix_web::test::{
call_and_read_body, call_and_read_body_json, call_service, init_service, ok_service,
read_body, read_body_json, simple_service, TestRequest,
};
use actix_web::{ use actix_web::{
body::MessageBody, body::MessageBody,
dev::{AppConfig, Server, ServerHandle, Service}, dev::{AppConfig, Server, ServerHandle, Service},
@@ -53,6 +45,12 @@ use actix_web::{
}; };
use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector}; use awc::{error::PayloadError, Client, ClientRequest, ClientResponse, Connector};
use futures_core::Stream; use futures_core::Stream;
pub use actix_http_test::unused_addr;
pub use actix_web::test::{
call_service, default_service, init_service, load_stream, ok_service, read_body,
read_body_json, read_response, read_response_json, TestRequest,
};
use tokio::sync::mpsc; use tokio::sync::mpsc;
/// Start default [`TestServer`]. /// Start default [`TestServer`].

View File

@@ -1,9 +1,6 @@
# Changes # Changes
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 4.0.0-beta.8 - 2021-12-11
* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920] * Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920] * Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
* Minimum supported Rust version (MSRV) is now 1.52. * Minimum supported Rust version (MSRV) is now 1.52.

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-actors" name = "actix-web-actors"
version = "4.0.0-beta.8" version = "4.0.0-beta.7"
authors = ["Nikolay Kim <fafhrd91@gmail.com>"] authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
description = "Actix actors support for Actix Web" description = "Actix actors support for Actix Web"
keywords = ["actix", "http", "web", "framework", "async"] keywords = ["actix", "http", "web", "framework", "async"]
@@ -16,8 +16,8 @@ path = "src/lib.rs"
[dependencies] [dependencies]
actix = { version = "0.12.0", default-features = false } actix = { version = "0.12.0", default-features = false }
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-http = "3.0.0-beta.16" actix-http = "3.0.0-beta.14"
actix-web = { version = "4.0.0-beta.15", default-features = false } actix-web = { version = "4.0.0-beta.11", default-features = false }
bytes = "1" bytes = "1"
bytestring = "1" bytestring = "1"
@@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] }
[dev-dependencies] [dev-dependencies]
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.9" actix-test = "0.1.0-beta.7"
awc = { version = "3.0.0-beta.14", default-features = false }
awc = { version = "3.0.0-beta.11", default-features = false }
env_logger = "0.9" env_logger = "0.9"
futures-util = { version = "0.3.7", default-features = false } futures-util = { version = "0.3.7", default-features = false }

View File

@@ -3,11 +3,11 @@
> Actix actors support for Actix Web. > 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) [![crates.io](https://img.shields.io/crates/v/actix-web-actors?label=latest)](https://crates.io/crates/actix-web-actors)
[![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.8)](https://docs.rs/actix-web-actors/4.0.0-beta.8) [![Documentation](https://docs.rs/actix-web-actors/badge.svg?version=4.0.0-beta.7)](https://docs.rs/actix-web-actors/4.0.0-beta.7)
[![Version](https://img.shields.io/badge/rustc-1.52+-ab6000.svg)](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html) [![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) ![License](https://img.shields.io/crates/l/actix-web-actors.svg)
<br /> <br />
[![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.8) [![dependency status](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7/status.svg)](https://deps.rs/crate/actix-web-actors/4.0.0-beta.7)
[![Download](https://img.shields.io/crates/d/actix-web-actors.svg)](https://crates.io/crates/actix-web-actors) [![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) [![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. //! Actix actors support for Actix Web.
#![deny(rust_2018_idioms, nonstandard_style)] #![deny(rust_2018_idioms)]
#![warn(future_incompatible)] #![allow(clippy::borrow_interior_mutable_const)]
mod context; mod context;
pub mod ws; pub mod ws;

View File

@@ -3,10 +3,6 @@
## Unreleased - 2021-xx-xx ## 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 ## 0.5.0-beta.5 - 2021-10-20
* Improve error recovery potential when macro input is invalid. [#2410] * Improve error recovery potential when macro input is invalid. [#2410]
* Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409] * Add `#[actix_web::test]` macro for setting up tests with a runtime. [#2409]

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "actix-web-codegen" name = "actix-web-codegen"
version = "0.5.0-beta.6" version = "0.5.0-beta.5"
description = "Routing and runtime macros for Actix Web" description = "Routing and runtime macros for Actix Web"
homepage = "https://actix.rs" homepage = "https://actix.rs"
repository = "https://github.com/actix/actix-web.git" repository = "https://github.com/actix/actix-web.git"
@@ -18,14 +18,14 @@ proc-macro = true
quote = "1" quote = "1"
syn = { version = "1", features = ["full", "parsing"] } syn = { version = "1", features = ["full", "parsing"] }
proc-macro2 = "1" proc-macro2 = "1"
actix-router = "0.5.0-beta.3" actix-router = "0.5.0-beta.2"
[dev-dependencies] [dev-dependencies]
actix-macros = "0.2.3"
actix-rt = "2.2" actix-rt = "2.2"
actix-test = "0.1.0-beta.9" actix-macros = "0.2.3"
actix-test = "0.1.0-beta.7"
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = "4.0.0-beta.15" actix-web = "4.0.0-beta.11"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
trybuild = "1" trybuild = "1"

View File

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

View File

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

View File

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

View File

@@ -3,16 +3,6 @@
## Unreleased - 2021-xx-xx ## Unreleased - 2021-xx-xx
## 3.0.0-beta.14 - 2021-12-17
* Add `ClientBuilder::add_default_header` and deprecate `ClientBuilder::header`. [#2510]
[#2510]: https://github.com/actix/actix-web/pull/2510
## 3.0.0-beta.13 - 2021-12-11
* No significant changes since `3.0.0-beta.12`.
## 3.0.0-beta.12 - 2021-11-30 ## 3.0.0-beta.12 - 2021-11-30
* Update `actix-tls` to `3.0.0-rc.1`. [#2474] * Update `actix-tls` to `3.0.0-rc.1`. [#2474]
@@ -66,7 +56,7 @@
* `ConnectorService` type is renamed to `BoxConnectorService`. [#2081] * `ConnectorService` type is renamed to `BoxConnectorService`. [#2081]
* Fix http/https encoding when enabling `compress` feature. [#2116] * Fix http/https encoding when enabling `compress` feature. [#2116]
* Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header * Rename `TestResponse::header` to `append_header`, `set` to `insert_header`. `TestResponse` header
methods now take `TryIntoHeaderPair` tuples. [#2094] methods now take `IntoHeaderPair` tuples. [#2094]
[#2081]: https://github.com/actix/actix-web/pull/2081 [#2081]: https://github.com/actix/actix-web/pull/2081
[#2094]: https://github.com/actix/actix-web/pull/2094 [#2094]: https://github.com/actix/actix-web/pull/2094

View File

@@ -1,6 +1,6 @@
[package] [package]
name = "awc" name = "awc"
version = "3.0.0-beta.14" version = "3.0.0-beta.12"
authors = [ authors = [
"Nikolay Kim <fafhrd91@gmail.com>", "Nikolay Kim <fafhrd91@gmail.com>",
"fakeshadow <24548779@qq.com>", "fakeshadow <24548779@qq.com>",
@@ -60,7 +60,7 @@ dangerous-h2c = []
[dependencies] [dependencies]
actix-codec = "0.4.1" actix-codec = "0.4.1"
actix-service = "2.0.0" actix-service = "2.0.0"
actix-http = "3.0.0-beta.16" actix-http = "3.0.0-beta.14"
actix-rt = { version = "2.1", default-features = false } actix-rt = { version = "2.1", default-features = false }
actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] } actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
actix-utils = "3.0.0" actix-utils = "3.0.0"
@@ -70,9 +70,9 @@ base64 = "0.13"
bytes = "1" bytes = "1"
cfg-if = "1" cfg-if = "1"
derive_more = "0.99.5" derive_more = "0.99.5"
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] } futures-core = { version = "0.3.7", default-features = false }
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] } futures-util = { version = "0.3.7", default-features = false }
h2 = "0.3.9" h2 = "0.3"
http = "0.2.5" http = "0.2.5"
itoa = "0.4" itoa = "0.4"
log =" 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 } trust-dns-resolver = { version = "0.20.0", optional = true }
[dev-dependencies] [dev-dependencies]
actix-http = { version = "3.0.0-beta.16", features = ["openssl"] } actix-web = { version = "4.0.0-beta.11", features = ["openssl"] }
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] } actix-http = { version = "3.0.0-beta.14", features = ["openssl"] }
actix-server = "2.0.0-rc.1" actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
actix-test = { version = "0.1.0-beta.9", features = ["openssl", "rustls"] }
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
actix-utils = "3.0.0" actix-utils = "3.0.0"
actix-web = { version = "4.0.0-beta.15", features = ["openssl"] } actix-server = "2.0.0-rc.1"
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
brotli2 = "0.3.2" brotli2 = "0.3.2"
env_logger = "0.9" env_logger = "0.9"

View File

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

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