mirror of
https://github.com/fafhrd91/actix-web
synced 2025-08-11 09:08:09 +02:00
Compare commits
14 Commits
codegen-v0
...
on-connect
Author | SHA1 | Date | |
---|---|---|---|
|
1aee8a1a58 | ||
|
cca0593df1 | ||
|
efa68ec453 | ||
|
01885f9954 | ||
|
a86c831b89 | ||
|
999c003aa8 | ||
|
2bc7102e37 | ||
|
20752fd82e | ||
|
e6290dfd09 | ||
|
9e685fc5fb | ||
|
cf63f5c755 | ||
|
694cfc94c9 | ||
|
6bb33ec5db | ||
|
3b2e2acb6c |
23
CHANGES.md
23
CHANGES.md
@@ -1,47 +1,36 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.0-beta.14 - 2021-12-11
|
||||
### Added
|
||||
* Methods on `AcceptLanguage`: `ranked` and `preference`. [#2480]
|
||||
* `AcceptEncoding` typed header. [#2482]
|
||||
* `Range` typed header. [#2485]
|
||||
* `HttpResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||
* `ServiceResponse::map_into_{left,right}_body` and `HttpResponse::map_into_boxed_body`. [#2468]
|
||||
* Connection data set through the `HttpServer::on_connect` callback is now accessible only from the new `HttpRequest::conn_data()` and `ServiceRequest::conn_data()` methods. [#2491]
|
||||
* `HttpRequest::{req_data,req_data_mut}`. [#2487]
|
||||
* `ServiceResponse::into_parts`. [#2499]
|
||||
* `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
|
||||
|
||||
[#2325]: https://github.com/actix/actix-web/pull/2325
|
||||
[#2327]: https://github.com/actix/actix-web/pull/2327
|
||||
|
||||
### Changed
|
||||
* Rename `Accept::{mime_precedence => ranked}`. [#2480]
|
||||
* Rename `Accept::{mime_preference => preference}`. [#2480]
|
||||
* Un-deprecate `App::data_factory`. [#2484]
|
||||
* `HttpRequest::url_for` no longer constructs URLs with query or fragment components. [#2430]
|
||||
* Remove `B` (body) type parameter on `App`. [#2493]
|
||||
* Add `B` (body) type parameter on `Scope`. [#2492]
|
||||
* Request-local data container is no longer part of a `RequestHead`. Instead it is a distinct part of a `Request`. [#2487]
|
||||
* `HttpServer::on_connect` now receives a `CloneableExtensions` object. [#2327]
|
||||
|
||||
### Fixed
|
||||
* Accept wildcard `*` items in `AcceptLanguage`. [#2480]
|
||||
* Re-exports `dev::{BodySize, MessageBody, SizedStream}`. They are exposed through the `body` module. [#2468]
|
||||
* Typed headers containing lists that require one or more items now enforce this minimum. [#2482]
|
||||
|
||||
### Removed
|
||||
* `ConnectionInfo::get`. [#2487]
|
||||
|
||||
[#2327]: https://github.com/actix/actix-web/pull/2327
|
||||
[#2430]: https://github.com/actix/actix-web/pull/2430
|
||||
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||
[#2480]: https://github.com/actix/actix-web/pull/2480
|
||||
[#2482]: https://github.com/actix/actix-web/pull/2482
|
||||
[#2484]: https://github.com/actix/actix-web/pull/2484
|
||||
[#2485]: https://github.com/actix/actix-web/pull/2485
|
||||
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||
[#2492]: https://github.com/actix/actix-web/pull/2492
|
||||
[#2493]: https://github.com/actix/actix-web/pull/2493
|
||||
[#2499]: https://github.com/actix/actix-web/pull/2499
|
||||
|
||||
|
||||
## 4.0.0-beta.13 - 2021-11-30
|
||||
|
10
Cargo.toml
10
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web"
|
||||
version = "4.0.0-beta.14"
|
||||
version = "4.0.0-beta.13"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix Web is a powerful, pragmatic, and extremely fast web framework for Rust"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
@@ -77,9 +77,9 @@ actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-tls = { version = "3.0.0-rc.1", default-features = false, optional = true }
|
||||
|
||||
actix-http = "3.0.0-beta.15"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-router = "0.5.0-beta.2"
|
||||
actix-web-codegen = "0.5.0-beta.6"
|
||||
actix-web-codegen = "0.5.0-beta.5"
|
||||
|
||||
ahash = "0.7"
|
||||
bytes = "1"
|
||||
@@ -107,8 +107,8 @@ time = { version = "0.3", default-features = false, features = ["formatting"] }
|
||||
url = "2.1"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.13", features = ["openssl"] }
|
||||
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
|
||||
awc = { version = "3.0.0-beta.11", features = ["openssl"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
|
@@ -6,10 +6,10 @@
|
||||
<p>
|
||||
|
||||
[](https://crates.io/crates/actix-web)
|
||||
[](https://docs.rs/actix-web/4.0.0-beta.14)
|
||||
[](https://docs.rs/actix-web/4.0.0-beta.13)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

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

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.10)
|
||||
[](https://deps.rs/crate/actix-files/0.6.0-beta.9)
|
||||
[](https://crates.io/crates/actix-files)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -262,9 +262,9 @@ impl Files {
|
||||
self
|
||||
}
|
||||
|
||||
/// See [`Files::method_guard`].
|
||||
#[doc(hidden)]
|
||||
#[deprecated(since = "0.6.0", note = "Renamed to `method_guard`.")]
|
||||
/// See [`Files::method_guard`].
|
||||
pub fn use_guards<G: Guard + 'static>(self, guard: G) -> Self {
|
||||
self.method_guard(guard)
|
||||
}
|
||||
|
@@ -11,8 +11,8 @@
|
||||
//! .service(Files::new("/static", ".").prefer_utf8(true));
|
||||
//! ```
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible, missing_docs, missing_debug_implementations)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![warn(missing_docs, missing_debug_implementations)]
|
||||
|
||||
use actix_service::boxed::{BoxService, BoxServiceFactory};
|
||||
use actix_web::{
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.9 - 2021-12-11
|
||||
* No significant changes since `3.0.0-beta.8`.
|
||||
|
||||
|
||||
## 3.0.0-beta.8 - 2021-11-30
|
||||
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http-test"
|
||||
version = "3.0.0-beta.9"
|
||||
version = "3.0.0-beta.8"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Various helpers for Actix applications to use during testing"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@@ -35,7 +35,7 @@ actix-tls = "3.0.0-rc.1"
|
||||
actix-utils = "3.0.0"
|
||||
actix-rt = "2.2"
|
||||
actix-server = "2.0.0-rc.1"
|
||||
awc = { version = "3.0.0-beta.13", default-features = false }
|
||||
awc = { version = "3.0.0-beta.11", default-features = false }
|
||||
|
||||
base64 = "0.13"
|
||||
bytes = "1"
|
||||
@@ -51,5 +51,5 @@ tls-openssl = { version = "0.10.9", package = "openssl", optional = true }
|
||||
tokio = { version = "1.2", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-beta.15"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
actix-http = "3.0.0-beta.14"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Various helpers for Actix applications to use during testing.
|
||||
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.9)
|
||||
[](https://docs.rs/actix-http-test/3.0.0-beta.8)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br>
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.9)
|
||||
[](https://deps.rs/crate/actix-http-test/3.0.0-beta.8)
|
||||
[](https://crates.io/crates/actix-http-test)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! Various helpers for Actix applications to use during testing.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
|
@@ -1,9 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.15 - 2021-12-11
|
||||
### Added
|
||||
* Add timeout for canceling HTTP/2 server side connection handshake. Default to 5 seconds. [#2483]
|
||||
* HTTP/2 handshake timeout can be configured with `ServiceConfig::client_timeout`. [#2483]
|
||||
@@ -17,11 +14,7 @@
|
||||
* `header::QualityItem::{max, min}`. [#2486]
|
||||
* `header::Quality::{MAX, MIN}`. [#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]
|
||||
* `Request::take_conn_data()`. [#2491]
|
||||
* `Request::take_req_data()`. [#2487]
|
||||
* `impl Clone` for `RequestHead`. [#2487]
|
||||
* New methods on `MessageBody` trait, `is_complete_body` and `take_complete_body`, both with default implementations, for optimisations on body types that are done in exactly one poll/chunk. [#2497]
|
||||
* `CloneableExtensions` object for use in `on_connect` handlers. [#2327]
|
||||
|
||||
### Changed
|
||||
* Rename `body::BoxBody::{from_body => new}`. [#2468]
|
||||
@@ -31,6 +24,7 @@
|
||||
* `From` implementations on error types now return a `Response<BoxBody>`. [#2468]
|
||||
* `ResponseBuilder::body(B)` now returns `Response<EitherBody<B>>`. [#2468]
|
||||
* `ResponseBuilder::finish()` now returns `Response<EitherBody<()>>`. [#2468]
|
||||
* `on_connect_ext` methods now receive a `CloneableExtensions` object. [#2327]
|
||||
|
||||
### Removed
|
||||
* `ResponseBuilder::streaming`. [#2468]
|
||||
@@ -42,14 +36,12 @@
|
||||
* `impl TryFrom<u16>` for `header::Quality`. [#2486]
|
||||
* `http` module. Most everything it contained is exported at the crate root. [#2488]
|
||||
|
||||
[#2327]: https://github.com/actix/actix-web/pull/2327
|
||||
[#2483]: https://github.com/actix/actix-web/pull/2483
|
||||
[#2468]: https://github.com/actix/actix-web/pull/2468
|
||||
[#1920]: https://github.com/actix/actix-web/pull/1920
|
||||
[#2486]: https://github.com/actix/actix-web/pull/2486
|
||||
[#2487]: https://github.com/actix/actix-web/pull/2487
|
||||
[#2488]: https://github.com/actix/actix-web/pull/2488
|
||||
[#2491]: https://github.com/actix/actix-web/pull/2491
|
||||
[#2497]: https://github.com/actix/actix-web/pull/2497
|
||||
|
||||
|
||||
## 3.0.0-beta.14 - 2021-11-30
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-http"
|
||||
version = "3.0.0-beta.15"
|
||||
version = "3.0.0-beta.14"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "HTTP primitives for the Actix ecosystem"
|
||||
keywords = ["actix", "http", "framework", "async", "futures"]
|
||||
@@ -56,7 +56,7 @@ derive_more = "0.99.5"
|
||||
encoding_rs = "0.8"
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc", "sink"] }
|
||||
h2 = "0.3.9"
|
||||
h2 = "0.3.1"
|
||||
http = "0.2.5"
|
||||
httparse = "1.5.1"
|
||||
httpdate = "1.0.1"
|
||||
@@ -81,11 +81,9 @@ flate2 = { version = "1.0.13", optional = true }
|
||||
zstd = { version = "0.9", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl"] }
|
||||
actix-web = "4.0.0-beta.14"
|
||||
|
||||
async-stream = "0.3"
|
||||
criterion = { version = "0.3", features = ["html_reports"] }
|
||||
env_logger = "0.9"
|
||||
@@ -97,7 +95,7 @@ serde_json = "1.0"
|
||||
static_assertions = "1"
|
||||
tls-openssl = { package = "openssl", version = "0.10.9" }
|
||||
tls-rustls = { package = "rustls", version = "0.20.0" }
|
||||
tokio = { version = "1.2", features = ["net", "rt", "macros"] }
|
||||
tokio = { version = "1.2", features = ["net", "rt"] }
|
||||
|
||||
[[example]]
|
||||
name = "ws"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> HTTP primitives for the Actix ecosystem.
|
||||
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://docs.rs/actix-http/3.0.0-beta.15)
|
||||
[](https://docs.rs/actix-http/3.0.0-beta.14)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.15)
|
||||
[](https://deps.rs/crate/actix-http/3.0.0-beta.14)
|
||||
[](https://crates.io/crates/actix-http)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -189,7 +189,11 @@ mod _original {
|
||||
n /= 100;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||
ptr::copy_nonoverlapping(
|
||||
lut_ptr.offset(d1 as isize),
|
||||
buf_ptr.offset(curr),
|
||||
2,
|
||||
);
|
||||
}
|
||||
|
||||
// decode last 1 or 2 chars
|
||||
@@ -202,7 +206,11 @@ mod _original {
|
||||
let d1 = n << 1;
|
||||
curr -= 2;
|
||||
unsafe {
|
||||
ptr::copy_nonoverlapping(lut_ptr.offset(d1 as isize), buf_ptr.offset(curr), 2);
|
||||
ptr::copy_nonoverlapping(
|
||||
lut_ptr.offset(d1 as isize),
|
||||
buf_ptr.offset(curr),
|
||||
2,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
@@ -54,10 +54,15 @@ const EMPTY_HEADER_INDEX: HeaderIndex = HeaderIndex {
|
||||
value: (0, 0),
|
||||
};
|
||||
|
||||
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] = [EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||
const EMPTY_HEADER_INDEX_ARRAY: [HeaderIndex; MAX_HEADERS] =
|
||||
[EMPTY_HEADER_INDEX; MAX_HEADERS];
|
||||
|
||||
impl HeaderIndex {
|
||||
fn record(bytes: &[u8], headers: &[httparse::Header<'_>], indices: &mut [HeaderIndex]) {
|
||||
fn record(
|
||||
bytes: &[u8],
|
||||
headers: &[httparse::Header<'_>],
|
||||
indices: &mut [HeaderIndex],
|
||||
) {
|
||||
let bytes_ptr = bytes.as_ptr() as usize;
|
||||
for (header, indices) in headers.iter().zip(indices.iter_mut()) {
|
||||
let name_start = header.name.as_ptr() as usize - bytes_ptr;
|
||||
|
@@ -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
|
||||
}
|
@@ -25,7 +25,10 @@ async fn main() -> io::Result<()> {
|
||||
|
||||
Ok::<_, Error>(
|
||||
Response::build(StatusCode::OK)
|
||||
.insert_header(("x-head", HeaderValue::from_static("dummy value!")))
|
||||
.insert_header((
|
||||
"x-head",
|
||||
HeaderValue::from_static("dummy value!"),
|
||||
))
|
||||
.body(body),
|
||||
)
|
||||
})
|
||||
|
@@ -1,7 +1,8 @@
|
||||
use std::io;
|
||||
|
||||
use actix_http::{
|
||||
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response, StatusCode,
|
||||
body::MessageBody, header::HeaderValue, Error, HttpService, Request, Response,
|
||||
StatusCode,
|
||||
};
|
||||
use actix_server::Server;
|
||||
use bytes::BytesMut;
|
||||
|
@@ -1,9 +1,8 @@
|
||||
use std::{convert::Infallible, io};
|
||||
|
||||
use actix_http::{
|
||||
header::HeaderValue, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||
};
|
||||
use actix_http::{HttpService, Response, StatusCode};
|
||||
use actix_server::Server;
|
||||
use http::header::HeaderValue;
|
||||
|
||||
#[actix_rt::main]
|
||||
async fn main() -> io::Result<()> {
|
||||
@@ -14,19 +13,13 @@ async fn main() -> io::Result<()> {
|
||||
HttpService::build()
|
||||
.client_timeout(1000)
|
||||
.client_disconnect(1000)
|
||||
.on_connect_ext(|_, ext| {
|
||||
ext.insert(42u32);
|
||||
})
|
||||
.finish(|req: Request| async move {
|
||||
.finish(|req| async move {
|
||||
log::info!("{:?}", req);
|
||||
|
||||
let mut res = Response::build(StatusCode::OK);
|
||||
res.insert_header(("x-head", HeaderValue::from_static("dummy value!")));
|
||||
|
||||
let forty_two = req.extensions().get::<u32>().unwrap().to_string();
|
||||
res.insert_header((
|
||||
"x-forty-two",
|
||||
HeaderValue::from_str(&forty_two).unwrap(),
|
||||
"x-head",
|
||||
HeaderValue::from_static("dummy value!"),
|
||||
));
|
||||
|
||||
Ok::<_, Infallible>(res.body("Hello world!"))
|
||||
|
@@ -60,7 +60,10 @@ impl Heartbeat {
|
||||
impl Stream for Heartbeat {
|
||||
type Item = Result<Bytes, Error>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
log::trace!("poll");
|
||||
|
||||
ready!(self.as_mut().interval.poll_tick(cx));
|
||||
|
5
actix-http/rustfmt.toml
Normal file
5
actix-http/rustfmt.toml
Normal file
@@ -0,0 +1,5 @@
|
||||
max_width = 89
|
||||
reorder_imports = true
|
||||
#wrap_comments = true
|
||||
#fn_args_density = "Compressed"
|
||||
#use_small_heuristics = false
|
@@ -165,7 +165,8 @@ mod tests {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn stream_delayed_error() {
|
||||
let body = BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
||||
let body =
|
||||
BodyStream::new(stream::iter(vec![Ok(Bytes::from("1")), Err(StreamErr)]));
|
||||
assert!(matches!(to_bytes(body).await, Err(StreamErr)));
|
||||
|
||||
pin_project! {
|
||||
|
@@ -24,7 +24,9 @@ impl BoxBody {
|
||||
}
|
||||
|
||||
/// Returns a mutable pinned reference to the inner message body type.
|
||||
pub fn as_pin_mut(&mut self) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
||||
pub fn as_pin_mut(
|
||||
&mut self,
|
||||
) -> Pin<&mut (dyn MessageBody<Error = Box<dyn StdError>>)> {
|
||||
self.0.as_mut()
|
||||
}
|
||||
}
|
||||
@@ -51,34 +53,6 @@ impl MessageBody for BoxBody {
|
||||
.poll_next(cx)
|
||||
.map_err(|err| Error::new_body().with_cause(err))
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
self.0.is_complete_body()
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
debug_assert!(
|
||||
self.is_complete_body(),
|
||||
"boxed type does not allow taking complete body; caller should make sure to \
|
||||
call `is_complete_body` first",
|
||||
);
|
||||
|
||||
// we do not have DerefMut access to call take_complete_body directly but since
|
||||
// is_complete_body is true we should expect the entire bytes chunk in one poll_next
|
||||
|
||||
let waker = futures_util::task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
match self.as_pin_mut().poll_next(&mut cx) {
|
||||
Poll::Ready(Some(Ok(data))) => data,
|
||||
_ => {
|
||||
panic!(
|
||||
"boxed type indicated it allows taking complete body but failed to \
|
||||
return Bytes when polled",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -67,20 +67,6 @@ where
|
||||
.map_err(|err| Error::new_body().with_cause(err)),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
match self {
|
||||
EitherBody::Left { body } => body.is_complete_body(),
|
||||
EitherBody::Right { body } => body.is_complete_body(),
|
||||
}
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
match self {
|
||||
EitherBody::Left { body } => body.take_complete_body(),
|
||||
EitherBody::Right { body } => body.take_complete_body(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@@ -25,58 +25,10 @@ pub trait MessageBody {
|
||||
fn size(&self) -> BodySize;
|
||||
|
||||
/// Attempt to pull out the next chunk of body bytes.
|
||||
// TODO: expand documentation
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>>;
|
||||
|
||||
/// Returns true if entire body bytes chunk is obtainable in one call to `poll_next`.
|
||||
///
|
||||
/// This method's implementation should agree with [`take_complete_body`] and should always be
|
||||
/// checked before taking the body.
|
||||
///
|
||||
/// The default implementation returns `false.
|
||||
///
|
||||
/// [`take_complete_body`]: MessageBody::take_complete_body
|
||||
fn is_complete_body(&self) -> bool {
|
||||
false
|
||||
}
|
||||
|
||||
/// Returns the complete chunk of body bytes.
|
||||
///
|
||||
/// Implementors of this method should note the following:
|
||||
/// - It is acceptable to skip the omit checks of [`is_complete_body`]. The responsibility of
|
||||
/// performing this check is delegated to the caller.
|
||||
/// - If the result of [`is_complete_body`] is conditional, that condition should be given
|
||||
/// equivalent attention here.
|
||||
/// - A second call call to [`take_complete_body`] should return an empty `Bytes` or panic.
|
||||
/// - A call to [`poll_next`] after calling [`take_complete_body`] should return `None` unless
|
||||
/// the chunk is guaranteed to be empty.
|
||||
///
|
||||
/// The default implementation panics unconditionally, indicating a control flow bug in the
|
||||
/// calling code.
|
||||
///
|
||||
/// # Panics
|
||||
/// With a correct implementation, panics if called without first checking [`is_complete_body`].
|
||||
///
|
||||
/// [`is_complete_body`]: MessageBody::is_complete_body
|
||||
/// [`take_complete_body`]: MessageBody::take_complete_body
|
||||
/// [`poll_next`]: MessageBody::poll_next
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
assert!(
|
||||
self.is_complete_body(),
|
||||
"type ({}) allows taking complete body but did not provide an implementation \
|
||||
of `take_complete_body`",
|
||||
std::any::type_name::<Self>()
|
||||
);
|
||||
|
||||
unimplemented!(
|
||||
"type ({}) does not allow taking complete body; caller should make sure to \
|
||||
check `is_complete_body` first",
|
||||
std::any::type_name::<Self>()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
mod foreign_impls {
|
||||
@@ -97,14 +49,6 @@ mod foreign_impls {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match *self {}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
match *self {}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for () {
|
||||
@@ -122,16 +66,6 @@ mod foreign_impls {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
Bytes::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Box<B>
|
||||
@@ -152,16 +86,6 @@ mod foreign_impls {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Pin::new(self.get_mut().as_mut()).poll_next(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_complete_body(&self) -> bool {
|
||||
self.as_ref().is_complete_body()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
self.as_mut().take_complete_body()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Pin<Box<B>>
|
||||
@@ -182,38 +106,6 @@ mod foreign_impls {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
self.as_mut().poll_next(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_complete_body(&self) -> bool {
|
||||
self.as_ref().is_complete_body()
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
debug_assert!(
|
||||
self.is_complete_body(),
|
||||
"inner type \"{}\" does not allow taking complete body; caller should make sure to \
|
||||
call `is_complete_body` first",
|
||||
std::any::type_name::<B>(),
|
||||
);
|
||||
|
||||
// we do not have DerefMut access to call take_complete_body directly but since
|
||||
// is_complete_body is true we should expect the entire bytes chunk in one poll_next
|
||||
|
||||
let waker = futures_util::task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
|
||||
match self.as_mut().poll_next(&mut cx) {
|
||||
Poll::Ready(Some(Ok(data))) => data,
|
||||
_ => {
|
||||
panic!(
|
||||
"inner type \"{}\" indicated it allows taking complete body but failed to \
|
||||
return Bytes when polled",
|
||||
std::any::type_name::<B>()
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static [u8] {
|
||||
@@ -224,23 +116,17 @@ mod foreign_impls {
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(self.take_complete_body())))
|
||||
let bytes = mem::take(self.get_mut());
|
||||
let bytes = Bytes::from_static(bytes);
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
Bytes::from_static(mem::take(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Bytes {
|
||||
@@ -251,23 +137,16 @@ mod foreign_impls {
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(self.take_complete_body())))
|
||||
let bytes = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
mem::take(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for BytesMut {
|
||||
@@ -278,23 +157,16 @@ mod foreign_impls {
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(self.take_complete_body())))
|
||||
let bytes = mem::take(self.get_mut()).freeze();
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
mem::take(self).freeze()
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for Vec<u8> {
|
||||
@@ -305,23 +177,16 @@ mod foreign_impls {
|
||||
}
|
||||
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
self: Pin<&mut Self>,
|
||||
_cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
if self.is_empty() {
|
||||
Poll::Ready(None)
|
||||
} else {
|
||||
Poll::Ready(Some(Ok(self.take_complete_body())))
|
||||
let bytes = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(Bytes::from(bytes))))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
Bytes::from(mem::take(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for &'static str {
|
||||
@@ -343,14 +208,6 @@ mod foreign_impls {
|
||||
Poll::Ready(Some(Ok(bytes)))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
Bytes::from_static(mem::take(self).as_bytes())
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for String {
|
||||
@@ -371,14 +228,6 @@ mod foreign_impls {
|
||||
Poll::Ready(Some(Ok(Bytes::from(string))))
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
Bytes::from(mem::take(self))
|
||||
}
|
||||
}
|
||||
|
||||
impl MessageBody for bytestring::ByteString {
|
||||
@@ -395,14 +244,6 @@ mod foreign_impls {
|
||||
let string = mem::take(self.get_mut());
|
||||
Poll::Ready(Some(Ok(string.into_bytes())))
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
mem::take(self).into_bytes()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -565,51 +406,6 @@ mod tests {
|
||||
assert_poll_next!(pl, Bytes::from("test"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_string() {
|
||||
let mut data = "test".repeat(2);
|
||||
let data_bytes = Bytes::from(data.clone());
|
||||
assert!(data.is_complete_body());
|
||||
assert_eq!(data.take_complete_body(), data_bytes);
|
||||
|
||||
let mut big_data = "test".repeat(64 * 1024);
|
||||
let data_bytes = Bytes::from(big_data.clone());
|
||||
assert!(big_data.is_complete_body());
|
||||
assert_eq!(big_data.take_complete_body(), data_bytes);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_boxed_equivalence() {
|
||||
let mut data = Bytes::from_static(b"test");
|
||||
assert!(data.is_complete_body());
|
||||
assert_eq!(data.take_complete_body(), b"test".as_ref());
|
||||
|
||||
let mut data = Box::new(Bytes::from_static(b"test"));
|
||||
assert!(data.is_complete_body());
|
||||
assert_eq!(data.take_complete_body(), b"test".as_ref());
|
||||
|
||||
let mut data = Box::pin(Bytes::from_static(b"test"));
|
||||
assert!(data.is_complete_body());
|
||||
assert_eq!(data.take_complete_body(), b"test".as_ref());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn take_policy() {
|
||||
let mut data = Bytes::from_static(b"test");
|
||||
// first call returns chunk
|
||||
assert_eq!(data.take_complete_body(), b"test".as_ref());
|
||||
// second call returns empty
|
||||
assert_eq!(data.take_complete_body(), b"".as_ref());
|
||||
|
||||
let waker = futures_util::task::noop_waker();
|
||||
let mut cx = Context::from_waker(&waker);
|
||||
let mut data = Bytes::from_static(b"test");
|
||||
// take returns whole chunk
|
||||
assert_eq!(data.take_complete_body(), b"test".as_ref());
|
||||
// subsequent poll_next returns None
|
||||
assert_eq!(Pin::new(&mut data).poll_next(&mut cx), Poll::Ready(None));
|
||||
}
|
||||
|
||||
// down-casting used to be done with a method on MessageBody trait
|
||||
// test is kept to demonstrate equivalence of Any trait
|
||||
#[actix_rt::test]
|
||||
|
@@ -40,14 +40,4 @@ impl MessageBody for None {
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
Poll::Ready(Option::None)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_complete_body(&self) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
Bytes::new()
|
||||
}
|
||||
}
|
||||
|
@@ -68,8 +68,9 @@ mod test {
|
||||
let bytes = to_bytes(body).await.unwrap();
|
||||
assert_eq!(bytes, b"123"[..]);
|
||||
|
||||
let stream = stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
|
||||
.map(Ok::<_, Error>);
|
||||
let stream =
|
||||
stream::iter(vec![Bytes::from_static(b"123"), Bytes::from_static(b"abc")])
|
||||
.map(Ok::<_, Error>);
|
||||
let body = BodyStream::new(stream);
|
||||
let bytes = to_bytes(body).await.unwrap();
|
||||
assert_eq!(bytes, b"123abc"[..]);
|
||||
|
@@ -6,10 +6,11 @@ use actix_service::{IntoServiceFactory, Service, ServiceFactory};
|
||||
use crate::{
|
||||
body::{BoxBody, MessageBody},
|
||||
config::{KeepAlive, ServiceConfig},
|
||||
extensions::CloneableExtensions,
|
||||
h1::{self, ExpectHandler, H1Service, UpgradeHandler},
|
||||
h2::H2Service,
|
||||
service::HttpService,
|
||||
ConnectCallback, Extensions, Request, Response,
|
||||
ConnectCallback, Request, Response,
|
||||
};
|
||||
|
||||
/// A HTTP service builder
|
||||
@@ -167,7 +168,7 @@ where
|
||||
/// and handlers.
|
||||
pub fn on_connect_ext<F>(mut self, f: F) -> Self
|
||||
where
|
||||
F: Fn(&T, &mut Extensions) + 'static,
|
||||
F: Fn(&T, &mut CloneableExtensions) + 'static,
|
||||
{
|
||||
self.on_connect_ext = Some(Rc::new(f));
|
||||
self
|
||||
@@ -214,7 +215,8 @@ where
|
||||
self.local_addr,
|
||||
);
|
||||
|
||||
H2Service::with_config(cfg, service.into_factory()).on_connect_ext(self.on_connect_ext)
|
||||
H2Service::with_config(cfg, service.into_factory())
|
||||
.on_connect_ext(self.on_connect_ext)
|
||||
}
|
||||
|
||||
/// Finish service configuration and create `HttpService` instance.
|
||||
|
@@ -44,17 +44,17 @@ where
|
||||
pub fn new(stream: S, encoding: ContentEncoding) -> Decoder<S> {
|
||||
let decoder = match encoding {
|
||||
#[cfg(feature = "compress-brotli")]
|
||||
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(BrotliDecoder::new(
|
||||
Writer::new(),
|
||||
)))),
|
||||
ContentEncoding::Br => Some(ContentDecoder::Br(Box::new(
|
||||
BrotliDecoder::new(Writer::new()),
|
||||
))),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Deflate => Some(ContentDecoder::Deflate(Box::new(
|
||||
ZlibDecoder::new(Writer::new()),
|
||||
))),
|
||||
#[cfg(feature = "compress-gzip")]
|
||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(GzDecoder::new(
|
||||
Writer::new(),
|
||||
)))),
|
||||
ContentEncoding::Gzip => Some(ContentDecoder::Gzip(Box::new(
|
||||
GzDecoder::new(Writer::new()),
|
||||
))),
|
||||
#[cfg(feature = "compress-zstd")]
|
||||
ContentEncoding::Zstd => Some(ContentDecoder::Zstd(Box::new(
|
||||
ZstdDecoder::new(Writer::new()).expect(
|
||||
@@ -93,7 +93,10 @@ where
|
||||
{
|
||||
type Item = Result<Bytes, PayloadError>;
|
||||
|
||||
fn poll_next(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
loop {
|
||||
if let Some(ref mut fut) = self.fut {
|
||||
let (chunk, decoder) =
|
||||
|
@@ -53,32 +53,36 @@ impl<B: MessageBody> Encoder<B> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn response(encoding: ContentEncoding, head: &mut ResponseHead, mut body: B) -> Self {
|
||||
pub fn response(
|
||||
encoding: ContentEncoding,
|
||||
head: &mut ResponseHead,
|
||||
body: B,
|
||||
) -> Self {
|
||||
let can_encode = !(head.headers().contains_key(&CONTENT_ENCODING)
|
||||
|| head.status == StatusCode::SWITCHING_PROTOCOLS
|
||||
|| head.status == StatusCode::NO_CONTENT
|
||||
|| encoding == ContentEncoding::Identity
|
||||
|| encoding == ContentEncoding::Auto);
|
||||
|
||||
// no need to compress an empty body
|
||||
if matches!(body.size(), BodySize::None) {
|
||||
return Self::none();
|
||||
match body.size() {
|
||||
// no need to compress an empty body
|
||||
BodySize::None => return Self::none(),
|
||||
|
||||
// we cannot assume that Sized is not a stream
|
||||
BodySize::Sized(_) | BodySize::Stream => {}
|
||||
}
|
||||
|
||||
let body = if body.is_complete_body() {
|
||||
let body = body.take_complete_body();
|
||||
EncoderBody::Full { body }
|
||||
} else {
|
||||
EncoderBody::Stream { body }
|
||||
};
|
||||
// TODO potentially some optimisation for single-chunk responses here by trying to read the
|
||||
// payload eagerly, stopping after 2 polls if the first is a chunk and the second is None
|
||||
|
||||
if can_encode {
|
||||
// Modify response body only if encoder is set
|
||||
if let Some(enc) = ContentEncoder::encoder(encoding) {
|
||||
update_head(encoding, head);
|
||||
head.no_chunking(false);
|
||||
|
||||
return Encoder {
|
||||
body,
|
||||
body: EncoderBody::Stream { body },
|
||||
encoder: Some(enc),
|
||||
fut: None,
|
||||
eof: false,
|
||||
@@ -87,7 +91,7 @@ impl<B: MessageBody> Encoder<B> {
|
||||
}
|
||||
|
||||
Encoder {
|
||||
body,
|
||||
body: EncoderBody::Stream { body },
|
||||
encoder: None,
|
||||
fut: None,
|
||||
eof: false,
|
||||
@@ -99,7 +103,6 @@ pin_project! {
|
||||
#[project = EncoderBodyProj]
|
||||
enum EncoderBody<B> {
|
||||
None,
|
||||
Full { body: Bytes },
|
||||
Stream { #[pin] body: B },
|
||||
}
|
||||
}
|
||||
@@ -113,7 +116,6 @@ where
|
||||
fn size(&self) -> BodySize {
|
||||
match self {
|
||||
EncoderBody::None => BodySize::None,
|
||||
EncoderBody::Full { body } => body.size(),
|
||||
EncoderBody::Stream { body } => body.size(),
|
||||
}
|
||||
}
|
||||
@@ -124,32 +126,12 @@ where
|
||||
) -> Poll<Option<Result<Bytes, Self::Error>>> {
|
||||
match self.project() {
|
||||
EncoderBodyProj::None => Poll::Ready(None),
|
||||
EncoderBodyProj::Full { body } => {
|
||||
Pin::new(body).poll_next(cx).map_err(|err| match err {})
|
||||
}
|
||||
|
||||
EncoderBodyProj::Stream { body } => body
|
||||
.poll_next(cx)
|
||||
.map_err(|err| EncoderError::Body(err.into())),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
match self {
|
||||
EncoderBody::None => true,
|
||||
EncoderBody::Full { .. } => true,
|
||||
EncoderBody::Stream { .. } => false,
|
||||
}
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
match self {
|
||||
EncoderBody::None => Bytes::new(),
|
||||
EncoderBody::Full { body } => body.take_complete_body(),
|
||||
EncoderBody::Stream { .. } => {
|
||||
panic!("EncoderBody::Stream variant cannot be taken")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> MessageBody for Encoder<B>
|
||||
@@ -159,10 +141,10 @@ where
|
||||
type Error = EncoderError;
|
||||
|
||||
fn size(&self) -> BodySize {
|
||||
if self.encoder.is_some() {
|
||||
BodySize::Stream
|
||||
} else {
|
||||
if self.encoder.is_none() {
|
||||
self.body.size()
|
||||
} else {
|
||||
BodySize::Stream
|
||||
}
|
||||
}
|
||||
|
||||
@@ -233,22 +215,6 @@ where
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_complete_body(&self) -> bool {
|
||||
if self.encoder.is_some() {
|
||||
false
|
||||
} else {
|
||||
self.body.is_complete_body()
|
||||
}
|
||||
}
|
||||
|
||||
fn take_complete_body(&mut self) -> Bytes {
|
||||
if self.encoder.is_some() {
|
||||
panic!("compressed body stream cannot be taken")
|
||||
} else {
|
||||
self.body.take_complete_body()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
@@ -256,8 +222,6 @@ fn update_head(encoding: ContentEncoding, head: &mut ResponseHead) {
|
||||
header::CONTENT_ENCODING,
|
||||
HeaderValue::from_static(encoding.as_str()),
|
||||
);
|
||||
|
||||
head.no_chunking(false);
|
||||
}
|
||||
|
||||
enum ContentEncoder {
|
||||
|
@@ -457,7 +457,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_payload_error() {
|
||||
let err: PayloadError = io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
||||
let err: PayloadError =
|
||||
io::Error::new(io::ErrorKind::Other, "ParseError").into();
|
||||
assert!(err.to_string().contains("ParseError"));
|
||||
|
||||
let err = PayloadError::Incomplete(None);
|
||||
|
@@ -19,7 +19,7 @@ impl Extensions {
|
||||
#[inline]
|
||||
pub fn new() -> Extensions {
|
||||
Extensions {
|
||||
map: AHashMap::new(),
|
||||
map: AHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -122,6 +122,13 @@ impl Extensions {
|
||||
pub fn extend(&mut self, other: Extensions) {
|
||||
self.map.extend(other.map);
|
||||
}
|
||||
|
||||
/// Sets (or overrides) items from cloneable extensions map into this map.
|
||||
pub(crate) fn clone_from(&mut self, other: &CloneableExtensions) {
|
||||
for (k, val) in &other.map {
|
||||
self.map.insert(*k, (**val).clone_to_any());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Extensions {
|
||||
@@ -134,6 +141,104 @@ fn downcast_owned<T: 'static>(boxed: Box<dyn Any>) -> Option<T> {
|
||||
boxed.downcast().ok().map(|boxed| *boxed)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait CloneToAny {
|
||||
/// Cast `self` into an `Any` reference.
|
||||
#[cfg(test)]
|
||||
fn any_ref(&self) -> &dyn Any;
|
||||
|
||||
/// Clone `self` to a new `Box<Any>` object.
|
||||
fn clone_to_any(&self) -> Box<dyn Any>;
|
||||
|
||||
/// Clone `self` to a new `Box<CloneAny>` object.
|
||||
fn clone_to_clone_any(&self) -> Box<dyn CloneAny>;
|
||||
}
|
||||
|
||||
impl<T: Clone + Any> CloneToAny for T {
|
||||
#[cfg(test)]
|
||||
fn any_ref(&self) -> &dyn Any {
|
||||
&*self
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_to_any(&self) -> Box<dyn Any> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn clone_to_clone_any(&self) -> Box<dyn CloneAny> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
}
|
||||
|
||||
/// An [`Any`] trait with an additional [`Clone`] requirement.
|
||||
pub trait CloneAny: CloneToAny + Any {}
|
||||
impl<T: Any + Clone> CloneAny for T {}
|
||||
|
||||
impl Clone for Box<dyn CloneAny> {
|
||||
#[inline]
|
||||
fn clone(&self) -> Self {
|
||||
(**self).clone_to_clone_any()
|
||||
}
|
||||
}
|
||||
|
||||
trait UncheckedAnyExt {
|
||||
/// # Safety
|
||||
/// Caller must ensure type `T` is true type.
|
||||
#[inline]
|
||||
unsafe fn downcast_unchecked<T: 'static>(self: Box<Self>) -> Box<T> {
|
||||
Box::from_raw(Box::into_raw(self) as *mut T)
|
||||
}
|
||||
}
|
||||
|
||||
impl UncheckedAnyExt for dyn CloneAny {}
|
||||
|
||||
/// A type map for `on_connect` extensions.
|
||||
///
|
||||
/// All entries into this map must be owned types and implement `Clone` trait.
|
||||
///
|
||||
/// Many requests can be processed for each connection but the `on_connect` will only be run once
|
||||
/// when the connection is opened. Therefore, items added to this special map type need to be cloned
|
||||
/// into the regular extensions map for each request. Most useful connection information types are
|
||||
/// cloneable already but you can use reference counted wrappers if not.
|
||||
#[derive(Default)]
|
||||
pub struct CloneableExtensions {
|
||||
/// Use AHasher with a std HashMap with for faster lookups on the small `TypeId` keys.
|
||||
map: AHashMap<TypeId, Box<dyn CloneAny>>,
|
||||
}
|
||||
|
||||
impl CloneableExtensions {
|
||||
/// Insert an item into the map.
|
||||
///
|
||||
/// If an item of this type was already stored, it will be replaced and returned.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// # use actix_http::Extensions;
|
||||
/// let mut map = Extensions::new();
|
||||
/// assert_eq!(map.insert(""), None);
|
||||
/// assert_eq!(map.insert(1u32), None);
|
||||
/// assert_eq!(map.insert(2u32), Some(1u32));
|
||||
/// assert_eq!(*map.get::<u32>().unwrap(), 2u32);
|
||||
/// ```
|
||||
pub fn insert<T: CloneAny>(&mut self, val: T) -> Option<T> {
|
||||
self.map
|
||||
.insert(TypeId::of::<T>(), Box::new(val))
|
||||
.map(|boxed| {
|
||||
// Safety:
|
||||
// Box is owned and `T` is known to be true type from map.
|
||||
*unsafe { UncheckedAnyExt::downcast_unchecked::<T>(boxed) }
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn get<T: CloneAny>(&self) -> Option<&T> {
|
||||
self.map
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|boxed| boxed.as_ref().any_ref().downcast_ref())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
@@ -277,4 +382,43 @@ mod tests {
|
||||
assert_eq!(extensions.get(), Some(&20u8));
|
||||
assert_eq!(extensions.get_mut(), Some(&mut 20u8));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clone_from() {
|
||||
#[derive(Clone)]
|
||||
struct NonCopy {
|
||||
num: u8,
|
||||
}
|
||||
|
||||
let mut ext = Extensions::new();
|
||||
ext.insert(2isize);
|
||||
|
||||
assert_eq!(ext.get::<isize>(), Some(&2isize));
|
||||
|
||||
let mut more_ext = CloneableExtensions::default();
|
||||
more_ext.insert(3isize);
|
||||
more_ext.insert(3usize);
|
||||
more_ext.insert(NonCopy { num: 8 });
|
||||
|
||||
ext.clone_from(&more_ext);
|
||||
|
||||
assert_eq!(ext.get::<isize>(), Some(&3isize));
|
||||
assert_eq!(ext.get::<usize>(), Some(&3usize));
|
||||
assert_eq!(more_ext.get::<isize>(), Some(&3isize));
|
||||
assert_eq!(more_ext.get::<usize>(), Some(&3usize));
|
||||
|
||||
assert!(ext.get::<NonCopy>().is_some());
|
||||
assert!(more_ext.get::<NonCopy>().is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn boxes_not_aliased() {
|
||||
let a: Box<dyn CloneAny> = Box::new(42);
|
||||
let b = a.clone_to_clone_any();
|
||||
assert_ne!(Box::into_raw(a) as *const (), Box::into_raw(b) as *const ());
|
||||
|
||||
let a: Box<dyn CloneAny> = Box::new(42);
|
||||
let b = a.clone_to_any();
|
||||
assert_ne!(Box::into_raw(a) as *const (), Box::into_raw(b) as *const ());
|
||||
}
|
||||
}
|
||||
|
@@ -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 rem = match byte!(rdr) {
|
||||
@@ -108,7 +111,10 @@ impl ChunkedState {
|
||||
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
|
||||
}
|
||||
}
|
||||
fn read_size_lf(rdr: &mut BytesMut, size: u64) -> Poll<Result<ChunkedState, io::Error>> {
|
||||
fn read_size_lf(
|
||||
rdr: &mut BytesMut,
|
||||
size: u64,
|
||||
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||
match byte!(rdr) {
|
||||
b'\n' if size > 0 => Poll::Ready(Ok(ChunkedState::Body)),
|
||||
b'\n' if size == 0 => Poll::Ready(Ok(ChunkedState::EndCr)),
|
||||
|
@@ -74,7 +74,8 @@ pub(crate) trait MessageType: Sized {
|
||||
let headers = self.headers_mut();
|
||||
|
||||
for idx in raw_headers.iter() {
|
||||
let name = HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
|
||||
let name =
|
||||
HeaderName::from_bytes(&slice[idx.name.0..idx.name.1]).unwrap();
|
||||
|
||||
// SAFETY: httparse already checks header value is only visible ASCII bytes
|
||||
// from_maybe_shared_unchecked contains debug assertions so they are omitted here
|
||||
@@ -604,7 +605,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_body() {
|
||||
let mut buf = BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||
let mut buf =
|
||||
BytesMut::from("GET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||
|
||||
let mut reader = MessageDecoder::<Request>::default();
|
||||
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
@@ -620,7 +622,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn test_parse_body_crlf() {
|
||||
let mut buf = BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||
let mut buf =
|
||||
BytesMut::from("\r\nGET /test HTTP/1.1\r\nContent-Length: 4\r\n\r\nbody");
|
||||
|
||||
let mut reader = MessageDecoder::<Request>::default();
|
||||
let (req, pl) = reader.decode(&mut buf).unwrap().unwrap();
|
||||
|
@@ -22,12 +22,11 @@ use crate::{
|
||||
config::ServiceConfig,
|
||||
error::{DispatchError, ParseError, PayloadError},
|
||||
service::HttpFlow,
|
||||
Extensions, OnConnectData, Request, Response, StatusCode,
|
||||
OnConnectData, Request, Response, StatusCode,
|
||||
};
|
||||
|
||||
use super::{
|
||||
codec::Codec,
|
||||
decoder::MAX_BUFFER_SIZE,
|
||||
payload::{Payload, PayloadSender, PayloadStatus},
|
||||
Message, MessageType,
|
||||
};
|
||||
@@ -101,9 +100,9 @@ where
|
||||
U::Error: fmt::Display,
|
||||
{
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
on_connect_data: OnConnectData,
|
||||
flags: Flags,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
conn_data: Option<Rc<Extensions>>,
|
||||
error: Option<DispatchError>,
|
||||
|
||||
#[pin]
|
||||
@@ -180,10 +179,10 @@ where
|
||||
/// Create HTTP/1 dispatcher.
|
||||
pub(crate) fn new(
|
||||
io: T,
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
config: ServiceConfig,
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
on_connect_data: OnConnectData,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
conn_data: OnConnectData,
|
||||
) -> Self {
|
||||
let flags = if config.keep_alive_enabled() {
|
||||
Flags::KEEPALIVE
|
||||
@@ -199,23 +198,20 @@ where
|
||||
|
||||
Dispatcher {
|
||||
inner: DispatcherState::Normal(InnerDispatcher {
|
||||
flow,
|
||||
flags,
|
||||
peer_addr,
|
||||
conn_data: conn_data.0.map(Rc::new),
|
||||
error: None,
|
||||
|
||||
state: State::None,
|
||||
payload: None,
|
||||
messages: VecDeque::new(),
|
||||
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
|
||||
io: Some(io),
|
||||
read_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
write_buf: BytesMut::with_capacity(HW_BUFFER_SIZE),
|
||||
payload: None,
|
||||
state: State::None,
|
||||
error: None,
|
||||
messages: VecDeque::new(),
|
||||
io: Some(io),
|
||||
codec: Codec::new(config),
|
||||
flow,
|
||||
on_connect_data,
|
||||
flags,
|
||||
peer_addr,
|
||||
ka_expire,
|
||||
ka_timer,
|
||||
}),
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -260,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 mut io = Pin::new(io.as_mut().unwrap());
|
||||
|
||||
@@ -270,7 +269,10 @@ where
|
||||
while written < len {
|
||||
match io.as_mut().poll_write(cx, &write_buf[written..])? {
|
||||
Poll::Ready(0) => {
|
||||
return Poll::Ready(Err(io::Error::new(io::ErrorKind::WriteZero, "")))
|
||||
return Poll::Ready(Err(io::Error::new(
|
||||
io::ErrorKind::WriteZero,
|
||||
"",
|
||||
)))
|
||||
}
|
||||
Poll::Ready(n) => written += n,
|
||||
Poll::Pending => {
|
||||
@@ -413,12 +415,15 @@ where
|
||||
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||
match stream.as_mut().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(item))) => {
|
||||
this.codec
|
||||
.encode(Message::Chunk(Some(item)), this.write_buf)?;
|
||||
this.codec.encode(
|
||||
Message::Chunk(Some(item)),
|
||||
this.write_buf,
|
||||
)?;
|
||||
}
|
||||
|
||||
Poll::Ready(None) => {
|
||||
this.codec.encode(Message::Chunk(None), this.write_buf)?;
|
||||
this.codec
|
||||
.encode(Message::Chunk(None), this.write_buf)?;
|
||||
// payload stream finished.
|
||||
// set state to None and handle next message
|
||||
this.state.set(State::None);
|
||||
@@ -445,12 +450,15 @@ where
|
||||
while this.write_buf.len() < super::payload::MAX_BUFFER_SIZE {
|
||||
match stream.as_mut().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(item))) => {
|
||||
this.codec
|
||||
.encode(Message::Chunk(Some(item)), this.write_buf)?;
|
||||
this.codec.encode(
|
||||
Message::Chunk(Some(item)),
|
||||
this.write_buf,
|
||||
)?;
|
||||
}
|
||||
|
||||
Poll::Ready(None) => {
|
||||
this.codec.encode(Message::Chunk(None), this.write_buf)?;
|
||||
this.codec
|
||||
.encode(Message::Chunk(None), this.write_buf)?;
|
||||
// payload stream finished.
|
||||
// set state to None and handle next message
|
||||
this.state.set(State::None);
|
||||
@@ -556,11 +564,9 @@ where
|
||||
}
|
||||
};
|
||||
}
|
||||
_ => {
|
||||
unreachable!(
|
||||
"State must be set to ServiceCall or ExceptCall in handle_request"
|
||||
)
|
||||
}
|
||||
_ => unreachable!(
|
||||
"State must be set to ServiceCall or ExceptCall in handle_request"
|
||||
),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -587,14 +593,16 @@ where
|
||||
Message::Item(mut req) => {
|
||||
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() {
|
||||
// Request is upgradable. add upgrade message and break.
|
||||
// everything remain in read buffer would be handed to
|
||||
// upgraded Request.
|
||||
MessageType::Stream if this.flow.upgrade.is_some() => {
|
||||
this.messages.push_back(DispatcherMessage::Upgrade(req));
|
||||
this.messages
|
||||
.push_back(DispatcherMessage::Upgrade(req));
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -609,7 +617,8 @@ where
|
||||
where the state can be collected and consumed.
|
||||
*/
|
||||
let (ps, pl) = Payload::create(false);
|
||||
let (req1, _) = req.replace_payload(crate::Payload::H1(pl));
|
||||
let (req1, _) =
|
||||
req.replace_payload(crate::Payload::H1(pl));
|
||||
req = req1;
|
||||
*this.payload = Some(ps);
|
||||
}
|
||||
@@ -630,7 +639,9 @@ where
|
||||
if let Some(ref mut payload) = this.payload {
|
||||
payload.feed_data(chunk);
|
||||
} else {
|
||||
error!("Internal server error: unexpected payload chunk");
|
||||
error!(
|
||||
"Internal server error: unexpected payload chunk"
|
||||
);
|
||||
this.flags.insert(Flags::READ_DISCONNECT);
|
||||
this.messages.push_back(DispatcherMessage::Error(
|
||||
Response::internal_server_error().drop_body(),
|
||||
@@ -668,11 +679,12 @@ where
|
||||
payload.set_error(PayloadError::Overflow);
|
||||
}
|
||||
// Requests overflow buffer size should be responded with 431
|
||||
this.messages
|
||||
.push_back(DispatcherMessage::Error(Response::with_body(
|
||||
this.messages.push_back(DispatcherMessage::Error(
|
||||
Response::with_body(
|
||||
StatusCode::REQUEST_HEADER_FIELDS_TOO_LARGE,
|
||||
(),
|
||||
)));
|
||||
),
|
||||
));
|
||||
this.flags.insert(Flags::READ_DISCONNECT);
|
||||
*this.error = Some(ParseError::TooLarge.into());
|
||||
break;
|
||||
@@ -714,7 +726,8 @@ where
|
||||
None => {
|
||||
// conditionally go into shutdown timeout
|
||||
if this.flags.contains(Flags::SHUTDOWN) {
|
||||
if let Some(deadline) = this.codec.config().client_disconnect_timer() {
|
||||
if let Some(deadline) = this.codec.config().client_disconnect_timer()
|
||||
{
|
||||
// write client disconnect time out and poll again to
|
||||
// go into Some<Pin<&mut Sleep>> branch
|
||||
this.ka_timer.set(Some(sleep_until(deadline)));
|
||||
@@ -757,7 +770,9 @@ where
|
||||
this.flags.insert(Flags::STARTED | Flags::SHUTDOWN);
|
||||
}
|
||||
// still have unfinished task. try to reset and register keep-alive.
|
||||
} else if let Some(deadline) = this.codec.config().keep_alive_expire() {
|
||||
} else if let Some(deadline) =
|
||||
this.codec.config().keep_alive_expire()
|
||||
{
|
||||
timer.as_mut().reset(deadline);
|
||||
let _ = timer.poll(cx);
|
||||
}
|
||||
@@ -776,6 +791,7 @@ where
|
||||
/// Returns true when io stream can be disconnected after write to it.
|
||||
///
|
||||
/// It covers these conditions:
|
||||
///
|
||||
/// - `std::io::ErrorKind::ConnectionReset` after partial read.
|
||||
/// - all data read done.
|
||||
#[inline(always)]
|
||||
@@ -795,39 +811,46 @@ where
|
||||
|
||||
loop {
|
||||
// Return early when read buf exceed decoder's max buffer size.
|
||||
if this.read_buf.len() >= MAX_BUFFER_SIZE {
|
||||
// At this point it's not known IO stream is still scheduled to be waked up so
|
||||
// force wake up dispatcher just in case.
|
||||
//
|
||||
// Reason:
|
||||
// AsyncRead mostly would only have guarantee wake up when the poll_read
|
||||
// return Poll::Pending.
|
||||
//
|
||||
// Case:
|
||||
// When read_buf is beyond max buffer size the early return could be successfully
|
||||
// be parsed as a new Request. This case would not generate ParseError::TooLarge and
|
||||
// at this point IO stream is not fully read to Pending and would result in
|
||||
// dispatcher stuck until timeout (KA)
|
||||
//
|
||||
// Note:
|
||||
// This is a perf choice to reduce branch on <Request as MessageType>::decode.
|
||||
//
|
||||
// A Request head too large to parse is only checked on
|
||||
// `httparse::Status::Partial` condition.
|
||||
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.
|
||||
|
||||
Reason:
|
||||
AsyncRead mostly would only have guarantee wake up
|
||||
when the poll_read return Poll::Pending.
|
||||
|
||||
Case:
|
||||
When read_buf is beyond max buffer size the early return
|
||||
could be successfully be parsed as a new Request.
|
||||
This case would not generate ParseError::TooLarge
|
||||
and at this point IO stream is not fully read to Pending
|
||||
and would result in dispatcher stuck until timeout (KA)
|
||||
|
||||
Note:
|
||||
This is a perf choice to reduce branch on
|
||||
<Request as MessageType>::decode.
|
||||
|
||||
A Request head too large to parse is only checked on
|
||||
httparse::Status::Partial condition.
|
||||
*/
|
||||
if this.payload.is_none() {
|
||||
// When dispatcher has a payload the responsibility of wake up it would be shift
|
||||
// to h1::payload::Payload.
|
||||
//
|
||||
// Reason:
|
||||
// Self wake up when there is payload would waste poll and/or result in
|
||||
// over read.
|
||||
//
|
||||
// Case:
|
||||
// When payload is (partial) dropped by user there is no need to do
|
||||
// read anymore. At this case read_buf could always remain beyond
|
||||
// MAX_BUFFER_SIZE and self wake up would be busy poll dispatcher and
|
||||
// waste resources.
|
||||
/*
|
||||
When dispatcher has a payload the responsibility of
|
||||
wake up it would be shift to h1::payload::Payload.
|
||||
|
||||
Reason:
|
||||
Self wake up when there is payload would waste poll
|
||||
and/or result in over read.
|
||||
|
||||
Case:
|
||||
When payload is (partial) dropped by user there is
|
||||
no need to do read anymore.
|
||||
At this case read_buf could always remain beyond
|
||||
MAX_BUFFER_SIZE and self wake up would be busy poll
|
||||
dispatcher and waste resource.
|
||||
|
||||
*/
|
||||
cx.waker().wake_by_ref();
|
||||
}
|
||||
|
||||
@@ -1035,12 +1058,14 @@ mod tests {
|
||||
}
|
||||
|
||||
fn ok_service(
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||
{
|
||||
fn_service(|_req: Request| ready(Ok::<_, Error>(Response::ok())))
|
||||
}
|
||||
|
||||
fn echo_path_service(
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error> {
|
||||
) -> impl Service<Request, Response = Response<impl MessageBody>, Error = Error>
|
||||
{
|
||||
fn_service(|req: Request| {
|
||||
let path = req.path().as_bytes();
|
||||
ready(Ok::<_, Error>(
|
||||
@@ -1049,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| {
|
||||
Box::pin(async move {
|
||||
use futures_util::stream::StreamExt as _;
|
||||
@@ -1075,10 +1100,10 @@ mod tests {
|
||||
|
||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||
buf,
|
||||
services,
|
||||
ServiceConfig::default(),
|
||||
None,
|
||||
services,
|
||||
OnConnectData::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
@@ -1115,10 +1140,10 @@ mod tests {
|
||||
|
||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||
buf,
|
||||
services,
|
||||
cfg,
|
||||
None,
|
||||
services,
|
||||
OnConnectData::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
@@ -1169,10 +1194,10 @@ mod tests {
|
||||
|
||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||
buf,
|
||||
services,
|
||||
cfg,
|
||||
None,
|
||||
services,
|
||||
OnConnectData::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
actix_rt::pin!(h1);
|
||||
@@ -1219,10 +1244,10 @@ mod tests {
|
||||
|
||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||
buf.clone(),
|
||||
services,
|
||||
cfg,
|
||||
None,
|
||||
services,
|
||||
OnConnectData::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
buf.extend_read_buf(
|
||||
@@ -1291,10 +1316,10 @@ mod tests {
|
||||
|
||||
let h1 = Dispatcher::<_, _, _, _, UpgradeHandler>::new(
|
||||
buf.clone(),
|
||||
services,
|
||||
cfg,
|
||||
None,
|
||||
services,
|
||||
OnConnectData::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
buf.extend_read_buf(
|
||||
@@ -1368,10 +1393,10 @@ mod tests {
|
||||
|
||||
let h1 = Dispatcher::<_, _, _, _, TestUpgrade>::new(
|
||||
buf.clone(),
|
||||
services,
|
||||
cfg,
|
||||
None,
|
||||
services,
|
||||
OnConnectData::default(),
|
||||
None,
|
||||
);
|
||||
|
||||
buf.extend_read_buf(
|
||||
|
@@ -103,7 +103,9 @@ pub(crate) trait MessageType: Sized {
|
||||
dst.put_slice(b"\r\n");
|
||||
}
|
||||
}
|
||||
BodySize::Sized(0) if camel_case => dst.put_slice(b"\r\nContent-Length: 0\r\n"),
|
||||
BodySize::Sized(0) if camel_case => {
|
||||
dst.put_slice(b"\r\nContent-Length: 0\r\n")
|
||||
}
|
||||
BodySize::Sized(0) => dst.put_slice(b"\r\ncontent-length: 0\r\n"),
|
||||
BodySize::Sized(len) => helpers::write_content_length(len, dst),
|
||||
BodySize::None => dst.put_slice(b"\r\n"),
|
||||
@@ -305,7 +307,11 @@ impl MessageType for RequestHeadType {
|
||||
Version::HTTP_11 => "HTTP/1.1",
|
||||
Version::HTTP_2 => "HTTP/2.0",
|
||||
Version::HTTP_3 => "HTTP/3.0",
|
||||
_ => return Err(io::Error::new(io::ErrorKind::Other, "unsupported version")),
|
||||
_ =>
|
||||
return Err(io::Error::new(
|
||||
io::ErrorKind::Other,
|
||||
"unsupported version"
|
||||
)),
|
||||
}
|
||||
)
|
||||
.map_err(|e| io::Error::new(io::ErrorKind::Other, e))
|
||||
@@ -562,7 +568,8 @@ mod tests {
|
||||
ConnectionType::Close,
|
||||
&ServiceConfig::default(),
|
||||
);
|
||||
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
let data =
|
||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
|
||||
assert!(data.contains("Content-Length: 0\r\n"));
|
||||
assert!(data.contains("Connection: close\r\n"));
|
||||
@@ -576,7 +583,8 @@ mod tests {
|
||||
ConnectionType::KeepAlive,
|
||||
&ServiceConfig::default(),
|
||||
);
|
||||
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
let data =
|
||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
assert!(data.contains("Transfer-Encoding: chunked\r\n"));
|
||||
assert!(data.contains("Content-Type: plain/text\r\n"));
|
||||
assert!(data.contains("Date: date\r\n"));
|
||||
@@ -597,7 +605,8 @@ mod tests {
|
||||
ConnectionType::KeepAlive,
|
||||
&ServiceConfig::default(),
|
||||
);
|
||||
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
let data =
|
||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
assert!(data.contains("transfer-encoding: chunked\r\n"));
|
||||
assert!(data.contains("content-type: xml\r\n"));
|
||||
assert!(data.contains("content-type: plain/text\r\n"));
|
||||
@@ -630,7 +639,8 @@ mod tests {
|
||||
ConnectionType::Close,
|
||||
&ServiceConfig::default(),
|
||||
);
|
||||
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
let data =
|
||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
assert!(data.contains("content-length: 0\r\n"));
|
||||
assert!(data.contains("connection: close\r\n"));
|
||||
assert!(data.contains("authorization: another authorization\r\n"));
|
||||
@@ -653,7 +663,8 @@ mod tests {
|
||||
ConnectionType::Upgrade,
|
||||
&ServiceConfig::default(),
|
||||
);
|
||||
let data = String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
let data =
|
||||
String::from_utf8(Vec::from(bytes.split().freeze().as_ref())).unwrap();
|
||||
assert!(!data.contains("content-length: 0\r\n"));
|
||||
assert!(!data.contains("transfer-encoding: chunked\r\n"));
|
||||
}
|
||||
|
@@ -227,7 +227,10 @@ impl Inner {
|
||||
self.len
|
||||
}
|
||||
|
||||
fn readany(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
||||
fn readany(
|
||||
&mut self,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Result<Bytes, PayloadError>>> {
|
||||
if let Some(data) = self.items.pop_front() {
|
||||
self.len -= data.len();
|
||||
self.need_read = self.len < MAX_BUFFER_SIZE;
|
||||
|
@@ -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
|
||||
T: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||
|
||||
@@ -309,9 +310,9 @@ where
|
||||
|
||||
let upgrade = match upgrade {
|
||||
Some(upgrade) => {
|
||||
let upgrade = upgrade
|
||||
.await
|
||||
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
|
||||
let upgrade = upgrade.await.map_err(|e| {
|
||||
log::error!("Init http upgrade service error: {:?}", e)
|
||||
})?;
|
||||
Some(upgrade)
|
||||
}
|
||||
None => None,
|
||||
@@ -335,7 +336,8 @@ where
|
||||
/// `Service` implementation for HTTP/1 transport
|
||||
pub type H1ServiceHandler<T, S, B, X, U> = HttpServiceHandler<T, S, B, X, U>;
|
||||
|
||||
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)> for HttpServiceHandler<T, S, B, X, U>
|
||||
impl<T, S, B, X, U> Service<(T, Option<net::SocketAddr>)>
|
||||
for HttpServiceHandler<T, S, B, X, U>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
|
||||
@@ -363,7 +365,15 @@ where
|
||||
}
|
||||
|
||||
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||
let conn_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||
Dispatcher::new(io, self.flow.clone(), self.cfg.clone(), addr, conn_data)
|
||||
let on_connect_data =
|
||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||
|
||||
Dispatcher::new(
|
||||
io,
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
addr,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
@@ -70,12 +70,15 @@ where
|
||||
.unwrap()
|
||||
.is_write_buf_full()
|
||||
{
|
||||
let next = match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
|
||||
Poll::Ready(Some(Err(err))) => return Poll::Ready(Err(err.into())),
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Pending => Poll::Pending,
|
||||
};
|
||||
let next =
|
||||
match this.body.as_mut().as_pin_mut().unwrap().poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(item))) => Poll::Ready(Some(item)),
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
return Poll::Ready(Err(err.into()))
|
||||
}
|
||||
Poll::Ready(None) => Poll::Ready(None),
|
||||
Poll::Pending => Poll::Pending,
|
||||
};
|
||||
|
||||
match next {
|
||||
Poll::Ready(item) => {
|
||||
@@ -85,9 +88,9 @@ where
|
||||
let _ = this.body.take();
|
||||
}
|
||||
let framed = this.framed.as_mut().as_pin_mut().unwrap();
|
||||
framed
|
||||
.write(Message::Chunk(item))
|
||||
.map_err(|err| Error::new_send_response().with_cause(err))?;
|
||||
framed.write(Message::Chunk(item)).map_err(|err| {
|
||||
Error::new_send_response().with_cause(err)
|
||||
})?;
|
||||
}
|
||||
Poll::Pending => body_ready = false,
|
||||
}
|
||||
|
@@ -19,15 +19,15 @@ use h2::{
|
||||
server::{Connection, SendResponse},
|
||||
Ping, PingPong,
|
||||
};
|
||||
use http::header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING};
|
||||
use log::{error, trace};
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::{
|
||||
body::{BodySize, BoxBody, MessageBody},
|
||||
config::ServiceConfig,
|
||||
header::{HeaderValue, CONNECTION, CONTENT_LENGTH, DATE, TRANSFER_ENCODING},
|
||||
service::HttpFlow,
|
||||
Extensions, OnConnectData, Payload, Request, Response, ResponseHead,
|
||||
OnConnectData, Payload, Request, Response, ResponseHead,
|
||||
};
|
||||
|
||||
const CHUNK_SIZE: usize = 16_384;
|
||||
@@ -37,7 +37,7 @@ pin_project! {
|
||||
pub struct Dispatcher<T, S, B, X, U> {
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
connection: Connection<T, Bytes>,
|
||||
conn_data: Option<Rc<Extensions>>,
|
||||
on_connect_data: OnConnectData,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
ping_pong: Option<H2PingPong>,
|
||||
@@ -50,11 +50,11 @@ where
|
||||
T: AsyncRead + AsyncWrite + Unpin,
|
||||
{
|
||||
pub(crate) fn new(
|
||||
mut conn: Connection<T, Bytes>,
|
||||
flow: Rc<HttpFlow<S, X, U>>,
|
||||
mut conn: Connection<T, Bytes>,
|
||||
on_connect_data: OnConnectData,
|
||||
config: ServiceConfig,
|
||||
peer_addr: Option<net::SocketAddr>,
|
||||
conn_data: OnConnectData,
|
||||
timer: Option<Pin<Box<Sleep>>>,
|
||||
) -> Self {
|
||||
let ping_pong = config.keep_alive().map(|dur| H2PingPong {
|
||||
@@ -74,7 +74,7 @@ where
|
||||
config,
|
||||
peer_addr,
|
||||
connection: conn,
|
||||
conn_data: conn_data.0.map(Rc::new),
|
||||
on_connect_data,
|
||||
ping_pong,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
@@ -109,7 +109,7 @@ where
|
||||
Poll::Ready(Some((req, tx))) => {
|
||||
let (parts, body) = req.into_parts();
|
||||
let pl = crate::h2::Payload::new(body);
|
||||
let pl = Payload::H2(pl);
|
||||
let pl = Payload::<crate::payload::PayloadStream>::H2(pl);
|
||||
let mut req = Request::with_payload(pl);
|
||||
|
||||
let head = req.head_mut();
|
||||
@@ -119,7 +119,8 @@ where
|
||||
head.headers = parts.headers.into();
|
||||
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 config = this.config.clone();
|
||||
@@ -160,11 +161,16 @@ where
|
||||
Poll::Ready(_) => {
|
||||
ping_pong.on_flight = false;
|
||||
|
||||
let dead_line = this.config.keep_alive_expire().unwrap();
|
||||
let dead_line =
|
||||
this.config.keep_alive_expire().unwrap();
|
||||
ping_pong.timer.as_mut().reset(dead_line);
|
||||
}
|
||||
Poll::Pending => {
|
||||
return ping_pong.timer.as_mut().poll(cx).map(|_| Ok(()))
|
||||
return ping_pong
|
||||
.timer
|
||||
.as_mut()
|
||||
.poll(cx)
|
||||
.map(|_| Ok(()))
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@@ -217,28 +223,25 @@ where
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// poll response body and send chunks to client
|
||||
// poll response body and send chunks to client.
|
||||
actix_rt::pin!(body);
|
||||
|
||||
while let Some(res) = poll_fn(|cx| body.as_mut().poll_next(cx)).await {
|
||||
let mut chunk = res.map_err(|err| DispatchError::ResponseBody(err.into()))?;
|
||||
|
||||
'send: loop {
|
||||
let chunk_size = cmp::min(chunk.len(), CHUNK_SIZE);
|
||||
|
||||
// reserve enough space and wait for stream ready.
|
||||
stream.reserve_capacity(chunk_size);
|
||||
stream.reserve_capacity(cmp::min(chunk.len(), CHUNK_SIZE));
|
||||
|
||||
match poll_fn(|cx| stream.poll_capacity(cx)).await {
|
||||
// No capacity left. drop body and return.
|
||||
None => return Ok(()),
|
||||
Some(res) => {
|
||||
// Split chuck to writeable size and send to client.
|
||||
let cap = res.map_err(DispatchError::SendData)?;
|
||||
|
||||
Some(Err(err)) => return Err(DispatchError::SendData(err)),
|
||||
|
||||
Some(Ok(cap)) => {
|
||||
// split chunk to writeable size and send to client
|
||||
let len = chunk.len();
|
||||
let bytes = chunk.split_to(cmp::min(len, cap));
|
||||
let bytes = chunk.split_to(cmp::min(cap, len));
|
||||
|
||||
stream
|
||||
.send_data(bytes, false)
|
||||
|
@@ -40,7 +40,10 @@ impl Payload {
|
||||
impl Stream for Payload {
|
||||
type Item = Result<Bytes, PayloadError>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
let this = self.get_mut();
|
||||
|
||||
match ready!(Pin::new(&mut this.stream).poll_data(cx)) {
|
||||
|
@@ -1,7 +1,7 @@
|
||||
use std::{
|
||||
future::Future,
|
||||
marker::PhantomData,
|
||||
mem, net,
|
||||
net,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
@@ -10,7 +10,8 @@ use std::{
|
||||
use actix_codec::{AsyncRead, AsyncWrite};
|
||||
use actix_rt::net::TcpStream;
|
||||
use actix_service::{
|
||||
fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory, ServiceFactoryExt as _,
|
||||
fn_factory, fn_service, IntoServiceFactory, Service, ServiceFactory,
|
||||
ServiceFactoryExt as _,
|
||||
};
|
||||
use actix_utils::future::ready;
|
||||
use futures_core::{future::LocalBoxFuture, ready};
|
||||
@@ -278,7 +279,8 @@ where
|
||||
}
|
||||
|
||||
fn call(&self, (io, addr): (T, Option<net::SocketAddr>)) -> Self::Future {
|
||||
let on_connect_data = OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||
let on_connect_data =
|
||||
OnConnectData::from_io(&io, self.on_connect_ext.as_deref());
|
||||
|
||||
H2ServiceHandlerResponse {
|
||||
state: State::Handshake(
|
||||
@@ -337,24 +339,21 @@ where
|
||||
ref mut srv,
|
||||
ref mut config,
|
||||
ref peer_addr,
|
||||
ref mut conn_data,
|
||||
ref mut on_connect_data,
|
||||
ref mut handshake,
|
||||
) => match ready!(Pin::new(handshake).poll(cx)) {
|
||||
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(
|
||||
conn,
|
||||
srv.take().unwrap(),
|
||||
conn,
|
||||
on_connect_data,
|
||||
config.take().unwrap(),
|
||||
*peer_addr,
|
||||
on_connect_data,
|
||||
timer,
|
||||
));
|
||||
|
||||
self.poll(cx)
|
||||
}
|
||||
|
||||
Err(err) => {
|
||||
trace!("H2 handshake error: {}", err);
|
||||
Poll::Ready(Err(err))
|
||||
|
@@ -6,7 +6,7 @@ use http::header::{HeaderName, InvalidHeaderName};
|
||||
|
||||
/// 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 struct Seal;
|
||||
|
@@ -12,7 +12,7 @@ use super::{Header, IntoHeaderValue};
|
||||
/// An interface for types that can be converted into a [`HeaderName`]/[`HeaderValue`] pair for
|
||||
/// insertion into a [`HeaderMap`].
|
||||
///
|
||||
/// [`HeaderMap`]: super::HeaderMap
|
||||
/// [`HeaderMap`]: crate::http::HeaderMap
|
||||
pub trait IntoHeaderPair: Sized {
|
||||
type Error: Into<HttpError>;
|
||||
|
||||
|
@@ -123,11 +123,12 @@ impl HeaderMap {
|
||||
let mut map = HeaderMap::with_capacity(capacity);
|
||||
map.append(first_name.clone(), first_value);
|
||||
|
||||
let (map, _) = drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
|
||||
let name = name.unwrap_or(prev_name);
|
||||
map.append(name.clone(), value);
|
||||
(map, name)
|
||||
});
|
||||
let (map, _) =
|
||||
drain.fold((map, first_name), |(mut map, prev_name), (name, value)| {
|
||||
let name = name.unwrap_or(prev_name);
|
||||
map.append(name.clone(), value);
|
||||
(map, name)
|
||||
});
|
||||
|
||||
map
|
||||
}
|
||||
|
@@ -11,20 +11,22 @@ pub use http::header::{
|
||||
pub use http::header::{
|
||||
ACCEPT, ACCEPT_CHARSET, ACCEPT_ENCODING, ACCEPT_LANGUAGE, ACCEPT_RANGES,
|
||||
ACCESS_CONTROL_ALLOW_CREDENTIALS, ACCESS_CONTROL_ALLOW_HEADERS,
|
||||
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_EXPOSE_HEADERS,
|
||||
ACCESS_CONTROL_MAX_AGE, ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE,
|
||||
ALLOW, ALT_SVC, AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION,
|
||||
CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
|
||||
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE, DATE,
|
||||
DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH, IF_MODIFIED_SINCE,
|
||||
IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED, LINK, LOCATION, MAX_FORWARDS,
|
||||
ORIGIN, PRAGMA, PROXY_AUTHENTICATE, PROXY_AUTHORIZATION, PUBLIC_KEY_PINS,
|
||||
PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER, REFERRER_POLICY, REFRESH, RETRY_AFTER,
|
||||
SEC_WEBSOCKET_ACCEPT, SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
|
||||
ACCESS_CONTROL_ALLOW_METHODS, ACCESS_CONTROL_ALLOW_ORIGIN,
|
||||
ACCESS_CONTROL_EXPOSE_HEADERS, ACCESS_CONTROL_MAX_AGE,
|
||||
ACCESS_CONTROL_REQUEST_HEADERS, ACCESS_CONTROL_REQUEST_METHOD, AGE, ALLOW, ALT_SVC,
|
||||
AUTHORIZATION, CACHE_CONTROL, CONNECTION, CONTENT_DISPOSITION, CONTENT_ENCODING,
|
||||
CONTENT_LANGUAGE, CONTENT_LENGTH, CONTENT_LOCATION, CONTENT_RANGE,
|
||||
CONTENT_SECURITY_POLICY, CONTENT_SECURITY_POLICY_REPORT_ONLY, CONTENT_TYPE, COOKIE,
|
||||
DATE, DNT, ETAG, EXPECT, EXPIRES, FORWARDED, FROM, HOST, IF_MATCH,
|
||||
IF_MODIFIED_SINCE, IF_NONE_MATCH, IF_RANGE, IF_UNMODIFIED_SINCE, LAST_MODIFIED,
|
||||
LINK, LOCATION, MAX_FORWARDS, ORIGIN, PRAGMA, PROXY_AUTHENTICATE,
|
||||
PROXY_AUTHORIZATION, PUBLIC_KEY_PINS, PUBLIC_KEY_PINS_REPORT_ONLY, RANGE, REFERER,
|
||||
REFERRER_POLICY, REFRESH, RETRY_AFTER, SEC_WEBSOCKET_ACCEPT,
|
||||
SEC_WEBSOCKET_EXTENSIONS, SEC_WEBSOCKET_KEY, SEC_WEBSOCKET_PROTOCOL,
|
||||
SEC_WEBSOCKET_VERSION, SERVER, SET_COOKIE, STRICT_TRANSPORT_SECURITY, TE, TRAILER,
|
||||
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA, WARNING,
|
||||
WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL, X_FRAME_OPTIONS,
|
||||
X_XSS_PROTECTION,
|
||||
TRANSFER_ENCODING, UPGRADE, UPGRADE_INSECURE_REQUESTS, USER_AGENT, VARY, VIA,
|
||||
WARNING, WWW_AUTHENTICATE, X_CONTENT_TYPE_OPTIONS, X_DNS_PREFETCH_CONTROL,
|
||||
X_FRAME_OPTIONS, X_XSS_PROTECTION,
|
||||
};
|
||||
|
||||
use crate::{error::ParseError, HttpMessage};
|
||||
@@ -41,8 +43,8 @@ pub use self::into_pair::IntoHeaderPair;
|
||||
pub use self::into_value::IntoHeaderValue;
|
||||
pub use self::map::HeaderMap;
|
||||
pub use self::shared::{
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate, LanguageTag,
|
||||
Quality, QualityItem,
|
||||
parse_extended_value, q, Charset, ContentEncoding, ExtendedValue, HttpDate,
|
||||
LanguageTag, Quality, QualityItem,
|
||||
};
|
||||
pub use self::utils::{
|
||||
fmt_comma_delimited, from_comma_delimited, from_one_raw_str, http_percent_encode,
|
||||
|
@@ -63,7 +63,9 @@ pub struct ExtendedValue {
|
||||
/// [RFC 2231 §7]: https://datatracker.ietf.org/doc/html/rfc2231#section-7
|
||||
/// [RFC 2978 §2.3]: https://datatracker.ietf.org/doc/html/rfc2978#section-2.3
|
||||
/// [RFC 3986 §2.1]: https://datatracker.ietf.org/doc/html/rfc5646#section-2.1
|
||||
pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, crate::error::ParseError> {
|
||||
pub fn parse_extended_value(
|
||||
val: &str,
|
||||
) -> Result<ExtendedValue, crate::error::ParseError> {
|
||||
// Break into three pieces separated by the single-quote character
|
||||
let mut parts = val.splitn(3, '\'');
|
||||
|
||||
@@ -98,7 +100,8 @@ pub fn parse_extended_value(val: &str) -> Result<ExtendedValue, crate::error::Pa
|
||||
|
||||
impl fmt::Display for ExtendedValue {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let encoded_value = percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
||||
let encoded_value =
|
||||
percent_encoding::percent_encode(&self.value[..], HTTP_VALUE);
|
||||
if let Some(ref lang) = self.language_tag {
|
||||
write!(f, "{}'{}'{}", self.charset, lang, encoded_value)
|
||||
} else {
|
||||
@@ -140,8 +143,8 @@ mod tests {
|
||||
assert!(extended_value.language_tag.is_none());
|
||||
assert_eq!(
|
||||
vec![
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
|
||||
b'e', b's',
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||
b't', b'e', b's',
|
||||
],
|
||||
extended_value.value
|
||||
);
|
||||
@@ -182,8 +185,8 @@ mod tests {
|
||||
charset: Charset::Ext("UTF-8".to_string()),
|
||||
language_tag: None,
|
||||
value: vec![
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a', b't',
|
||||
b'e', b's',
|
||||
194, 163, b' ', b'a', b'n', b'd', b' ', 226, 130, 172, b' ', b'r', b'a',
|
||||
b't', b'e', b's',
|
||||
],
|
||||
};
|
||||
assert_eq!(
|
||||
|
@@ -4,7 +4,8 @@ use bytes::BytesMut;
|
||||
use http::header::{HeaderValue, InvalidHeaderValue};
|
||||
|
||||
use crate::{
|
||||
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue, helpers::MutWriter,
|
||||
config::DATE_VALUE_LENGTH, error::ParseError, header::IntoHeaderValue,
|
||||
helpers::MutWriter,
|
||||
};
|
||||
|
||||
/// A timestamp with HTTP-style formatting and parsing.
|
||||
|
@@ -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 = Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
|
||||
let q_value =
|
||||
Quality::try_from(q_value).map_err(|_| ParseError::Header)?;
|
||||
|
||||
quality = q_value;
|
||||
raw_item = val;
|
||||
|
@@ -14,8 +14,7 @@
|
||||
//! [rustls]: https://crates.io/crates/rustls
|
||||
//! [trust-dns]: https://crates.io/crates/trust-dns
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![deny(rust_2018_idioms, nonstandard_style, clippy::uninit_assumed_init)]
|
||||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::too_many_arguments,
|
||||
@@ -54,7 +53,7 @@ pub mod ws;
|
||||
pub use self::builder::HttpServiceBuilder;
|
||||
pub use self::config::{KeepAlive, ServiceConfig};
|
||||
pub use self::error::Error;
|
||||
pub use self::extensions::Extensions;
|
||||
pub use self::extensions::{CloneableExtensions, Extensions};
|
||||
pub use self::header::ContentEncoding;
|
||||
pub use self::http_message::HttpMessage;
|
||||
pub use self::message::ConnectionType;
|
||||
@@ -77,24 +76,35 @@ pub enum Protocol {
|
||||
Http3,
|
||||
}
|
||||
|
||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut Extensions);
|
||||
type ConnectCallback<IO> = dyn Fn(&IO, &mut CloneableExtensions);
|
||||
|
||||
/// Container for data that extract with ConnectCallback.
|
||||
///
|
||||
/// # Implementation Details
|
||||
/// Uses Option to reduce necessary allocations when merging with request extensions.
|
||||
#[derive(Default)]
|
||||
pub(crate) struct OnConnectData(Option<Extensions>);
|
||||
pub(crate) struct OnConnectData(Option<CloneableExtensions>);
|
||||
|
||||
impl OnConnectData {
|
||||
/// Construct by calling the on-connect callback with the underlying transport I/O.
|
||||
pub(crate) fn from_io<T>(io: &T, on_connect_ext: Option<&ConnectCallback<T>>) -> Self {
|
||||
pub(crate) fn from_io<T>(
|
||||
io: &T,
|
||||
on_connect_ext: Option<&ConnectCallback<T>>,
|
||||
) -> Self {
|
||||
let ext = on_connect_ext.map(|handler| {
|
||||
let mut extensions = Extensions::default();
|
||||
let mut extensions = CloneableExtensions::default();
|
||||
handler(io, &mut extensions);
|
||||
extensions
|
||||
});
|
||||
|
||||
Self(ext)
|
||||
}
|
||||
|
||||
/// Merge self into given request's extensions.
|
||||
#[inline]
|
||||
pub(crate) fn merge_into(&mut self, req: &mut Request) {
|
||||
if let Some(ref ext) = self.0 {
|
||||
req.head.extensions.get_mut().clone_from(ext);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -44,12 +44,13 @@ pub trait Head: Default + 'static {
|
||||
F: FnOnce(&MessagePool<Self>) -> R;
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
#[derive(Debug)]
|
||||
pub struct RequestHead {
|
||||
pub method: Method,
|
||||
pub uri: Uri,
|
||||
pub version: Version,
|
||||
pub headers: HeaderMap,
|
||||
pub extensions: RefCell<Extensions>,
|
||||
pub peer_addr: Option<net::SocketAddr>,
|
||||
flags: Flags,
|
||||
}
|
||||
@@ -61,6 +62,7 @@ impl Default for RequestHead {
|
||||
uri: Uri::default(),
|
||||
version: Version::HTTP_11,
|
||||
headers: HeaderMap::with_capacity(16),
|
||||
extensions: RefCell::new(Extensions::new()),
|
||||
peer_addr: None,
|
||||
flags: Flags::empty(),
|
||||
}
|
||||
@@ -71,6 +73,7 @@ impl Head for RequestHead {
|
||||
fn clear(&mut self) {
|
||||
self.flags = Flags::empty();
|
||||
self.headers.clear();
|
||||
self.extensions.get_mut().clear();
|
||||
}
|
||||
|
||||
fn with_pool<F, R>(f: F) -> R
|
||||
@@ -82,6 +85,18 @@ impl Head for RequestHead {
|
||||
}
|
||||
|
||||
impl RequestHead {
|
||||
/// Message extensions
|
||||
#[inline]
|
||||
pub fn extensions(&self) -> Ref<'_, Extensions> {
|
||||
self.extensions.borrow()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the message's extensions
|
||||
#[inline]
|
||||
pub fn extensions_mut(&self) -> RefMut<'_, Extensions> {
|
||||
self.extensions.borrow_mut()
|
||||
}
|
||||
|
||||
/// Read the message headers.
|
||||
pub fn headers(&self) -> &HeaderMap {
|
||||
&self.headers
|
||||
|
@@ -56,7 +56,10 @@ where
|
||||
type Item = Result<Bytes, PayloadError>;
|
||||
|
||||
#[inline]
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
fn poll_next(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut Context<'_>,
|
||||
) -> Poll<Option<Self::Item>> {
|
||||
match self.get_mut() {
|
||||
Payload::None => Poll::Ready(None),
|
||||
Payload::H1(ref mut pl) => pl.readany(cx),
|
||||
|
@@ -1,10 +1,8 @@
|
||||
//! HTTP requests.
|
||||
|
||||
use std::{
|
||||
cell::{Ref, RefCell, RefMut},
|
||||
fmt, mem, net,
|
||||
rc::Rc,
|
||||
str,
|
||||
cell::{Ref, RefMut},
|
||||
fmt, net, str,
|
||||
};
|
||||
|
||||
use http::{header, Method, Uri, Version};
|
||||
@@ -21,8 +19,6 @@ use crate::{
|
||||
pub struct Request<P = PayloadStream> {
|
||||
pub(crate) payload: Payload<P>,
|
||||
pub(crate) head: Message<RequestHead>,
|
||||
pub(crate) conn_data: Option<Rc<Extensions>>,
|
||||
pub(crate) req_data: RefCell<Extensions>,
|
||||
}
|
||||
|
||||
impl<P> HttpMessage for Request<P> {
|
||||
@@ -34,19 +30,19 @@ impl<P> HttpMessage for Request<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
|
||||
#[inline]
|
||||
fn extensions(&self) -> Ref<'_, Extensions> {
|
||||
self.req_data.borrow()
|
||||
self.head.extensions()
|
||||
}
|
||||
|
||||
/// Mutable reference to a the request's extensions
|
||||
#[inline]
|
||||
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 {
|
||||
head,
|
||||
payload: Payload::None,
|
||||
req_data: RefCell::new(Extensions::default()),
|
||||
conn_data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,8 +61,6 @@ impl Request<PayloadStream> {
|
||||
Request {
|
||||
head: Message::new(),
|
||||
payload: Payload::None,
|
||||
req_data: RefCell::new(Extensions::default()),
|
||||
conn_data: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -79,21 +71,16 @@ impl<P> Request<P> {
|
||||
Request {
|
||||
payload,
|
||||
head: Message::new(),
|
||||
req_data: RefCell::new(Extensions::default()),
|
||||
conn_data: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new Request instance
|
||||
pub fn replace_payload<P1>(self, payload: Payload<P1>) -> (Request<P1>, Payload<P>) {
|
||||
let pl = self.payload;
|
||||
|
||||
(
|
||||
Request {
|
||||
payload,
|
||||
head: self.head,
|
||||
req_data: self.req_data,
|
||||
conn_data: self.conn_data,
|
||||
},
|
||||
pl,
|
||||
)
|
||||
@@ -106,7 +93,7 @@ impl<P> Request<P> {
|
||||
|
||||
/// Get request's payload
|
||||
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
|
||||
@@ -129,7 +116,7 @@ impl<P> Request<P> {
|
||||
|
||||
/// Mutable reference to the message's headers.
|
||||
pub fn headers_mut(&mut self) -> &mut HeaderMap {
|
||||
&mut self.head.headers
|
||||
&mut self.head_mut().headers
|
||||
}
|
||||
|
||||
/// Request's uri.
|
||||
@@ -141,7 +128,7 @@ impl<P> Request<P> {
|
||||
/// Mutable reference to the request's uri.
|
||||
#[inline]
|
||||
pub fn uri_mut(&mut self) -> &mut Uri {
|
||||
&mut self.head.uri
|
||||
&mut self.head_mut().uri
|
||||
}
|
||||
|
||||
/// Read the Request method.
|
||||
@@ -183,31 +170,6 @@ impl<P> Request<P> {
|
||||
pub fn peer_addr(&self) -> Option<net::SocketAddr> {
|
||||
self.head().peer_addr
|
||||
}
|
||||
|
||||
/// Returns a reference a piece of connection data set in an [on-connect] callback.
|
||||
///
|
||||
/// ```ignore
|
||||
/// let opt_t = req.conn_data::<PeerCertificate>();
|
||||
/// ```
|
||||
///
|
||||
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
||||
pub fn conn_data<T: 'static>(&self) -> Option<&T> {
|
||||
self.conn_data
|
||||
.as_deref()
|
||||
.and_then(|container| container.get::<T>())
|
||||
}
|
||||
|
||||
/// Returns the connection data container if an [on-connect] callback was registered.
|
||||
///
|
||||
/// [on-connect]: crate::HttpServiceBuilder::on_connect_ext
|
||||
pub fn take_conn_data(&mut self) -> Option<Rc<Extensions>> {
|
||||
self.conn_data.take()
|
||||
}
|
||||
|
||||
/// Returns the request data container, leaving an empty one in it's place.
|
||||
pub fn take_req_data(&mut self) -> Extensions {
|
||||
mem::take(&mut self.req_data.get_mut())
|
||||
}
|
||||
}
|
||||
|
||||
impl<P> fmt::Debug for Request<P> {
|
||||
|
@@ -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 {
|
||||
match res {
|
||||
Ok(val) => val.into(),
|
||||
|
@@ -47,8 +47,7 @@ impl ResponseBuilder {
|
||||
/// Create response builder
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{Response, ResponseBuilder, StatusCode};
|
||||
// /// use actix_http::{Response, ResponseBuilder, StatusCode};, / ``
|
||||
/// let res: Response<_> = ResponseBuilder::default().finish();
|
||||
/// assert_eq!(res.status(), StatusCode::OK);
|
||||
/// ```
|
||||
@@ -63,8 +62,7 @@ impl ResponseBuilder {
|
||||
/// Set HTTP status code of this response.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use actix_http::{ResponseBuilder, StatusCode};
|
||||
// /// use actix_http::{ResponseBuilder, StatusCode};, / ``
|
||||
/// let res = ResponseBuilder::default().status(StatusCode::NOT_FOUND).finish();
|
||||
/// assert_eq!(res.status(), StatusCode::NOT_FOUND);
|
||||
/// ```
|
||||
|
@@ -161,7 +161,11 @@ where
|
||||
X::Error: Into<Response<BoxBody>>,
|
||||
X::InitError: fmt::Debug,
|
||||
|
||||
U: ServiceFactory<(Request, Framed<TcpStream, h1::Codec>), Config = (), Response = ()>,
|
||||
U: ServiceFactory<
|
||||
(Request, Framed<TcpStream, h1::Codec>),
|
||||
Config = (),
|
||||
Response = (),
|
||||
>,
|
||||
U::Future: 'static,
|
||||
U::Error: fmt::Display + Into<Response<BoxBody>>,
|
||||
U::InitError: fmt::Debug,
|
||||
@@ -377,9 +381,9 @@ where
|
||||
|
||||
let upgrade = match upgrade {
|
||||
Some(upgrade) => {
|
||||
let upgrade = upgrade
|
||||
.await
|
||||
.map_err(|e| log::error!("Init http upgrade service error: {:?}", e))?;
|
||||
let upgrade = upgrade.await.map_err(|e| {
|
||||
log::error!("Init http upgrade service error: {:?}", e)
|
||||
})?;
|
||||
Some(upgrade)
|
||||
}
|
||||
None => None,
|
||||
@@ -503,7 +507,8 @@ where
|
||||
&self,
|
||||
(io, proto, peer_addr): (T, Protocol, Option<net::SocketAddr>),
|
||||
) -> 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 {
|
||||
Protocol::Http2 => HttpServiceHandlerResponse {
|
||||
@@ -512,7 +517,7 @@ where
|
||||
h2::handshake_with_timeout(io, &self.cfg),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
conn_data,
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
)),
|
||||
},
|
||||
@@ -522,10 +527,10 @@ where
|
||||
state: State::H1 {
|
||||
dispatcher: h1::Dispatcher::new(
|
||||
io,
|
||||
self.flow.clone(),
|
||||
self.cfg.clone(),
|
||||
self.flow.clone(),
|
||||
on_connect_data,
|
||||
peer_addr,
|
||||
conn_data,
|
||||
),
|
||||
},
|
||||
},
|
||||
@@ -622,11 +627,17 @@ where
|
||||
StateProj::H2Handshake { handshake: data } => {
|
||||
match ready!(Pin::new(&mut data.as_mut().unwrap().0).poll(cx)) {
|
||||
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 {
|
||||
dispatcher: h2::Dispatcher::new(
|
||||
conn, flow, config, peer_addr, conn_data, timer,
|
||||
flow,
|
||||
conn,
|
||||
on_connect_data,
|
||||
config,
|
||||
peer_addr,
|
||||
timer,
|
||||
),
|
||||
});
|
||||
self.poll(cx)
|
||||
|
@@ -224,7 +224,9 @@ impl Decoder for Codec {
|
||||
OpCode::Continue => {
|
||||
if self.flags.contains(Flags::CONTINUATION) {
|
||||
Ok(Some(Frame::Continuation(Item::Continue(
|
||||
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
|
||||
payload
|
||||
.map(|pl| pl.freeze())
|
||||
.unwrap_or_else(Bytes::new),
|
||||
))))
|
||||
} else {
|
||||
Err(ProtocolError::ContinuationNotStarted)
|
||||
@@ -234,7 +236,9 @@ impl Decoder for Codec {
|
||||
if !self.flags.contains(Flags::CONTINUATION) {
|
||||
self.flags.insert(Flags::CONTINUATION);
|
||||
Ok(Some(Frame::Continuation(Item::FirstBinary(
|
||||
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
|
||||
payload
|
||||
.map(|pl| pl.freeze())
|
||||
.unwrap_or_else(Bytes::new),
|
||||
))))
|
||||
} else {
|
||||
Err(ProtocolError::ContinuationStarted)
|
||||
@@ -244,7 +248,9 @@ impl Decoder for Codec {
|
||||
if !self.flags.contains(Flags::CONTINUATION) {
|
||||
self.flags.insert(Flags::CONTINUATION);
|
||||
Ok(Some(Frame::Continuation(Item::FirstText(
|
||||
payload.map(|pl| pl.freeze()).unwrap_or_else(Bytes::new),
|
||||
payload
|
||||
.map(|pl| pl.freeze())
|
||||
.unwrap_or_else(Bytes::new),
|
||||
))))
|
||||
} else {
|
||||
Err(ProtocolError::ContinuationStarted)
|
||||
|
@@ -304,7 +304,8 @@ mod inner {
|
||||
let item = match this.framed.next_item(cx) {
|
||||
Poll::Ready(Some(Ok(el))) => el,
|
||||
Poll::Ready(Some(Err(err))) => {
|
||||
*this.state = State::FramedError(DispatcherError::Decoder(err));
|
||||
*this.state =
|
||||
State::FramedError(DispatcherError::Decoder(err));
|
||||
return true;
|
||||
}
|
||||
Poll::Pending => return false,
|
||||
@@ -347,7 +348,8 @@ mod inner {
|
||||
match Pin::new(&mut this.rx).poll_next(cx) {
|
||||
Poll::Ready(Some(Ok(Message::Item(msg)))) => {
|
||||
if let Err(err) = this.framed.as_mut().write(msg) {
|
||||
*this.state = State::FramedError(DispatcherError::Encoder(err));
|
||||
*this.state =
|
||||
State::FramedError(DispatcherError::Encoder(err));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -369,7 +371,8 @@ mod inner {
|
||||
Poll::Ready(Ok(_)) => {}
|
||||
Poll::Ready(Err(err)) => {
|
||||
debug!("Error sending data: {:?}", err);
|
||||
*this.state = State::FramedError(DispatcherError::Encoder(err));
|
||||
*this.state =
|
||||
State::FramedError(DispatcherError::Encoder(err));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -429,7 +432,9 @@ mod inner {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
State::FramedError(_) => Poll::Ready(Err(this.state.take_framed_error())),
|
||||
State::FramedError(_) => {
|
||||
Poll::Ready(Err(this.state.take_framed_error()))
|
||||
}
|
||||
State::Stopping => Poll::Ready(Ok(())),
|
||||
};
|
||||
}
|
||||
|
@@ -16,7 +16,8 @@ impl Parser {
|
||||
src: &[u8],
|
||||
server: bool,
|
||||
max_size: usize,
|
||||
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError> {
|
||||
) -> Result<Option<(usize, bool, OpCode, usize, Option<[u8; 4]>)>, ProtocolError>
|
||||
{
|
||||
let chunk_len = src.len();
|
||||
|
||||
let mut idx = 2;
|
||||
@@ -227,11 +228,15 @@ mod tests {
|
||||
payload: Bytes,
|
||||
}
|
||||
|
||||
fn is_none(frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> bool {
|
||||
fn is_none(
|
||||
frm: &Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
|
||||
) -> bool {
|
||||
matches!(*frm, Ok(None))
|
||||
}
|
||||
|
||||
fn extract(frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>) -> F {
|
||||
fn extract(
|
||||
frm: Result<Option<(bool, OpCode, Option<BytesMut>)>, ProtocolError>,
|
||||
) -> F {
|
||||
match frm {
|
||||
Ok(Some((finished, opcode, payload))) => F {
|
||||
finished,
|
||||
|
@@ -54,8 +54,8 @@ mod tests {
|
||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||
|
||||
let unmasked = vec![
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
||||
0x12, 0x03,
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
||||
0x74, 0xf9, 0x12, 0x03,
|
||||
];
|
||||
|
||||
// Check masking with proper alignment.
|
||||
@@ -85,8 +85,8 @@ mod tests {
|
||||
fn test_apply_mask() {
|
||||
let mask = [0x6d, 0xb6, 0xb2, 0x80];
|
||||
let unmasked = vec![
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17, 0x74, 0xf9,
|
||||
0x12, 0x03,
|
||||
0xf3, 0x00, 0x01, 0x02, 0x03, 0x80, 0x81, 0x82, 0xff, 0xfe, 0x00, 0x17,
|
||||
0x74, 0xf9, 0x12, 0x03,
|
||||
];
|
||||
|
||||
for data_len in 0..=unmasked.len() {
|
||||
|
@@ -9,7 +9,9 @@ use derive_more::{Display, Error, From};
|
||||
use http::{header, Method, StatusCode};
|
||||
|
||||
use crate::body::BoxBody;
|
||||
use crate::{header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder};
|
||||
use crate::{
|
||||
header::HeaderValue, message::RequestHead, response::Response, ResponseBuilder,
|
||||
};
|
||||
|
||||
mod codec;
|
||||
mod dispatcher;
|
||||
|
@@ -1,6 +1,8 @@
|
||||
use std::convert::Infallible;
|
||||
|
||||
use actix_http::{body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode};
|
||||
use actix_http::{
|
||||
body::BoxBody, HttpMessage, HttpService, Request, Response, StatusCode,
|
||||
};
|
||||
use actix_http_test::test_server;
|
||||
use actix_service::ServiceFactoryExt;
|
||||
use actix_utils::future;
|
||||
|
@@ -8,7 +8,7 @@ use actix_http::{
|
||||
body::{BodyStream, BoxBody, SizedStream},
|
||||
error::PayloadError,
|
||||
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_service::{fn_service, ServiceFactoryExt};
|
||||
@@ -101,7 +101,7 @@ async fn test_h2_1() -> io::Result<()> {
|
||||
|
||||
#[actix_rt::test]
|
||||
async fn test_h2_body() -> io::Result<()> {
|
||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024); // 640 KiB
|
||||
let data = "HELLOWORLD".to_owned().repeat(64 * 1024);
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|mut req: Request<_>| async move {
|
||||
@@ -170,11 +170,10 @@ async fn test_h2_headers() {
|
||||
|
||||
let mut srv = test_server(move || {
|
||||
let data = data.clone();
|
||||
HttpService::build()
|
||||
.h2(move |_| {
|
||||
let mut builder = Response::build(StatusCode::OK);
|
||||
for idx in 0..90 {
|
||||
builder.insert_header(
|
||||
HttpService::build().h2(move |_| {
|
||||
let mut builder = Response::build(StatusCode::OK);
|
||||
for idx in 0..90 {
|
||||
builder.insert_header(
|
||||
(format!("X-TEST-{}", idx).as_str(),
|
||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
@@ -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 ",
|
||||
));
|
||||
}
|
||||
ok::<_, Infallible>(builder.body(data.clone()))
|
||||
})
|
||||
}
|
||||
ok::<_, Infallible>(builder.body(data.clone()))
|
||||
})
|
||||
.openssl(tls_config())
|
||||
.map_err(|_| ())
|
||||
})
|
||||
.await;
|
||||
.map_err(|_| ())
|
||||
}).await;
|
||||
|
||||
let response = srv.sget("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
@@ -317,8 +315,9 @@ async fn test_h2_body_length() {
|
||||
let mut srv = test_server(move || {
|
||||
HttpService::build()
|
||||
.h2(|_| async {
|
||||
let body =
|
||||
once(async { Ok::<_, Infallible>(Bytes::from_static(STR.as_ref())) });
|
||||
let body = once(async {
|
||||
Ok::<_, Infallible>(Bytes::from_static(STR.as_ref()))
|
||||
});
|
||||
|
||||
Ok::<_, Infallible>(
|
||||
Response::ok().set_body(SizedStream::new(STR.len() as u64, body)),
|
||||
@@ -431,7 +430,7 @@ async fn test_h2_on_connect() {
|
||||
data.insert(20isize);
|
||||
})
|
||||
.h2(|req: Request| {
|
||||
assert!(req.conn_data::<isize>().is_some());
|
||||
assert!(req.extensions().contains::<isize>());
|
||||
ok::<_, Infallible>(Response::ok())
|
||||
})
|
||||
.openssl(tls_config())
|
||||
|
@@ -238,11 +238,10 @@ async fn test_h2_headers() {
|
||||
|
||||
let mut srv = test_server(move || {
|
||||
let data = data.clone();
|
||||
HttpService::build()
|
||||
.h2(move |_| {
|
||||
let mut config = Response::build(StatusCode::OK);
|
||||
for idx in 0..90 {
|
||||
config.insert_header((
|
||||
HttpService::build().h2(move |_| {
|
||||
let mut config = Response::build(StatusCode::OK);
|
||||
for idx in 0..90 {
|
||||
config.insert_header((
|
||||
format!("X-TEST-{}", idx).as_str(),
|
||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
@@ -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 ",
|
||||
));
|
||||
}
|
||||
ok::<_, Infallible>(config.body(data.clone()))
|
||||
})
|
||||
}
|
||||
ok::<_, Infallible>(config.body(data.clone()))
|
||||
})
|
||||
.rustls(tls_config())
|
||||
})
|
||||
.await;
|
||||
}).await;
|
||||
|
||||
let response = srv.sget("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
|
@@ -7,7 +7,7 @@ use std::{
|
||||
|
||||
use actix_http::{
|
||||
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_rt::time::sleep;
|
||||
@@ -154,7 +154,9 @@ async fn test_chunked_payload() {
|
||||
})
|
||||
.fold(0usize, |acc, chunk| ready(acc + chunk.len()))
|
||||
.map(|req_size| {
|
||||
Ok::<_, Error>(Response::ok().set_body(format!("size={}", req_size)))
|
||||
Ok::<_, Error>(
|
||||
Response::ok().set_body(format!("size={}", req_size)),
|
||||
)
|
||||
})
|
||||
}))
|
||||
.tcp()
|
||||
@@ -163,7 +165,8 @@ async fn test_chunked_payload() {
|
||||
|
||||
let returned_size = {
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
let _ = stream
|
||||
.write_all(b"POST /test HTTP/1.1\r\nTransfer-Encoding: chunked\r\n\r\n");
|
||||
|
||||
for chunk_size in chunk_sizes.iter() {
|
||||
let mut bytes = Vec::new();
|
||||
@@ -290,7 +293,8 @@ async fn test_http1_keepalive_close() {
|
||||
.await;
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ = stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n");
|
||||
let _ =
|
||||
stream.write_all(b"GET /test/tests/test HTTP/1.1\r\nconnection: close\r\n\r\n");
|
||||
let mut data = vec![0; 1024];
|
||||
let _ = stream.read(&mut data);
|
||||
assert_eq!(&data[..17], b"HTTP/1.1 200 OK\r\n");
|
||||
@@ -334,8 +338,8 @@ async fn test_http10_keepalive() {
|
||||
.await;
|
||||
|
||||
let mut stream = net::TcpStream::connect(srv.addr()).unwrap();
|
||||
let _ =
|
||||
stream.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
||||
let _ = stream
|
||||
.write_all(b"GET /test/tests/test HTTP/1.0\r\nconnection: keep-alive\r\n\r\n");
|
||||
let mut data = vec![0; 1024];
|
||||
let _ = stream.read(&mut data);
|
||||
assert_eq!(&data[..17], b"HTTP/1.0 200 OK\r\n");
|
||||
@@ -432,11 +436,10 @@ async fn test_h1_headers() {
|
||||
|
||||
let mut srv = test_server(move || {
|
||||
let data = data.clone();
|
||||
HttpService::build()
|
||||
.h1(move |_| {
|
||||
let mut builder = Response::build(StatusCode::OK);
|
||||
for idx in 0..90 {
|
||||
builder.insert_header((
|
||||
HttpService::build().h1(move |_| {
|
||||
let mut builder = Response::build(StatusCode::OK);
|
||||
for idx in 0..90 {
|
||||
builder.insert_header((
|
||||
format!("X-TEST-{}", idx).as_str(),
|
||||
"TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST TEST \
|
||||
@@ -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 ",
|
||||
));
|
||||
}
|
||||
ok::<_, Infallible>(builder.body(data.clone()))
|
||||
})
|
||||
.tcp()
|
||||
})
|
||||
.await;
|
||||
}
|
||||
ok::<_, Infallible>(builder.body(data.clone()))
|
||||
}).tcp()
|
||||
}).await;
|
||||
|
||||
let response = srv.get("/").send().await.unwrap();
|
||||
assert!(response.status().is_success());
|
||||
@@ -654,7 +655,9 @@ async fn test_h1_body_chunked_implicit() {
|
||||
HttpService::build()
|
||||
.h1(|_| {
|
||||
let body = once(ok::<_, Error>(Bytes::from_static(STR.as_ref())));
|
||||
ok::<_, Infallible>(Response::build(StatusCode::OK).body(BodyStream::new(body)))
|
||||
ok::<_, Infallible>(
|
||||
Response::build(StatusCode::OK).body(BodyStream::new(body)),
|
||||
)
|
||||
})
|
||||
.tcp()
|
||||
})
|
||||
@@ -745,7 +748,7 @@ async fn test_h1_on_connect() {
|
||||
data.insert(20isize);
|
||||
})
|
||||
.h1(|req: Request| {
|
||||
assert!(req.conn_data::<isize>().is_some());
|
||||
assert!(req.extensions().contains::<isize>());
|
||||
ok::<_, Infallible>(Response::ok())
|
||||
})
|
||||
.tcp()
|
||||
@@ -773,8 +776,10 @@ async fn test_not_modified_spec_h1() {
|
||||
.h1(|req: Request| {
|
||||
let res: Response<BoxBody> = match req.path() {
|
||||
// with no content-length
|
||||
"/none" => Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
|
||||
.map_into_boxed_body(),
|
||||
"/none" => {
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new())
|
||||
.map_into_boxed_body()
|
||||
}
|
||||
|
||||
// with no content-length
|
||||
"/body" => Response::with_body(StatusCode::NOT_MODIFIED, "1234")
|
||||
@@ -782,8 +787,10 @@ async fn test_not_modified_spec_h1() {
|
||||
|
||||
// with manual content-length header and specific None body
|
||||
"/cl-none" => {
|
||||
let mut res =
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, body::None::new());
|
||||
let mut res = Response::with_body(
|
||||
StatusCode::NOT_MODIFIED,
|
||||
body::None::new(),
|
||||
);
|
||||
res.headers_mut()
|
||||
.insert(CL.clone(), header::HeaderValue::from_static("24"));
|
||||
res.map_into_boxed_body()
|
||||
@@ -791,7 +798,8 @@ async fn test_not_modified_spec_h1() {
|
||||
|
||||
// with manual content-length header and ignore-able body
|
||||
"/cl-body" => {
|
||||
let mut res = Response::with_body(StatusCode::NOT_MODIFIED, "1234");
|
||||
let mut res =
|
||||
Response::with_body(StatusCode::NOT_MODIFIED, "1234");
|
||||
res.headers_mut()
|
||||
.insert(CL.clone(), header::HeaderValue::from_static("4"));
|
||||
res.map_into_boxed_body()
|
||||
|
@@ -56,9 +56,8 @@ impl From<WsServiceError> for Response<BoxBody> {
|
||||
WsServiceError::Http(err) => err.into(),
|
||||
WsServiceError::Ws(err) => err.into(),
|
||||
WsServiceError::Io(_err) => unreachable!(),
|
||||
WsServiceError::Dispatcher => {
|
||||
Response::internal_server_error().set_body(BoxBody::new(format!("{}", err)))
|
||||
}
|
||||
WsServiceError::Dispatcher => Response::internal_server_error()
|
||||
.set_body(BoxBody::new(format!("{}", err))),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -98,7 +97,9 @@ where
|
||||
async fn service(msg: Frame) -> Result<Message, Error> {
|
||||
let msg = match msg {
|
||||
Frame::Ping(msg) => Message::Pong(msg),
|
||||
Frame::Text(text) => Message::Text(String::from_utf8_lossy(&text).into_owned().into()),
|
||||
Frame::Text(text) => {
|
||||
Message::Text(String::from_utf8_lossy(&text).into_owned().into())
|
||||
}
|
||||
Frame::Binary(bin) => Message::Binary(bin),
|
||||
Frame::Continuation(item) => Message::Continuation(item),
|
||||
Frame::Close(reason) => Message::Close(reason),
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.4.0-beta.10 - 2021-12-11
|
||||
* No significant changes since `0.4.0-beta.9`.
|
||||
|
||||
|
||||
## 0.4.0-beta.9 - 2021-12-01
|
||||
* Polling `Field` after dropping `Multipart` now fails immediately instead of hanging forever. [#2463]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-multipart"
|
||||
version = "0.4.0-beta.10"
|
||||
version = "0.4.0-beta.9"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Multipart form support for Actix Web"
|
||||
keywords = ["http", "web", "framework", "async", "futures"]
|
||||
@@ -14,8 +14,8 @@ name = "actix_multipart"
|
||||
path = "src/lib.rs"
|
||||
|
||||
[dependencies]
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.14", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
derive_more = "0.99.5"
|
||||
@@ -28,7 +28,7 @@ twoway = "0.2"
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-http = "3.0.0-beta.15"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
futures-util = { version = "0.3.7", default-features = false, features = ["alloc"] }
|
||||
tokio = { version = "1", features = ["sync"] }
|
||||
tokio-stream = "0.1"
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Multipart form support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.10)
|
||||
[](https://docs.rs/actix-multipart/0.4.0-beta.9)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.10)
|
||||
[](https://deps.rs/crate/actix-multipart/0.4.0-beta.9)
|
||||
[](https://crates.io/crates/actix-multipart)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! Multipart form support for Actix Web.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![allow(clippy::borrow_interior_mutable_const)]
|
||||
|
||||
mod error;
|
||||
|
@@ -1,7 +1,6 @@
|
||||
//! Resource path matching and router.
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
||||
|
@@ -168,7 +168,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
||||
/// extracted in the same way as non-tail dynamic segments.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use actix_router::{Path, ResourceDef};
|
||||
/// let resource = ResourceDef::new("/blob/{tail}*");
|
||||
/// assert!(resource.is_match("/blob/HEAD/Cargo.toml"));
|
||||
@@ -191,7 +191,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
||||
/// expectations in the router using these definitions and cause runtime panics.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use actix_router::ResourceDef;
|
||||
/// let resource = ResourceDef::new(["/home", "/index"]);
|
||||
/// assert!(resource.is_match("/home"));
|
||||
@@ -206,7 +206,7 @@ const REGEX_FLAGS: &str = "(?s-m)";
|
||||
/// resource-path pairs that would not be compatible.
|
||||
///
|
||||
/// ## Examples
|
||||
/// ```
|
||||
/// ```rust
|
||||
/// # use actix_router::ResourceDef;
|
||||
/// assert!(!ResourceDef::new("/root").is_match("/root/"));
|
||||
/// assert!(!ResourceDef::new("/root/").is_match("/root"));
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 0.1.0-beta.8 - 2021-12-11
|
||||
* No significant changes since `0.1.0-beta.7`.
|
||||
|
||||
|
||||
## 0.1.0-beta.7 - 2021-11-22
|
||||
* Fix compatibility with experimental `io-uring` feature of `actix-rt`. [#2408]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-test"
|
||||
version = "0.1.0-beta.8"
|
||||
version = "0.1.0-beta.7"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"Rob Ede <robjtede@icloud.com>",
|
||||
@@ -29,13 +29,13 @@ openssl = ["tls-openssl", "actix-http/openssl", "awc/openssl"]
|
||||
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-http = "3.0.0-beta.15"
|
||||
actix-http-test = "3.0.0-beta.9"
|
||||
actix-rt = "2.1"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-http-test = "3.0.0-beta.7"
|
||||
actix-service = "2.0.0"
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.14", default-features = false, features = ["cookies"] }
|
||||
awc = { version = "3.0.0-beta.13", default-features = false, features = ["cookies"] }
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
actix-rt = "2.1"
|
||||
awc = { version = "3.0.0-beta.11", default-features = false, features = ["cookies"] }
|
||||
|
||||
futures-core = { version = "0.3.7", default-features = false, features = ["std"] }
|
||||
futures-util = { version = "0.3.7", default-features = false, features = [] }
|
||||
|
@@ -26,9 +26,6 @@
|
||||
//! }
|
||||
//! ```
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
|
||||
#[cfg(feature = "openssl")]
|
||||
extern crate tls_openssl as openssl;
|
||||
#[cfg(feature = "rustls")]
|
||||
|
@@ -1,9 +1,6 @@
|
||||
# Changes
|
||||
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 4.0.0-beta.8 - 2021-12-11
|
||||
* Add `ws:WsResponseBuilder` for building WebSocket session response. [#1920]
|
||||
* Deprecate `ws::{start_with_addr, start_with_protocols}`. [#1920]
|
||||
* Minimum supported Rust version (MSRV) is now 1.52.
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "actix-web-actors"
|
||||
version = "4.0.0-beta.8"
|
||||
version = "4.0.0-beta.7"
|
||||
authors = ["Nikolay Kim <fafhrd91@gmail.com>"]
|
||||
description = "Actix actors support for Actix Web"
|
||||
keywords = ["actix", "http", "web", "framework", "async"]
|
||||
@@ -16,8 +16,8 @@ path = "src/lib.rs"
|
||||
[dependencies]
|
||||
actix = { version = "0.12.0", default-features = false }
|
||||
actix-codec = "0.4.1"
|
||||
actix-http = "3.0.0-beta.15"
|
||||
actix-web = { version = "4.0.0-beta.14", default-features = false }
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-web = { version = "4.0.0-beta.11", default-features = false }
|
||||
|
||||
bytes = "1"
|
||||
bytestring = "1"
|
||||
@@ -27,8 +27,8 @@ tokio = { version = "1", features = ["sync"] }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-rt = "2.2"
|
||||
actix-test = "0.1.0-beta.8"
|
||||
awc = { version = "3.0.0-beta.13", default-features = false }
|
||||
actix-test = "0.1.0-beta.7"
|
||||
|
||||
awc = { version = "3.0.0-beta.11", default-features = false }
|
||||
env_logger = "0.9"
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
|
@@ -3,11 +3,11 @@
|
||||
> Actix actors support for Actix Web.
|
||||
|
||||
[](https://crates.io/crates/actix-web-actors)
|
||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.8)
|
||||
[](https://docs.rs/actix-web-actors/4.0.0-beta.7)
|
||||
[](https://blog.rust-lang.org/2021/05/06/Rust-1.52.0.html)
|
||||

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

|
||||
<br />
|
||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.6)
|
||||
[](https://deps.rs/crate/actix-web-codegen/0.5.0-beta.5)
|
||||
[](https://crates.io/crates/actix-web-codegen)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
|
@@ -57,8 +57,6 @@
|
||||
//! [DELETE]: macro@delete
|
||||
|
||||
#![recursion_limit = "512"]
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
|
||||
use proc_macro::TokenStream;
|
||||
use quote::quote;
|
||||
|
@@ -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 proc_macro::TokenStream;
|
||||
|
@@ -3,10 +3,6 @@
|
||||
## Unreleased - 2021-xx-xx
|
||||
|
||||
|
||||
## 3.0.0-beta.13 - 2021-12-11
|
||||
* No significant changes since `3.0.0-beta.12`.
|
||||
|
||||
|
||||
## 3.0.0-beta.12 - 2021-11-30
|
||||
* Update `actix-tls` to `3.0.0-rc.1`. [#2474]
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "awc"
|
||||
version = "3.0.0-beta.13"
|
||||
version = "3.0.0-beta.12"
|
||||
authors = [
|
||||
"Nikolay Kim <fafhrd91@gmail.com>",
|
||||
"fakeshadow <24548779@qq.com>",
|
||||
@@ -60,7 +60,7 @@ dangerous-h2c = []
|
||||
[dependencies]
|
||||
actix-codec = "0.4.1"
|
||||
actix-service = "2.0.0"
|
||||
actix-http = "3.0.0-beta.15"
|
||||
actix-http = "3.0.0-beta.14"
|
||||
actix-rt = { version = "2.1", default-features = false }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["connect", "uri"] }
|
||||
actix-utils = "3.0.0"
|
||||
@@ -72,7 +72,7 @@ cfg-if = "1"
|
||||
derive_more = "0.99.5"
|
||||
futures-core = { version = "0.3.7", default-features = false }
|
||||
futures-util = { version = "0.3.7", default-features = false }
|
||||
h2 = "0.3.9"
|
||||
h2 = "0.3"
|
||||
http = "0.2.5"
|
||||
itoa = "0.4"
|
||||
log =" 0.4"
|
||||
@@ -93,13 +93,13 @@ tls-rustls = { package = "rustls", version = "0.20.0", optional = true, features
|
||||
trust-dns-resolver = { version = "0.20.0", optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
actix-http = { version = "3.0.0-beta.15", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.9", features = ["openssl"] }
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-test = { version = "0.1.0-beta.8", features = ["openssl", "rustls"] }
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
|
||||
actix-web = { version = "4.0.0-beta.11", features = ["openssl"] }
|
||||
actix-http = { version = "3.0.0-beta.14", features = ["openssl"] }
|
||||
actix-http-test = { version = "3.0.0-beta.7", features = ["openssl"] }
|
||||
actix-utils = "3.0.0"
|
||||
actix-web = { version = "4.0.0-beta.14", features = ["openssl"] }
|
||||
actix-server = "2.0.0-rc.1"
|
||||
actix-tls = { version = "3.0.0-rc.1", features = ["openssl", "rustls"] }
|
||||
actix-test = { version = "0.1.0-beta.7", features = ["openssl", "rustls"] }
|
||||
|
||||
brotli2 = "0.3.2"
|
||||
env_logger = "0.9"
|
||||
|
@@ -3,9 +3,9 @@
|
||||
> Async HTTP and WebSocket client library.
|
||||
|
||||
[](https://crates.io/crates/awc)
|
||||
[](https://docs.rs/awc/3.0.0-beta.13)
|
||||
[](https://docs.rs/awc/3.0.0-beta.12)
|
||||

|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.13)
|
||||
[](https://deps.rs/crate/awc/3.0.0-beta.12)
|
||||
[](https://discord.gg/NWpN5mmg3x)
|
||||
|
||||
## Documentation & Resources
|
||||
|
@@ -95,8 +95,7 @@
|
||||
//! # }
|
||||
//! ```
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
#![allow(
|
||||
clippy::type_complexity,
|
||||
clippy::borrow_interior_mutable_const,
|
||||
|
@@ -6,10 +6,8 @@
|
||||
|
||||
use std::{any::Any, io, net::SocketAddr};
|
||||
|
||||
use actix_web::{
|
||||
dev::Extensions, rt::net::TcpStream, web, App, HttpRequest, HttpResponse, HttpServer,
|
||||
Responder,
|
||||
};
|
||||
use actix_http::CloneableExtensions;
|
||||
use actix_web::{rt::net::TcpStream, web, App, HttpServer};
|
||||
|
||||
#[allow(dead_code)]
|
||||
#[derive(Debug, Clone)]
|
||||
@@ -19,19 +17,14 @@ struct ConnectionInfo {
|
||||
ttl: Option<u32>,
|
||||
}
|
||||
|
||||
async fn route_whoami(req: HttpRequest) -> impl Responder {
|
||||
match req.conn_data::<ConnectionInfo>() {
|
||||
Some(info) => HttpResponse::Ok().body(format!(
|
||||
"Here is some info about your connection:\n\n{:#?}",
|
||||
info
|
||||
)),
|
||||
None => {
|
||||
HttpResponse::InternalServerError().body("Missing expected request extension data")
|
||||
}
|
||||
}
|
||||
async fn route_whoami(conn_info: web::ReqData<ConnectionInfo>) -> String {
|
||||
format!(
|
||||
"Here is some info about your connection:\n\n{:#?}",
|
||||
conn_info
|
||||
)
|
||||
}
|
||||
|
||||
fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
|
||||
fn get_conn_info(connection: &dyn Any, data: &mut CloneableExtensions) {
|
||||
if let Some(sock) = connection.downcast_ref::<TcpStream>() {
|
||||
data.insert(ConnectionInfo {
|
||||
bind: sock.local_addr().unwrap(),
|
||||
@@ -47,12 +40,9 @@ fn get_conn_info(connection: &dyn Any, data: &mut Extensions) {
|
||||
async fn main() -> io::Result<()> {
|
||||
env_logger::init_from_env(env_logger::Env::new().default_filter_or("info"));
|
||||
|
||||
let bind = ("127.0.0.1", 8080);
|
||||
log::info!("staring server at http://{}:{}", &bind.0, &bind.1);
|
||||
|
||||
HttpServer::new(|| App::new().default_service(web::to(route_whoami)))
|
||||
.on_connect(get_conn_info)
|
||||
.bind(bind)?
|
||||
.bind(("127.0.0.1", 8080))?
|
||||
.workers(1)
|
||||
.run()
|
||||
.await
|
||||
|
31
scripts/bump
31
scripts/bump
@@ -41,8 +41,6 @@ cat "$CHANGELOG_FILE" |
|
||||
# if word count of changelog chunk is 0 then insert filler changelog chunk
|
||||
if [ "$(wc -w "$CHANGE_CHUNK_FILE" | awk '{ print $1 }')" = "0" ]; then
|
||||
echo "* No significant changes since \`$CURRENT_VERSION\`." >"$CHANGE_CHUNK_FILE"
|
||||
echo >>"$CHANGE_CHUNK_FILE"
|
||||
echo >>"$CHANGE_CHUNK_FILE"
|
||||
fi
|
||||
|
||||
if [ -n "${2-}" ]; then
|
||||
@@ -84,33 +82,8 @@ rm -f $README_FILE.bak
|
||||
echo "manifest, changelog, and readme updated"
|
||||
echo
|
||||
echo "check other references:"
|
||||
rg --glob='**/Cargo.toml' "\
|
||||
${PACKAGE_NAME} ?= ?\"[^\"]+\"\
|
||||
|${PACKAGE_NAME} ?=.*version ?= ?\"([^\"]+)\"\
|
||||
|package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\"([^\"]+)\"\
|
||||
|version ?= ?\"([^\"]+)\".*package ?= ?\"${PACKAGE_NAME}\"" || true
|
||||
|
||||
echo
|
||||
read -p "Update all references: (y/N) " UPDATE_REFERENCES
|
||||
UPDATE_REFERENCES="${UPDATE_REFERENCES:-n}"
|
||||
|
||||
if [ "$UPDATE_REFERENCES" = 'y' ] || [ "$UPDATE_REFERENCES" = 'Y' ]; then
|
||||
|
||||
for f in $(fd Cargo.toml); do
|
||||
sed -i.bak -E \
|
||||
"s/^(${PACKAGE_NAME} ?= ?\")[^\"]+(\")$/\1${NEW_VERSION}\2/g" $f
|
||||
sed -i.bak -E \
|
||||
"s/^(${PACKAGE_NAME} ?=.*version ?= ?\")[^\"]+(\".*)$/\1${NEW_VERSION}\2/g" $f
|
||||
sed -i.bak -E \
|
||||
"s/^(.*package ?= ?\"${PACKAGE_NAME}\".*version ?= ?\")[^\"]+(\".*)$/\1${NEW_VERSION}\2/g" $f
|
||||
sed -i.bak -E \
|
||||
"s/^(.*version ?= ?\")[^\"]+(\".*package ?= ?\"${PACKAGE_NAME}\".*)$/\1${NEW_VERSION}\2/g" $f
|
||||
|
||||
# remove backup file
|
||||
rm -f $f.bak
|
||||
done
|
||||
|
||||
fi
|
||||
rg "$PACKAGE_NAME =" || true
|
||||
rg "package = \"$PACKAGE_NAME\"" || true
|
||||
|
||||
if [ $MACOS ]; then
|
||||
printf "prepare $PACKAGE_NAME release $NEW_VERSION" | pbcopy
|
||||
|
53
src/app.rs
53
src/app.rs
@@ -1,6 +1,9 @@
|
||||
use std::{cell::RefCell, fmt, future::Future, rc::Rc};
|
||||
use std::{cell::RefCell, fmt, future::Future, marker::PhantomData, rc::Rc};
|
||||
|
||||
use actix_http::{body::MessageBody, Extensions, Request};
|
||||
use actix_http::{
|
||||
body::{BoxBody, MessageBody},
|
||||
Extensions, Request,
|
||||
};
|
||||
use actix_service::{
|
||||
apply, apply_fn_factory, boxed, IntoServiceFactory, ServiceFactory, ServiceFactoryExt,
|
||||
Transform,
|
||||
@@ -23,7 +26,7 @@ use crate::{
|
||||
|
||||
/// Application builder - structure that follows the builder pattern
|
||||
/// for building application instances.
|
||||
pub struct App<T> {
|
||||
pub struct App<T, B> {
|
||||
endpoint: T,
|
||||
services: Vec<Box<dyn AppServiceFactory>>,
|
||||
default: Option<Rc<BoxedHttpServiceFactory>>,
|
||||
@@ -31,9 +34,10 @@ pub struct App<T> {
|
||||
data_factories: Vec<FnDataFactory>,
|
||||
external: Vec<ResourceDef>,
|
||||
extensions: Extensions,
|
||||
_phantom: PhantomData<B>,
|
||||
}
|
||||
|
||||
impl App<AppEntry> {
|
||||
impl App<AppEntry, BoxBody> {
|
||||
/// Create application builder. Application can be configured with a builder-like pattern.
|
||||
#[allow(clippy::new_without_default)]
|
||||
pub fn new() -> Self {
|
||||
@@ -47,11 +51,22 @@ impl App<AppEntry> {
|
||||
factory_ref,
|
||||
external: Vec::new(),
|
||||
extensions: Extensions::new(),
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> App<T> {
|
||||
impl<T, B> App<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Config = (),
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
{
|
||||
/// Set application (root level) data.
|
||||
///
|
||||
/// Application data stored with `App::app_data()` method is available through the
|
||||
@@ -350,7 +365,7 @@ impl<T> App<T> {
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap<M, B, B1>(
|
||||
pub fn wrap<M, B1>(
|
||||
self,
|
||||
mw: M,
|
||||
) -> App<
|
||||
@@ -361,16 +376,9 @@ impl<T> App<T> {
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
B1,
|
||||
>
|
||||
where
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
Config = (),
|
||||
InitError = (),
|
||||
>,
|
||||
B: MessageBody,
|
||||
M: Transform<
|
||||
T::Service,
|
||||
ServiceRequest,
|
||||
@@ -388,6 +396,7 @@ impl<T> App<T> {
|
||||
factory_ref: self.factory_ref,
|
||||
external: self.external,
|
||||
extensions: self.extensions,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -422,7 +431,7 @@ impl<T> App<T> {
|
||||
/// .route("/index.html", web::get().to(index));
|
||||
/// }
|
||||
/// ```
|
||||
pub fn wrap_fn<F, R, B, B1>(
|
||||
pub fn wrap_fn<B1, F, R>(
|
||||
self,
|
||||
mw: F,
|
||||
) -> App<
|
||||
@@ -433,19 +442,12 @@ impl<T> App<T> {
|
||||
Error = Error,
|
||||
InitError = (),
|
||||
>,
|
||||
B1,
|
||||
>
|
||||
where
|
||||
T: ServiceFactory<
|
||||
ServiceRequest,
|
||||
Response = ServiceResponse<B>,
|
||||
Error = Error,
|
||||
Config = (),
|
||||
InitError = (),
|
||||
>,
|
||||
B: MessageBody,
|
||||
B1: MessageBody,
|
||||
F: Fn(ServiceRequest, &T::Service) -> R + Clone,
|
||||
R: Future<Output = Result<ServiceResponse<B1>, Error>>,
|
||||
B1: MessageBody,
|
||||
{
|
||||
App {
|
||||
endpoint: apply_fn_factory(self.endpoint, mw),
|
||||
@@ -455,11 +457,12 @@ impl<T> App<T> {
|
||||
factory_ref: self.factory_ref,
|
||||
external: self.external,
|
||||
extensions: self.extensions,
|
||||
_phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> IntoServiceFactory<AppInit<T, B>, Request> for App<T>
|
||||
impl<T, B> IntoServiceFactory<AppInit<T, B>, Request> for App<T, B>
|
||||
where
|
||||
B: MessageBody,
|
||||
T: ServiceFactory<
|
||||
|
@@ -197,9 +197,7 @@ where
|
||||
|
||||
actix_service::forward_ready!(service);
|
||||
|
||||
fn call(&self, mut req: Request) -> Self::Future {
|
||||
let req_data = Rc::new(RefCell::new(req.take_req_data()));
|
||||
let conn_data = req.take_conn_data();
|
||||
fn call(&self, req: Request) -> Self::Future {
|
||||
let (head, payload) = req.into_parts();
|
||||
|
||||
let req = if let Some(mut req) = self.app_state.pool().pop() {
|
||||
@@ -207,8 +205,6 @@ where
|
||||
inner.path.get_mut().update(&head.uri);
|
||||
inner.path.reset();
|
||||
inner.head = head;
|
||||
inner.conn_data = conn_data;
|
||||
inner.req_data = req_data;
|
||||
req
|
||||
} else {
|
||||
HttpRequest::new(
|
||||
@@ -216,8 +212,6 @@ where
|
||||
head,
|
||||
self.app_state.clone(),
|
||||
self.app_data.clone(),
|
||||
conn_data,
|
||||
req_data,
|
||||
)
|
||||
};
|
||||
self.service.call(ServiceRequest::new(req, payload))
|
||||
|
50
src/data.rs
50
src/data.rs
@@ -31,53 +31,41 @@ pub(crate) type FnDataFactory =
|
||||
/// server constructs an application instance for each thread, thus application data must be
|
||||
/// constructed multiple times. If you want to share data between different threads, a shareable
|
||||
/// object should be used, e.g. `Send + Sync`. Application data does not need to be `Send`
|
||||
/// or `Sync`. Internally `Data` contains an `Arc`.
|
||||
/// or `Sync`. Internally `Data` uses `Arc`.
|
||||
///
|
||||
/// If route data is not set for a handler, using `Data<T>` extractor would cause a `500 Internal
|
||||
/// Server Error` response.
|
||||
/// If route data is not set for a handler, using `Data<T>` extractor would cause *Internal
|
||||
/// Server Error* response.
|
||||
///
|
||||
/// # Unsized Data
|
||||
/// For types that are unsized, most commonly `dyn T`, `Data` can wrap these types by first
|
||||
/// constructing an `Arc<dyn T>` and using the `From` implementation to convert it.
|
||||
///
|
||||
/// ```
|
||||
/// # use std::{fmt::Display, sync::Arc};
|
||||
/// # use actix_web::web::Data;
|
||||
/// let displayable_arc: Arc<dyn Display> = Arc::new(42usize);
|
||||
/// let displayable_data: Data<dyn Display> = Data::from(displayable_arc);
|
||||
/// ```
|
||||
// TODO: document `dyn T` functionality through converting an Arc
|
||||
// TODO: note equivalence of req.app_data<Data<T>> and Data<T> extractor
|
||||
// TODO: note that data must be inserted using Data<T> in order to extract it
|
||||
///
|
||||
/// # Examples
|
||||
/// ```
|
||||
/// use std::sync::Mutex;
|
||||
/// use actix_web::{App, HttpRequest, HttpResponse, Responder, web::{self, Data}};
|
||||
/// use actix_web::{web, App, HttpResponse, Responder};
|
||||
///
|
||||
/// struct MyData {
|
||||
/// counter: usize,
|
||||
/// }
|
||||
///
|
||||
/// /// Use the `Data<T>` extractor to access data in a handler.
|
||||
/// async fn index(data: Data<Mutex<MyData>>) -> impl Responder {
|
||||
/// let mut my_data = data.lock().unwrap();
|
||||
/// my_data.counter += 1;
|
||||
/// async fn index(data: web::Data<Mutex<MyData>>) -> impl Responder {
|
||||
/// let mut data = data.lock().unwrap();
|
||||
/// data.counter += 1;
|
||||
/// HttpResponse::Ok()
|
||||
/// }
|
||||
///
|
||||
/// /// Alteratively, use the `HttpRequest::app_data` method to access data in a handler.
|
||||
/// async fn index_alt(req: HttpRequest) -> impl Responder {
|
||||
/// let data = req.app_data::<Data<Mutex<MyData>>>().unwrap();
|
||||
/// let mut my_data = data.lock().unwrap();
|
||||
/// my_data.counter += 1;
|
||||
/// HttpResponse::Ok()
|
||||
/// fn main() {
|
||||
/// let data = web::Data::new(Mutex::new(MyData{ counter: 0 }));
|
||||
///
|
||||
/// let app = App::new()
|
||||
/// // Store `MyData` in application storage.
|
||||
/// .app_data(data.clone())
|
||||
/// .service(
|
||||
/// web::resource("/index.html").route(
|
||||
/// web::get().to(index)));
|
||||
/// }
|
||||
///
|
||||
/// let data = Data::new(Mutex::new(MyData { counter: 0 }));
|
||||
///
|
||||
/// let app = App::new()
|
||||
/// // Store `MyData` in application storage.
|
||||
/// .app_data(Data::clone(&data))
|
||||
/// .route("/index.html", web::get().to(index))
|
||||
/// .route("/index-alt.html", web::get().to(index_alt));
|
||||
/// ```
|
||||
#[derive(Debug)]
|
||||
pub struct Data<T: ?Sized>(Arc<T>);
|
||||
|
@@ -14,7 +14,10 @@ pub use crate::types::form::UrlEncoded;
|
||||
pub use crate::types::json::JsonBody;
|
||||
pub use crate::types::readlines::Readlines;
|
||||
|
||||
pub use actix_http::{Extensions, Payload, PayloadStream, RequestHead, Response, ResponseHead};
|
||||
pub use actix_http::{
|
||||
CloneableExtensions, Extensions, Payload, PayloadStream, RequestHead, Response,
|
||||
ResponseHead,
|
||||
};
|
||||
pub use actix_router::{Path, ResourceDef, ResourcePath, Url};
|
||||
pub use actix_server::{Server, ServerHandle};
|
||||
pub use actix_service::{
|
||||
|
@@ -128,7 +128,7 @@ macro_rules! error_helper {
|
||||
InternalError::new(err, StatusCode::$status).into()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
error_helper!(ErrorBadRequest, BAD_REQUEST);
|
||||
|
@@ -1,9 +1,8 @@
|
||||
//! Error and Result module
|
||||
// This is meant to be a glob import of the whole error module except for `Error`. Rustdoc can't yet
|
||||
// correctly resolve the conflicting `Error` type defined in this module, so these re-exports are
|
||||
// expanded manually.
|
||||
//
|
||||
// See <https://github.com/rust-lang/rust/issues/83375>
|
||||
|
||||
/// This is meant to be a glob import of the whole error module, but rustdoc can't handle
|
||||
/// shadowing `Error` type, so it is expanded manually.
|
||||
/// See https://github.com/rust-lang/rust/issues/83375
|
||||
pub use actix_http::error::{
|
||||
BlockingError, ContentTypeError, DispatchError, HttpError, ParseError, PayloadError,
|
||||
};
|
||||
|
12
src/info.rs
12
src/info.rs
@@ -1,4 +1,4 @@
|
||||
use std::{convert::Infallible, net::SocketAddr};
|
||||
use std::{cell::Ref, convert::Infallible, net::SocketAddr};
|
||||
|
||||
use actix_utils::future::{err, ok, Ready};
|
||||
use derive_more::{Display, Error};
|
||||
@@ -72,7 +72,15 @@ pub struct ConnectionInfo {
|
||||
}
|
||||
|
||||
impl ConnectionInfo {
|
||||
pub(crate) fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
|
||||
/// Create *ConnectionInfo* instance for a request.
|
||||
pub fn get<'a>(req: &'a RequestHead, cfg: &AppConfig) -> Ref<'a, Self> {
|
||||
if !req.extensions().contains::<ConnectionInfo>() {
|
||||
req.extensions_mut().insert(ConnectionInfo::new(req, cfg));
|
||||
}
|
||||
Ref::map(req.extensions(), |e| e.get().unwrap())
|
||||
}
|
||||
|
||||
fn new(req: &RequestHead, cfg: &AppConfig) -> ConnectionInfo {
|
||||
let mut host = None;
|
||||
let mut scheme = None;
|
||||
let mut realip_remote_addr = None;
|
||||
|
@@ -65,7 +65,6 @@
|
||||
//! * `secure-cookies` - secure cookies support
|
||||
|
||||
#![deny(rust_2018_idioms, nonstandard_style)]
|
||||
#![warn(future_incompatible)]
|
||||
#![allow(clippy::needless_doctest_main, clippy::type_complexity)]
|
||||
#![doc(html_logo_url = "https://actix.rs/img/logo.png")]
|
||||
#![doc(html_favicon_url = "https://actix.rs/favicon.ico")]
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user