Compare commits
41 Commits
v0.14.18-p
...
5e20688398
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5e20688398 | ||
|
|
bb3af17ce1 | ||
|
|
889fa2d872 | ||
|
|
cd32454403 | ||
|
|
c558647762 | ||
|
|
3c7bef3b6f | ||
|
|
491b076bca | ||
|
|
ca99e23e27 | ||
|
|
0c8ee93d7f | ||
|
|
d4b5bd4ee6 | ||
|
|
509672aada | ||
|
|
09e35668e5 | ||
|
|
3660443108 | ||
|
|
ce72f73464 | ||
|
|
a563404033 | ||
|
|
5fa113ebff | ||
|
|
e9cab49e6e | ||
|
|
2c7344a65b | ||
|
|
b2052a433f | ||
|
|
f12d4d4aa8 | ||
|
|
4545c3ef19 | ||
|
|
f8e2a83194 | ||
|
|
a929df843e | ||
|
|
3a755a632d | ||
|
|
4678be9e81 | ||
|
|
775fac114b | ||
|
|
a32658c1ae | ||
|
|
67b73138f1 | ||
|
|
faf24c6ad8 | ||
|
|
6a35c175f2 | ||
|
|
89598dfcfe | ||
|
|
78de8914ea | ||
|
|
e1138d716d | ||
|
|
8834d5a2a7 | ||
|
|
ffbf610b16 | ||
|
|
d2c945e8ed | ||
|
|
311ba2b97e | ||
|
|
1d895b8dfc | ||
|
|
e3ee1de32d | ||
|
|
dd08d9c3e5 | ||
|
|
0fec1c8737 |
24
.github/workflows/CI.yml
vendored
24
.github/workflows/CI.yml
vendored
@@ -16,6 +16,7 @@ jobs:
|
||||
- style
|
||||
- test
|
||||
- msrv
|
||||
- miri
|
||||
- features
|
||||
- ffi
|
||||
- ffi-header
|
||||
@@ -100,7 +101,7 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
rust:
|
||||
- 1.49 # keep in sync with MSRV.md dev doc
|
||||
- 1.56 # keep in sync with MSRV.md dev doc
|
||||
|
||||
os:
|
||||
- ubuntu-latest
|
||||
@@ -124,6 +125,27 @@ jobs:
|
||||
command: check
|
||||
args: --features full
|
||||
|
||||
miri:
|
||||
name: Test with Miri
|
||||
needs: [style]
|
||||
runs-on: ubuntu-latest
|
||||
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v1
|
||||
|
||||
- name: Install Rust
|
||||
uses: actions-rs/toolchain@v1
|
||||
with:
|
||||
profile: minimal
|
||||
toolchain: nightly
|
||||
components: miri
|
||||
override: true
|
||||
|
||||
- name: Test
|
||||
# Can't enable tcp feature since Miri does not support the tokio runtime
|
||||
run: MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test --features http1,http2,client,server,nightly
|
||||
|
||||
features:
|
||||
name: features
|
||||
needs: [style]
|
||||
|
||||
6
.github/workflows/bench.yml
vendored
6
.github/workflows/bench.yml
vendored
@@ -11,9 +11,9 @@ jobs:
|
||||
strategy:
|
||||
matrix:
|
||||
bench:
|
||||
- connect
|
||||
- end_to_end
|
||||
- pipeline
|
||||
#- connect
|
||||
#- end_to_end
|
||||
#- pipeline
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,3 +1,2 @@
|
||||
target
|
||||
Cargo.lock
|
||||
.history
|
||||
23
CHANGELOG.md
23
CHANGELOG.md
@@ -1,3 +1,26 @@
|
||||
### v0.14.19 (2022-05-27)
|
||||
|
||||
|
||||
#### Bug Fixes
|
||||
|
||||
* **http1:** fix preserving header case without enabling ffi (#2820) ([6a35c175](https://github.com/hyperium/hyper/commit/6a35c175f2b416851518b5831c2c7827d6dbd822))
|
||||
* **server:** don't add implicit content-length to HEAD responses (#2836) ([67b73138](https://github.com/hyperium/hyper/commit/67b73138f110979f3c77ef7b56588f018837e592))
|
||||
|
||||
|
||||
#### Features
|
||||
|
||||
* **server:**
|
||||
* add `Connection::http2_max_header_list_size` option (#2828) ([a32658c1](https://github.com/hyperium/hyper/commit/a32658c1ae7f1261fa234a767df963be4fc63521), closes [#2826](https://github.com/hyperium/hyper/issues/2826))
|
||||
* add `AddrStream::local_addr()` (#2816) ([ffbf610b](https://github.com/hyperium/hyper/commit/ffbf610b1631cabfacb20886270e3c137fa93800), closes [#2773](https://github.com/hyperium/hyper/issues/2773))
|
||||
|
||||
|
||||
#### Breaking Changes
|
||||
|
||||
* **ffi (unstable):**
|
||||
* `hyper_clientconn_options_new` no longer sets the `http1_preserve_header_case` connection option by default.
|
||||
Users should now call `hyper_clientconn_options_set_preserve_header_case` if they desire that functionality. ([78de8914](https://github.com/hyperium/hyper/commit/78de8914eadeab4b9a2c71a82c77b2ce33fe6c74))
|
||||
|
||||
|
||||
### v0.14.18 (2022-03-22)
|
||||
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@ You want to contribute? You're awesome! Don't know where to start? Check the [li
|
||||
[easy tag]: https://github.com/hyperium/hyper/issues?q=label%3AE-easy+is%3Aopen
|
||||
|
||||
|
||||
## Pull Requests
|
||||
## [Pull Requests](./docs/PULL_REQUESTS.md)
|
||||
|
||||
- [Submitting a Pull Request](./docs/PULL_REQUESTS.md#submitting-a-pull-request)
|
||||
- [Commit Guidelines](./docs/COMMITS.md)
|
||||
|
||||
24
Cargo.toml
24
Cargo.toml
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "hyper"
|
||||
version = "0.14.18"
|
||||
version = "1.0.0-dev.0"
|
||||
description = "A fast and correct HTTP library."
|
||||
readme = "README.md"
|
||||
homepage = "https://hyper.rs"
|
||||
@@ -12,6 +12,8 @@ keywords = ["http", "hyper", "hyperium"]
|
||||
categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"]
|
||||
edition = "2018"
|
||||
|
||||
publish = false # no accidents while in dev
|
||||
|
||||
include = [
|
||||
"Cargo.toml",
|
||||
"LICENSE",
|
||||
@@ -25,7 +27,8 @@ futures-core = { version = "0.3", default-features = false }
|
||||
futures-channel = "0.3"
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
http = "0.2"
|
||||
http-body = "0.4"
|
||||
http-body = { git = "https://github.com/hyperium/http-body", branch = "master" }
|
||||
http-body-util = { git = "https://github.com/hyperium/http-body", branch = "master" }
|
||||
httpdate = "1.0"
|
||||
httparse = "1.6"
|
||||
h2 = { version = "0.3.9", optional = true }
|
||||
@@ -61,7 +64,7 @@ tokio = { version = "1", features = [
|
||||
"test-util",
|
||||
] }
|
||||
tokio-test = "0.4"
|
||||
tokio-util = { version = "0.6", features = ["codec"] }
|
||||
tokio-util = { version = "0.7", features = ["codec"] }
|
||||
tower = { version = "0.4", features = ["make", "util"] }
|
||||
url = "2.2"
|
||||
|
||||
@@ -78,7 +81,6 @@ full = [
|
||||
"http1",
|
||||
"http2",
|
||||
"server",
|
||||
"stream",
|
||||
"runtime",
|
||||
]
|
||||
|
||||
@@ -90,17 +92,8 @@ http2 = ["h2"]
|
||||
client = []
|
||||
server = []
|
||||
|
||||
# `impl Stream` for things
|
||||
stream = []
|
||||
|
||||
# Tokio support
|
||||
runtime = [
|
||||
"tcp",
|
||||
"tokio/rt",
|
||||
"tokio/time",
|
||||
]
|
||||
tcp = [
|
||||
"socket2",
|
||||
"tokio/net",
|
||||
"tokio/rt",
|
||||
"tokio/time",
|
||||
@@ -188,11 +181,6 @@ name = "state"
|
||||
path = "examples/state.rs"
|
||||
required-features = ["full"]
|
||||
|
||||
[[example]]
|
||||
name = "tower_client"
|
||||
path = "examples/tower_client.rs"
|
||||
required-features = ["full"]
|
||||
|
||||
[[example]]
|
||||
name = "tower_server"
|
||||
path = "examples/tower_server.rs"
|
||||
|
||||
@@ -8,6 +8,10 @@
|
||||
|
||||
A **fast** and **correct** HTTP implementation for Rust.
|
||||
|
||||
> **Note**: hyper's [master](https://github.com/hyperium/hyper) branch is
|
||||
> currently preparing breaking changes. For the most recently *released* code,
|
||||
> look to the [0.14.x branch](https://github.com/hyperium/hyper/tree/0.14.x).
|
||||
|
||||
- HTTP/1 and HTTP/2
|
||||
- Asynchronous design
|
||||
- Leading in performance
|
||||
|
||||
@@ -6,7 +6,7 @@ extern crate test;
|
||||
use bytes::Buf;
|
||||
use futures_util::stream;
|
||||
use futures_util::StreamExt;
|
||||
use hyper::body::Body;
|
||||
use http_body_util::StreamBody;
|
||||
|
||||
macro_rules! bench_stream {
|
||||
($bencher:ident, bytes: $bytes:expr, count: $count:expr, $total_ident:ident, $body_pat:pat, $block:expr) => {{
|
||||
@@ -20,9 +20,10 @@ macro_rules! bench_stream {
|
||||
|
||||
$bencher.iter(|| {
|
||||
rt.block_on(async {
|
||||
let $body_pat = Body::wrap_stream(
|
||||
let $body_pat = StreamBody::new(
|
||||
stream::iter(__s.iter()).map(|&s| Ok::<_, std::convert::Infallible>(s)),
|
||||
);
|
||||
|
||||
$block;
|
||||
});
|
||||
});
|
||||
|
||||
@@ -3,35 +3,38 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
use http::Uri;
|
||||
use hyper::client::connect::HttpConnector;
|
||||
use hyper::service::Service;
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::TcpListener;
|
||||
// TODO: Reimplement http_connector bench using hyper::client::conn
|
||||
// (instead of removed HttpConnector).
|
||||
|
||||
#[bench]
|
||||
fn http_connector(b: &mut test::Bencher) {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("rt build");
|
||||
let listener = rt
|
||||
.block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0))))
|
||||
.expect("bind");
|
||||
let addr = listener.local_addr().expect("local_addr");
|
||||
let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse");
|
||||
let mut connector = HttpConnector::new();
|
||||
// use http::Uri;
|
||||
// use hyper::client::connect::HttpConnector;
|
||||
// use hyper::service::Service;
|
||||
// use std::net::SocketAddr;
|
||||
// use tokio::net::TcpListener;
|
||||
|
||||
rt.spawn(async move {
|
||||
loop {
|
||||
let _ = listener.accept().await;
|
||||
}
|
||||
});
|
||||
// #[bench]
|
||||
// fn http_connector(b: &mut test::Bencher) {
|
||||
// let _ = pretty_env_logger::try_init();
|
||||
// let rt = tokio::runtime::Builder::new_current_thread()
|
||||
// .enable_all()
|
||||
// .build()
|
||||
// .expect("rt build");
|
||||
// let listener = rt
|
||||
// .block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0))))
|
||||
// .expect("bind");
|
||||
// let addr = listener.local_addr().expect("local_addr");
|
||||
// let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse");
|
||||
// let mut connector = HttpConnector::new();
|
||||
|
||||
b.iter(|| {
|
||||
rt.block_on(async {
|
||||
connector.call(dst.clone()).await.expect("connect");
|
||||
});
|
||||
});
|
||||
}
|
||||
// rt.spawn(async move {
|
||||
// loop {
|
||||
// let _ = listener.accept().await;
|
||||
// }
|
||||
// });
|
||||
|
||||
// b.iter(|| {
|
||||
// rt.block_on(async {
|
||||
// connector.call(dst.clone()).await.expect("connect");
|
||||
// });
|
||||
// });
|
||||
// }
|
||||
|
||||
@@ -3,380 +3,383 @@
|
||||
|
||||
extern crate test;
|
||||
|
||||
use std::net::SocketAddr;
|
||||
// TODO: Reimplement Opts::bench using hyper::server::conn and hyper::client::conn
|
||||
// (instead of Server and HttpClient).
|
||||
|
||||
use futures_util::future::join_all;
|
||||
// use std::net::SocketAddr;
|
||||
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server};
|
||||
// use futures_util::future::join_all;
|
||||
|
||||
// HTTP1
|
||||
// use hyper::client::HttpConnector;
|
||||
// use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server};
|
||||
|
||||
#[bench]
|
||||
fn http1_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||
opts().bench(b)
|
||||
}
|
||||
// // HTTP1
|
||||
|
||||
#[bench]
|
||||
fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||
opts()
|
||||
.method(Method::POST)
|
||||
.request_body(&[b's'; 10])
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||
// opts().bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 100];
|
||||
opts()
|
||||
.method(Method::POST)
|
||||
.request_body(body)
|
||||
.response_body(body)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||
// opts()
|
||||
// .method(Method::POST)
|
||||
// .request_body(&[b's'; 10])
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 10];
|
||||
opts()
|
||||
.method(Method::POST)
|
||||
.request_body(body)
|
||||
.response_body(body)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 100];
|
||||
// opts()
|
||||
// .method(Method::POST)
|
||||
// .request_body(body)
|
||||
// .response_body(body)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_parallel_x10_empty(b: &mut test::Bencher) {
|
||||
opts().parallel(10).bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||
// opts()
|
||||
// .method(Method::POST)
|
||||
// .request_body(body)
|
||||
// .response_body(body)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 10];
|
||||
opts()
|
||||
.parallel(10)
|
||||
.method(Method::POST)
|
||||
.request_body(body)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_parallel_x10_empty(b: &mut test::Bencher) {
|
||||
// opts().parallel(10).bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 10];
|
||||
opts()
|
||||
.parallel(10)
|
||||
.method(Method::POST)
|
||||
.request_chunks(body, 100)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||
// opts()
|
||||
// .parallel(10)
|
||||
// .method(Method::POST)
|
||||
// .request_body(body)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 1];
|
||||
opts().parallel(10).response_body(body).bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 10];
|
||||
// opts()
|
||||
// .parallel(10)
|
||||
// .method(Method::POST)
|
||||
// .request_chunks(body, 100)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 10];
|
||||
opts().parallel(10).response_body(body).bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 1];
|
||||
// opts().parallel(10).response_body(body).bench(b)
|
||||
// }
|
||||
|
||||
// HTTP2
|
||||
// #[bench]
|
||||
// fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||
// opts().parallel(10).response_body(body).bench(b)
|
||||
// }
|
||||
|
||||
const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1;
|
||||
// // HTTP2
|
||||
|
||||
#[bench]
|
||||
fn http2_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||
opts().http2().bench(b)
|
||||
}
|
||||
// const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1;
|
||||
|
||||
#[bench]
|
||||
fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||
opts()
|
||||
.http2()
|
||||
.method(Method::POST)
|
||||
.request_body(&[b's'; 10])
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||
// opts().http2().bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 100];
|
||||
opts()
|
||||
.http2()
|
||||
.method(Method::POST)
|
||||
.request_body(body)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||
// opts()
|
||||
// .http2()
|
||||
// .method(Method::POST)
|
||||
// .request_body(&[b's'; 10])
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_empty(b: &mut test::Bencher) {
|
||||
opts().http2().parallel(10).bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 100];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .method(Method::POST)
|
||||
// .request_body(body)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 10];
|
||||
opts()
|
||||
.http2()
|
||||
.parallel(10)
|
||||
.method(Method::POST)
|
||||
.request_body(body)
|
||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_empty(b: &mut test::Bencher) {
|
||||
// opts().http2().parallel(10).bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 10];
|
||||
opts()
|
||||
.http2()
|
||||
.parallel(10)
|
||||
.method(Method::POST)
|
||||
.request_chunks(body, 100)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .parallel(10)
|
||||
// .method(Method::POST)
|
||||
// .request_body(body)
|
||||
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 10];
|
||||
opts()
|
||||
.http2()
|
||||
.parallel(10)
|
||||
.method(Method::POST)
|
||||
.request_chunks(body, 100)
|
||||
.http2_adaptive_window()
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 10];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .parallel(10)
|
||||
// .method(Method::POST)
|
||||
// .request_chunks(body, 100)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 10];
|
||||
opts()
|
||||
.http2()
|
||||
.parallel(10)
|
||||
.method(Method::POST)
|
||||
.request_chunks(body, 100)
|
||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 10];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .parallel(10)
|
||||
// .method(Method::POST)
|
||||
// .request_chunks(body, 100)
|
||||
// .http2_adaptive_window()
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 1];
|
||||
opts()
|
||||
.http2()
|
||||
.parallel(10)
|
||||
.response_body(body)
|
||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 10];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .parallel(10)
|
||||
// .method(Method::POST)
|
||||
// .request_chunks(body, 100)
|
||||
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
#[bench]
|
||||
fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
||||
let body = &[b'x'; 1024 * 1024 * 10];
|
||||
opts()
|
||||
.http2()
|
||||
.parallel(10)
|
||||
.response_body(body)
|
||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
.bench(b)
|
||||
}
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 1];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .parallel(10)
|
||||
// .response_body(body)
|
||||
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
// ==== Benchmark Options =====
|
||||
// #[bench]
|
||||
// fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
||||
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||
// opts()
|
||||
// .http2()
|
||||
// .parallel(10)
|
||||
// .response_body(body)
|
||||
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||
// .bench(b)
|
||||
// }
|
||||
|
||||
struct Opts {
|
||||
http2: bool,
|
||||
http2_stream_window: Option<u32>,
|
||||
http2_conn_window: Option<u32>,
|
||||
http2_adaptive_window: bool,
|
||||
parallel_cnt: u32,
|
||||
request_method: Method,
|
||||
request_body: Option<&'static [u8]>,
|
||||
request_chunks: usize,
|
||||
response_body: &'static [u8],
|
||||
}
|
||||
// // ==== Benchmark Options =====
|
||||
|
||||
fn opts() -> Opts {
|
||||
Opts {
|
||||
http2: false,
|
||||
http2_stream_window: None,
|
||||
http2_conn_window: None,
|
||||
http2_adaptive_window: false,
|
||||
parallel_cnt: 1,
|
||||
request_method: Method::GET,
|
||||
request_body: None,
|
||||
request_chunks: 0,
|
||||
response_body: b"",
|
||||
}
|
||||
}
|
||||
// struct Opts {
|
||||
// http2: bool,
|
||||
// http2_stream_window: Option<u32>,
|
||||
// http2_conn_window: Option<u32>,
|
||||
// http2_adaptive_window: bool,
|
||||
// parallel_cnt: u32,
|
||||
// request_method: Method,
|
||||
// request_body: Option<&'static [u8]>,
|
||||
// request_chunks: usize,
|
||||
// response_body: &'static [u8],
|
||||
// }
|
||||
|
||||
impl Opts {
|
||||
fn http2(mut self) -> Self {
|
||||
self.http2 = true;
|
||||
self
|
||||
}
|
||||
// fn opts() -> Opts {
|
||||
// Opts {
|
||||
// http2: false,
|
||||
// http2_stream_window: None,
|
||||
// http2_conn_window: None,
|
||||
// http2_adaptive_window: false,
|
||||
// parallel_cnt: 1,
|
||||
// request_method: Method::GET,
|
||||
// request_body: None,
|
||||
// request_chunks: 0,
|
||||
// response_body: b"",
|
||||
// }
|
||||
// }
|
||||
|
||||
fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
assert!(!self.http2_adaptive_window);
|
||||
self.http2_stream_window = sz.into();
|
||||
self
|
||||
}
|
||||
// impl Opts {
|
||||
// fn http2(mut self) -> Self {
|
||||
// self.http2 = true;
|
||||
// self
|
||||
// }
|
||||
|
||||
fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
assert!(!self.http2_adaptive_window);
|
||||
self.http2_conn_window = sz.into();
|
||||
self
|
||||
}
|
||||
// fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
// assert!(!self.http2_adaptive_window);
|
||||
// self.http2_stream_window = sz.into();
|
||||
// self
|
||||
// }
|
||||
|
||||
fn http2_adaptive_window(mut self) -> Self {
|
||||
assert!(self.http2_stream_window.is_none());
|
||||
assert!(self.http2_conn_window.is_none());
|
||||
self.http2_adaptive_window = true;
|
||||
self
|
||||
}
|
||||
// fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
// assert!(!self.http2_adaptive_window);
|
||||
// self.http2_conn_window = sz.into();
|
||||
// self
|
||||
// }
|
||||
|
||||
fn method(mut self, m: Method) -> Self {
|
||||
self.request_method = m;
|
||||
self
|
||||
}
|
||||
// fn http2_adaptive_window(mut self) -> Self {
|
||||
// assert!(self.http2_stream_window.is_none());
|
||||
// assert!(self.http2_conn_window.is_none());
|
||||
// self.http2_adaptive_window = true;
|
||||
// self
|
||||
// }
|
||||
|
||||
fn request_body(mut self, body: &'static [u8]) -> Self {
|
||||
self.request_body = Some(body);
|
||||
self
|
||||
}
|
||||
// fn method(mut self, m: Method) -> Self {
|
||||
// self.request_method = m;
|
||||
// self
|
||||
// }
|
||||
|
||||
fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self {
|
||||
assert!(cnt > 0);
|
||||
self.request_body = Some(chunk);
|
||||
self.request_chunks = cnt;
|
||||
self
|
||||
}
|
||||
// fn request_body(mut self, body: &'static [u8]) -> Self {
|
||||
// self.request_body = Some(body);
|
||||
// self
|
||||
// }
|
||||
|
||||
fn response_body(mut self, body: &'static [u8]) -> Self {
|
||||
self.response_body = body;
|
||||
self
|
||||
}
|
||||
// fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self {
|
||||
// assert!(cnt > 0);
|
||||
// self.request_body = Some(chunk);
|
||||
// self.request_chunks = cnt;
|
||||
// self
|
||||
// }
|
||||
|
||||
fn parallel(mut self, cnt: u32) -> Self {
|
||||
assert!(cnt > 0, "parallel count must be larger than 0");
|
||||
self.parallel_cnt = cnt;
|
||||
self
|
||||
}
|
||||
// fn response_body(mut self, body: &'static [u8]) -> Self {
|
||||
// self.response_body = body;
|
||||
// self
|
||||
// }
|
||||
|
||||
fn bench(self, b: &mut test::Bencher) {
|
||||
use std::sync::Arc;
|
||||
let _ = pretty_env_logger::try_init();
|
||||
// Create a runtime of current thread.
|
||||
let rt = Arc::new(
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("rt build"),
|
||||
);
|
||||
let exec = rt.clone();
|
||||
// fn parallel(mut self, cnt: u32) -> Self {
|
||||
// assert!(cnt > 0, "parallel count must be larger than 0");
|
||||
// self.parallel_cnt = cnt;
|
||||
// self
|
||||
// }
|
||||
|
||||
let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64;
|
||||
let req_len = if self.request_chunks > 0 {
|
||||
req_len * self.request_chunks as u64
|
||||
} else {
|
||||
req_len
|
||||
};
|
||||
let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64;
|
||||
b.bytes = bytes_per_iter;
|
||||
// fn bench(self, b: &mut test::Bencher) {
|
||||
// use std::sync::Arc;
|
||||
// let _ = pretty_env_logger::try_init();
|
||||
// // Create a runtime of current thread.
|
||||
// let rt = Arc::new(
|
||||
// tokio::runtime::Builder::new_current_thread()
|
||||
// .enable_all()
|
||||
// .build()
|
||||
// .expect("rt build"),
|
||||
// );
|
||||
// let exec = rt.clone();
|
||||
|
||||
let addr = spawn_server(&rt, &self);
|
||||
// let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64;
|
||||
// let req_len = if self.request_chunks > 0 {
|
||||
// req_len * self.request_chunks as u64
|
||||
// } else {
|
||||
// req_len
|
||||
// };
|
||||
// let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64;
|
||||
// b.bytes = bytes_per_iter;
|
||||
|
||||
let connector = HttpConnector::new();
|
||||
let client = hyper::Client::builder()
|
||||
.http2_only(self.http2)
|
||||
.http2_initial_stream_window_size(self.http2_stream_window)
|
||||
.http2_initial_connection_window_size(self.http2_conn_window)
|
||||
.http2_adaptive_window(self.http2_adaptive_window)
|
||||
.build::<_, Body>(connector);
|
||||
// let addr = spawn_server(&rt, &self);
|
||||
|
||||
let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap();
|
||||
// let connector = HttpConnector::new();
|
||||
// let client = hyper::Client::builder()
|
||||
// .http2_only(self.http2)
|
||||
// .http2_initial_stream_window_size(self.http2_stream_window)
|
||||
// .http2_initial_connection_window_size(self.http2_conn_window)
|
||||
// .http2_adaptive_window(self.http2_adaptive_window)
|
||||
// .build::<_, Body>(connector);
|
||||
|
||||
let make_request = || {
|
||||
let chunk_cnt = self.request_chunks;
|
||||
let body = if chunk_cnt > 0 {
|
||||
let (mut tx, body) = Body::channel();
|
||||
let chunk = self
|
||||
.request_body
|
||||
.expect("request_chunks means request_body");
|
||||
exec.spawn(async move {
|
||||
for _ in 0..chunk_cnt {
|
||||
tx.send_data(chunk.into()).await.expect("send_data");
|
||||
}
|
||||
});
|
||||
body
|
||||
} else {
|
||||
self.request_body
|
||||
.map(Body::from)
|
||||
.unwrap_or_else(Body::empty)
|
||||
};
|
||||
let mut req = Request::new(body);
|
||||
*req.method_mut() = self.request_method.clone();
|
||||
*req.uri_mut() = url.clone();
|
||||
req
|
||||
};
|
||||
// let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap();
|
||||
|
||||
let send_request = |req: Request<Body>| {
|
||||
let fut = client.request(req);
|
||||
async {
|
||||
let res = fut.await.expect("client wait");
|
||||
let mut body = res.into_body();
|
||||
while let Some(_chunk) = body.data().await {}
|
||||
}
|
||||
};
|
||||
// let make_request = || {
|
||||
// let chunk_cnt = self.request_chunks;
|
||||
// let body = if chunk_cnt > 0 {
|
||||
// let (mut tx, body) = Body::channel();
|
||||
// let chunk = self
|
||||
// .request_body
|
||||
// .expect("request_chunks means request_body");
|
||||
// exec.spawn(async move {
|
||||
// for _ in 0..chunk_cnt {
|
||||
// tx.send_data(chunk.into()).await.expect("send_data");
|
||||
// }
|
||||
// });
|
||||
// body
|
||||
// } else {
|
||||
// self.request_body
|
||||
// .map(Body::from)
|
||||
// .unwrap_or_else(Body::empty)
|
||||
// };
|
||||
// let mut req = Request::new(body);
|
||||
// *req.method_mut() = self.request_method.clone();
|
||||
// *req.uri_mut() = url.clone();
|
||||
// req
|
||||
// };
|
||||
|
||||
if self.parallel_cnt == 1 {
|
||||
b.iter(|| {
|
||||
let req = make_request();
|
||||
rt.block_on(send_request(req));
|
||||
});
|
||||
} else {
|
||||
b.iter(|| {
|
||||
let futs = (0..self.parallel_cnt).map(|_| {
|
||||
let req = make_request();
|
||||
send_request(req)
|
||||
});
|
||||
// Await all spawned futures becoming completed.
|
||||
rt.block_on(join_all(futs));
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
// let send_request = |req: Request<Body>| {
|
||||
// let fut = client.request(req);
|
||||
// async {
|
||||
// let res = fut.await.expect("client wait");
|
||||
// let mut body = res.into_body();
|
||||
// while let Some(_chunk) = body.data().await {}
|
||||
// }
|
||||
// };
|
||||
|
||||
fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr {
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
// if self.parallel_cnt == 1 {
|
||||
// b.iter(|| {
|
||||
// let req = make_request();
|
||||
// rt.block_on(send_request(req));
|
||||
// });
|
||||
// } else {
|
||||
// b.iter(|| {
|
||||
// let futs = (0..self.parallel_cnt).map(|_| {
|
||||
// let req = make_request();
|
||||
// send_request(req)
|
||||
// });
|
||||
// // Await all spawned futures becoming completed.
|
||||
// rt.block_on(join_all(futs));
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
let body = opts.response_body;
|
||||
let srv = rt.block_on(async move {
|
||||
Server::bind(&addr)
|
||||
.http2_only(opts.http2)
|
||||
.http2_initial_stream_window_size(opts.http2_stream_window)
|
||||
.http2_initial_connection_window_size(opts.http2_conn_window)
|
||||
.http2_adaptive_window(opts.http2_adaptive_window)
|
||||
.serve(make_service_fn(move |_| async move {
|
||||
Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move {
|
||||
let mut req_body = req.into_body();
|
||||
while let Some(_chunk) = req_body.data().await {}
|
||||
Ok::<_, hyper::Error>(Response::new(Body::from(body)))
|
||||
}))
|
||||
}))
|
||||
});
|
||||
let addr = srv.local_addr();
|
||||
rt.spawn(async {
|
||||
if let Err(err) = srv.await {
|
||||
panic!("server error: {}", err);
|
||||
}
|
||||
});
|
||||
addr
|
||||
}
|
||||
// fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr {
|
||||
// use hyper::service::{make_service_fn, service_fn};
|
||||
// let addr = "127.0.0.1:0".parse().unwrap();
|
||||
|
||||
// let body = opts.response_body;
|
||||
// let srv = rt.block_on(async move {
|
||||
// Server::bind(&addr)
|
||||
// .http2_only(opts.http2)
|
||||
// .http2_initial_stream_window_size(opts.http2_stream_window)
|
||||
// .http2_initial_connection_window_size(opts.http2_conn_window)
|
||||
// .http2_adaptive_window(opts.http2_adaptive_window)
|
||||
// .serve(make_service_fn(move |_| async move {
|
||||
// Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move {
|
||||
// let mut req_body = req.into_body();
|
||||
// while let Some(_chunk) = req_body.data().await {}
|
||||
// Ok::<_, hyper::Error>(Response::new(Body::from(body)))
|
||||
// }))
|
||||
// }))
|
||||
// });
|
||||
// let addr = srv.local_addr();
|
||||
// rt.spawn(async {
|
||||
// if let Err(err) = srv.await {
|
||||
// panic!("server error: {}", err);
|
||||
// }
|
||||
// });
|
||||
// addr
|
||||
// }
|
||||
|
||||
@@ -4,14 +4,16 @@
|
||||
extern crate test;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net::TcpStream;
|
||||
use std::net::{SocketAddr, TcpStream};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Response, Server};
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Response};
|
||||
|
||||
const PIPELINED_REQUESTS: usize = 16;
|
||||
|
||||
@@ -23,35 +25,34 @@ fn hello_world_16(b: &mut test::Bencher) {
|
||||
let addr = {
|
||||
let (addr_tx, addr_rx) = mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
|
||||
let make_svc = make_service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!")))
|
||||
}))
|
||||
});
|
||||
|
||||
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("rt build");
|
||||
let srv = rt.block_on(async move {
|
||||
Server::bind(&addr)
|
||||
.http1_pipeline_flush(true)
|
||||
.serve(make_svc)
|
||||
});
|
||||
|
||||
addr_tx.send(srv.local_addr()).unwrap();
|
||||
let listener = rt.block_on(TcpListener::bind(addr)).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
let graceful = srv.with_graceful_shutdown(async {
|
||||
until_rx.await.ok();
|
||||
});
|
||||
rt.spawn(async move {
|
||||
loop {
|
||||
let (stream, _addr) = listener.accept().await.expect("accept");
|
||||
|
||||
rt.block_on(async {
|
||||
if let Err(e) = graceful.await {
|
||||
panic!("server error: {}", e);
|
||||
Http::new()
|
||||
.pipeline_flush(true)
|
||||
.serve_connection(
|
||||
stream,
|
||||
service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!")))
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
addr_tx.send(addr).unwrap();
|
||||
rt.block_on(until_rx).ok();
|
||||
});
|
||||
|
||||
addr_rx.recv().unwrap()
|
||||
|
||||
@@ -4,53 +4,59 @@
|
||||
extern crate test;
|
||||
|
||||
use std::io::{Read, Write};
|
||||
use std::net::{TcpListener, TcpStream};
|
||||
use std::net::{SocketAddr, TcpListener, TcpStream};
|
||||
use std::sync::mpsc;
|
||||
use std::time::Duration;
|
||||
|
||||
use futures_util::{stream, StreamExt};
|
||||
use http_body_util::{BodyExt, StreamBody};
|
||||
use tokio::sync::oneshot;
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Response, Server};
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::Response;
|
||||
|
||||
macro_rules! bench_server {
|
||||
($b:ident, $header:expr, $body:expr) => {{
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let (_until_tx, until_rx) = oneshot::channel::<()>();
|
||||
|
||||
let addr = {
|
||||
let (addr_tx, addr_rx) = mpsc::channel();
|
||||
std::thread::spawn(move || {
|
||||
let addr = "127.0.0.1:0".parse().unwrap();
|
||||
let make_svc = make_service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(
|
||||
Response::builder()
|
||||
.header($header.0, $header.1)
|
||||
.header("content-type", "text/plain")
|
||||
.body($body())
|
||||
.unwrap(),
|
||||
)
|
||||
}))
|
||||
});
|
||||
|
||||
let addr: SocketAddr = "127.0.0.1:0".parse().unwrap();
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.expect("rt build");
|
||||
|
||||
let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) });
|
||||
let listener = rt.block_on(tokio::net::TcpListener::bind(addr)).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
addr_tx.send(srv.local_addr()).unwrap();
|
||||
rt.spawn(async move {
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await.expect("accept");
|
||||
|
||||
let graceful = srv.with_graceful_shutdown(async {
|
||||
until_rx.await.ok();
|
||||
});
|
||||
rt.block_on(async move {
|
||||
if let Err(e) = graceful.await {
|
||||
panic!("server error: {}", e);
|
||||
Http::new()
|
||||
.serve_connection(
|
||||
stream,
|
||||
service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(
|
||||
Response::builder()
|
||||
.header($header.0, $header.1)
|
||||
.header("content-type", "text/plain")
|
||||
.body($body())
|
||||
.unwrap(),
|
||||
)
|
||||
}),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
|
||||
addr_tx.send(addr).unwrap();
|
||||
rt.block_on(until_rx).ok();
|
||||
});
|
||||
|
||||
addr_rx.recv().unwrap()
|
||||
@@ -99,9 +105,11 @@ fn throughput_fixedsize_large_payload(b: &mut test::Bencher) {
|
||||
|
||||
#[bench]
|
||||
fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) {
|
||||
bench_server!(b, ("content-length", "1000000"), || {
|
||||
bench_server!(b, ("content-length", "1000000"), move || {
|
||||
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
||||
Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
||||
BodyExt::boxed(StreamBody::new(
|
||||
stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -123,7 +131,9 @@ fn throughput_chunked_large_payload(b: &mut test::Bencher) {
|
||||
fn throughput_chunked_many_chunks(b: &mut test::Bencher) {
|
||||
bench_server!(b, ("transfer-encoding", "chunked"), || {
|
||||
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
||||
Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
||||
BodyExt::boxed(StreamBody::new(
|
||||
stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)),
|
||||
))
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
@@ -24,44 +24,42 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b
|
||||
struct conn_data *conn = (struct conn_data *)userdata;
|
||||
ssize_t ret = read(conn->fd, buf, buf_len);
|
||||
|
||||
if (ret < 0) {
|
||||
int err = errno;
|
||||
if (err == EAGAIN) {
|
||||
// would block, register interest
|
||||
if (conn->read_waker != NULL) {
|
||||
hyper_waker_free(conn->read_waker);
|
||||
}
|
||||
conn->read_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
} else {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN) {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
|
||||
// would block, register interest
|
||||
if (conn->read_waker != NULL) {
|
||||
hyper_waker_free(conn->read_waker);
|
||||
}
|
||||
conn->read_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
}
|
||||
|
||||
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
|
||||
struct conn_data *conn = (struct conn_data *)userdata;
|
||||
ssize_t ret = write(conn->fd, buf, buf_len);
|
||||
|
||||
if (ret < 0) {
|
||||
int err = errno;
|
||||
if (err == EAGAIN) {
|
||||
// would block, register interest
|
||||
if (conn->write_waker != NULL) {
|
||||
hyper_waker_free(conn->write_waker);
|
||||
}
|
||||
conn->write_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
} else {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN) {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
|
||||
// would block, register interest
|
||||
if (conn->write_waker != NULL) {
|
||||
hyper_waker_free(conn->write_waker);
|
||||
}
|
||||
conn->write_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
}
|
||||
|
||||
static void free_conn_data(struct conn_data *conn) {
|
||||
@@ -98,9 +96,9 @@ static int connect_to(const char *host, const char *port) {
|
||||
|
||||
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||
break;
|
||||
} else {
|
||||
close(sfd);
|
||||
}
|
||||
|
||||
close(sfd);
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
@@ -142,17 +140,17 @@ typedef enum {
|
||||
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
|
||||
|
||||
int main(int argc, char *argv[]) {
|
||||
const char *host = argc > 1 ? argv[1] : "httpbin.org";
|
||||
const char *port = argc > 2 ? argv[2] : "80";
|
||||
const char *path = argc > 3 ? argv[3] : "/";
|
||||
printf("connecting to port %s on %s...\n", port, host);
|
||||
const char *host = argc > 1 ? argv[1] : "httpbin.org";
|
||||
const char *port = argc > 2 ? argv[2] : "80";
|
||||
const char *path = argc > 3 ? argv[3] : "/";
|
||||
printf("connecting to port %s on %s...\n", port, host);
|
||||
|
||||
int fd = connect_to(host, port);
|
||||
int fd = connect_to(host, port);
|
||||
if (fd < 0) {
|
||||
return 1;
|
||||
}
|
||||
printf("connected to %s, now get %s\n", host, path);
|
||||
|
||||
printf("connected to %s, now get %s\n", host, path);
|
||||
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
|
||||
printf("failed to set socket to non-blocking\n");
|
||||
return 1;
|
||||
@@ -168,7 +166,6 @@ int main(int argc, char *argv[]) {
|
||||
conn->read_waker = NULL;
|
||||
conn->write_waker = NULL;
|
||||
|
||||
|
||||
// Hookup the IO
|
||||
hyper_io *io = hyper_io_new();
|
||||
hyper_io_set_userdata(io, (void *)conn);
|
||||
@@ -315,15 +312,16 @@ int main(int argc, char *argv[]) {
|
||||
if (sel_ret < 0) {
|
||||
printf("select() error\n");
|
||||
return 1;
|
||||
} else {
|
||||
if (FD_ISSET(conn->fd, &fds_read)) {
|
||||
hyper_waker_wake(conn->read_waker);
|
||||
conn->read_waker = NULL;
|
||||
}
|
||||
if (FD_ISSET(conn->fd, &fds_write)) {
|
||||
hyper_waker_wake(conn->write_waker);
|
||||
conn->write_waker = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(conn->fd, &fds_read)) {
|
||||
hyper_waker_wake(conn->read_waker);
|
||||
conn->read_waker = NULL;
|
||||
}
|
||||
|
||||
if (FD_ISSET(conn->fd, &fds_write)) {
|
||||
hyper_waker_wake(conn->write_waker);
|
||||
conn->write_waker = NULL;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -24,44 +24,42 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b
|
||||
struct conn_data *conn = (struct conn_data *)userdata;
|
||||
ssize_t ret = read(conn->fd, buf, buf_len);
|
||||
|
||||
if (ret < 0) {
|
||||
int err = errno;
|
||||
if (err == EAGAIN) {
|
||||
// would block, register interest
|
||||
if (conn->read_waker != NULL) {
|
||||
hyper_waker_free(conn->read_waker);
|
||||
}
|
||||
conn->read_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
} else {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN) {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
|
||||
// would block, register interest
|
||||
if (conn->read_waker != NULL) {
|
||||
hyper_waker_free(conn->read_waker);
|
||||
}
|
||||
conn->read_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
}
|
||||
|
||||
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
|
||||
struct conn_data *conn = (struct conn_data *)userdata;
|
||||
ssize_t ret = write(conn->fd, buf, buf_len);
|
||||
|
||||
if (ret < 0) {
|
||||
int err = errno;
|
||||
if (err == EAGAIN) {
|
||||
// would block, register interest
|
||||
if (conn->write_waker != NULL) {
|
||||
hyper_waker_free(conn->write_waker);
|
||||
}
|
||||
conn->write_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
} else {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
} else {
|
||||
if (ret >= 0) {
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (errno != EAGAIN) {
|
||||
// kaboom
|
||||
return HYPER_IO_ERROR;
|
||||
}
|
||||
|
||||
// would block, register interest
|
||||
if (conn->write_waker != NULL) {
|
||||
hyper_waker_free(conn->write_waker);
|
||||
}
|
||||
conn->write_waker = hyper_context_waker(ctx);
|
||||
return HYPER_IO_PENDING;
|
||||
}
|
||||
|
||||
static void free_conn_data(struct conn_data *conn) {
|
||||
@@ -98,9 +96,9 @@ static int connect_to(const char *host, const char *port) {
|
||||
|
||||
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||
break;
|
||||
} else {
|
||||
close(sfd);
|
||||
}
|
||||
|
||||
close(sfd);
|
||||
}
|
||||
|
||||
freeaddrinfo(result);
|
||||
@@ -126,17 +124,20 @@ static int poll_req_upload(void *userdata,
|
||||
struct upload_body* upload = userdata;
|
||||
|
||||
ssize_t res = read(upload->fd, upload->buf, upload->len);
|
||||
if (res < 0) {
|
||||
printf("error reading upload file: %d", errno);
|
||||
return HYPER_POLL_ERROR;
|
||||
} else if (res == 0) {
|
||||
// All done!
|
||||
*chunk = NULL;
|
||||
return HYPER_POLL_READY;
|
||||
} else {
|
||||
if (res > 0) {
|
||||
*chunk = hyper_buf_copy(upload->buf, res);
|
||||
return HYPER_POLL_READY;
|
||||
}
|
||||
|
||||
if (res == 0) {
|
||||
// All done!
|
||||
*chunk = NULL;
|
||||
return HYPER_POLL_READY;
|
||||
}
|
||||
|
||||
// Oh no!
|
||||
printf("error reading upload file: %d", errno);
|
||||
return HYPER_POLL_ERROR;
|
||||
}
|
||||
|
||||
static int print_each_header(void *userdata,
|
||||
@@ -348,20 +349,20 @@ int main(int argc, char *argv[]) {
|
||||
hyper_executor_push(exec, body_data);
|
||||
|
||||
break;
|
||||
} else {
|
||||
assert(task_type == HYPER_TASK_EMPTY);
|
||||
hyper_task_free(task);
|
||||
hyper_body_free(resp_body);
|
||||
|
||||
printf("\n -- Done! -- \n");
|
||||
|
||||
// Cleaning up before exiting
|
||||
hyper_executor_free(exec);
|
||||
free_conn_data(conn);
|
||||
free(upload.buf);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
assert(task_type == HYPER_TASK_EMPTY);
|
||||
hyper_task_free(task);
|
||||
hyper_body_free(resp_body);
|
||||
|
||||
printf("\n -- Done! -- \n");
|
||||
|
||||
// Cleaning up before exiting
|
||||
hyper_executor_free(exec);
|
||||
free_conn_data(conn);
|
||||
free(upload.buf);
|
||||
|
||||
return 0;
|
||||
case EXAMPLE_NOT_SET:
|
||||
// A background task for hyper completed...
|
||||
hyper_task_free(task);
|
||||
@@ -387,17 +388,17 @@ int main(int argc, char *argv[]) {
|
||||
if (sel_ret < 0) {
|
||||
printf("select() error\n");
|
||||
return 1;
|
||||
} else {
|
||||
if (FD_ISSET(conn->fd, &fds_read)) {
|
||||
hyper_waker_wake(conn->read_waker);
|
||||
conn->read_waker = NULL;
|
||||
}
|
||||
if (FD_ISSET(conn->fd, &fds_write)) {
|
||||
hyper_waker_wake(conn->write_waker);
|
||||
conn->write_waker = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
if (FD_ISSET(conn->fd, &fds_read)) {
|
||||
hyper_waker_wake(conn->read_waker);
|
||||
conn->read_waker = NULL;
|
||||
}
|
||||
|
||||
if (FD_ISSET(conn->fd, &fds_write)) {
|
||||
hyper_waker_wake(conn->write_waker);
|
||||
conn->write_waker = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -355,6 +355,22 @@ void hyper_clientconn_free(struct hyper_clientconn *conn);
|
||||
*/
|
||||
struct hyper_clientconn_options *hyper_clientconn_options_new(void);
|
||||
|
||||
/*
|
||||
Set the whether or not header case is preserved.
|
||||
|
||||
Pass `0` to allow lowercase normalization (default), `1` to retain original case.
|
||||
*/
|
||||
void hyper_clientconn_options_set_preserve_header_case(struct hyper_clientconn_options *opts,
|
||||
int enabled);
|
||||
|
||||
/*
|
||||
Set the whether or not header order is preserved.
|
||||
|
||||
Pass `0` to allow reordering (default), `1` to retain original ordering.
|
||||
*/
|
||||
void hyper_clientconn_options_set_preserve_header_order(struct hyper_clientconn_options *opts,
|
||||
int enabled);
|
||||
|
||||
/*
|
||||
Free a `hyper_clientconn_options *`.
|
||||
*/
|
||||
@@ -386,6 +402,16 @@ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options *
|
||||
enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts,
|
||||
int enabled);
|
||||
|
||||
/*
|
||||
Set whether HTTP/1 connections will accept obsolete line folding for header values.
|
||||
Newline codepoints (\r and \n) will be transformed to spaces when parsing.
|
||||
|
||||
Pass `0` to disable, `1` to enable.
|
||||
|
||||
*/
|
||||
enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hyper_clientconn_options *opts,
|
||||
int enabled);
|
||||
|
||||
/*
|
||||
Frees a `hyper_error`.
|
||||
*/
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
- Don't be mean.
|
||||
- Insulting anyone is prohibited.
|
||||
- Harrassment of any kind is prohibited.
|
||||
- Harassment of any kind is prohibited.
|
||||
- If another person feels uncomfortable with your remarks, stop it.
|
||||
- If a moderator deems your comment or conduct as inappropriate, stop it.
|
||||
- Disagreeing is fine, but keep it to technical arguments. Never attack the person.
|
||||
|
||||
111
docs/GOVERNANCE.md
Normal file
111
docs/GOVERNANCE.md
Normal file
@@ -0,0 +1,111 @@
|
||||
# Governance
|
||||
|
||||
## Making decisions
|
||||
|
||||
There's two main pieces to the way decisions are made in hyper:
|
||||
|
||||
1. A decision-making framework
|
||||
2. The people who apply it
|
||||
|
||||
The people are described [lower down in this document](#roles).
|
||||
|
||||
### Decision-making framework
|
||||
|
||||
We start with the users. The project wouldn't exist without them, and it exists
|
||||
in order to enable users to do amazing things with HTTP. We listen to our
|
||||
users. Some actively contribute their thoughts, but many others we must seek
|
||||
out to learn about their usage, joys, and headaches. Those insights allow our
|
||||
experts to determine the best solutions for the users.
|
||||
|
||||
We then define a set of [TENETS](./TENETS.md), which are guiding principles
|
||||
that can be used to measure aspects of individual decisions. It should be
|
||||
possible to identify one or more tenets that apply to why a decision is made.
|
||||
And the set helps us balance which priorities are more important for our users.
|
||||
|
||||
We combine the usecases with the tenets to come up with a [VISION](./VISION.md)
|
||||
that provides a longer-term plan of what hyper _should_ look like.
|
||||
|
||||
Finally, we define a [ROADMAP](./ROADMAP.md) that describes what the
|
||||
short-term, tactical changes to bring hyper closer in line with the vision.
|
||||
|
||||
## Roles
|
||||
|
||||
These are the roles people can fill when participating in the project. A list
|
||||
of the people in each role is available in [MAINTAINERS](./MAINTAINERS.md).
|
||||
|
||||
### Contributor
|
||||
|
||||
A contributor is anyone who contributes their time to provide value for the
|
||||
project. This could be in the form of code, documentation, filing issues,
|
||||
discussing designs, or helping others on the issue tracker or in chat.
|
||||
|
||||
All contributors MUST follow the [Code of Conduct][coc].
|
||||
|
||||
👋 **New here?** [This could be you!][contrib]
|
||||
|
||||
|
||||
### Triager
|
||||
|
||||
Triagers assess issues on the issue tracker. They help make sure the work is
|
||||
well organized, and are critical for making new issue reporters feeling
|
||||
welcome.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Adhere to the [Code of Conduct][coc]
|
||||
- Follow the [triager's guide][triage-guide]
|
||||
|
||||
Privileges:
|
||||
|
||||
- Can edit, label, and close issues
|
||||
- Member of the organization
|
||||
- Can be assigned issues and pull requests
|
||||
|
||||
How to become:
|
||||
|
||||
- Make a few [contributions][contrib] to the project, to show you can follow
|
||||
the [Code of Conduct][coc].
|
||||
- Self-nominate by making a pull request adding yourself to the
|
||||
[list](./MAINTAINERS.md#triagers).
|
||||
|
||||
|
||||
### Collaborator
|
||||
|
||||
Collaborators are contributors who have been helping out in a consistent basis.
|
||||
|
||||
Responsibilities:
|
||||
|
||||
- Be exemplars of the [Code of Conduct][coc]
|
||||
- Internalize the [VISION](./VISION.md)
|
||||
- Reviewing pull requests from other contributors
|
||||
- Provide feedback on proposed features and design documents
|
||||
- [Welcome new contributors][triage-guide]
|
||||
- Answer questions in issues and/or chat
|
||||
- Mentor contributors to become members
|
||||
|
||||
Privileges:
|
||||
|
||||
- Can review and merge pull requests
|
||||
- Can trigger re-runs of CI, and approve CI for first-time contributors
|
||||
- Can assign issues and pull requests to other organization members
|
||||
|
||||
How to become:
|
||||
|
||||
- Work at fulfilling the above responsibilities.
|
||||
- Any collaborator may nominate a contributor who has been around for some time
|
||||
and is already filling the responsibilities.
|
||||
- Another collaborator must second the nomination.
|
||||
- If there are no objections, a maintainer will welcome the new collaborator.
|
||||
|
||||
Don't be afraid to ask a collaborator for what you could work on to reach this
|
||||
goal!
|
||||
|
||||
### Maintainer
|
||||
|
||||
Maintainers are the project admins. Besides being a collaborator, they take care
|
||||
house-keeping duties, help lead the direction, and have the final authority when
|
||||
required.
|
||||
|
||||
[coc]: ./CODE_OF_CONDUCT.md
|
||||
[contrib]: ../CONTRIBUTING.md
|
||||
[triage-guide]: ./ISSUES.md#triaging
|
||||
@@ -2,6 +2,68 @@
|
||||
|
||||
The [issue tracker][issues] for hyper is where we track all features, bugs, and discuss proposals.
|
||||
|
||||
## Triaging
|
||||
|
||||
Once an issue has been opened, it is normal for there to be discussion
|
||||
around it. Some contributors may have differing opinions about the issue,
|
||||
including whether the behavior being seen is a bug or a feature. This
|
||||
discussion is part of the process and should be kept focused, helpful, and
|
||||
professional.
|
||||
|
||||
The objective of helping with triaging issues is to help reduce the issue
|
||||
backlog and keep the issue tracker healthy, while enabling newcomers another
|
||||
meaningful way to get engaged and contribute.
|
||||
|
||||
### Acknowledge
|
||||
|
||||
Acknowledge the human. This is meant actively, such as giving a welcome, or
|
||||
thanks for a detailed report, or any other greeting that makes the person feel
|
||||
that their contribution (issues are contributions!) is valued. It also is meant
|
||||
to be internalized, and be sure to always [treat the person kindly][COC]
|
||||
throughout the rest of the steps of triaging.
|
||||
|
||||
### Ask for more info
|
||||
|
||||
Frequently, we need more information than was originally provided to fully
|
||||
evaluate an issue.
|
||||
|
||||
If it is a bug report, ask follow up questions that help us get a [minimum
|
||||
reproducible example][MRE]. This may take several round-trip questions. Once
|
||||
all the details are gathered, it may be helpful to edit the original issue text
|
||||
to include them all.
|
||||
|
||||
### Categorize
|
||||
|
||||
Once enough information has been gathered, the issue should be categorized
|
||||
with [labels][#labels]. Ideally, most issues should be labelled with an area,
|
||||
effort, and severity. An issue _can_ have multiple areas, pick what fits. There
|
||||
should be only one severity, and the descriptions of each should help to pick
|
||||
the right one. The hardest label to select is "effort". If after reading the
|
||||
descriptions of each effort level, you're still unsure, you can ping a
|
||||
maintainer to pick one.
|
||||
|
||||
### Adjust the title
|
||||
|
||||
An optional step when triaging is to adjust the title once more information is
|
||||
known. Sometimes an issue starts as a question, and through discussion, it
|
||||
turns out to be a feature request, or a bug report. In those cases, the title
|
||||
should be changed from a question, and the title should be a succinct action to
|
||||
be taken. For example, a question about an non-existent configuration option
|
||||
may be reworded to "Add option to Client to do Zed".
|
||||
|
||||
### Mentoring
|
||||
|
||||
The last part of triaging is to try to make the issue a learning experience.
|
||||
After a discussion with the reporter, it would be good to ask if they are now
|
||||
interested in submitting the change described in the issue.
|
||||
|
||||
Otherwise, it would be best to leave the issue with a series of steps for
|
||||
anyone else to try to write the change. That could be pointing out that a
|
||||
design proposal is needed, addressing certain points. Or, if the required
|
||||
changes are mostly know, a list of links to modules and functions where code
|
||||
needs to be changed, and to what. That way we mentor newcomers to become
|
||||
successful contributors of new [pull requests][PRs].
|
||||
|
||||
## Labels
|
||||
|
||||
Issues are organized with a set of labels. Most labels follow a system of being prefixed by a "type".
|
||||
@@ -47,3 +109,5 @@ The severity marks how _severe_ the issue is. Note this isn't "importance" or "p
|
||||
- **S-refactor**: improve internal code to help readability and maintenance.
|
||||
|
||||
[issues]: https://github.com/hyperium/hyper/issues
|
||||
[COC]: ./CODE_OF_CONDUCT.md
|
||||
[PRs]: ./PULL_REQUESTS.md
|
||||
|
||||
30
docs/MAINTAINERS.md
Normal file
30
docs/MAINTAINERS.md
Normal file
@@ -0,0 +1,30 @@
|
||||
# The People
|
||||
|
||||
To see what these roles do, and how to become one, look at [GOVERNANCE](./GOVERNANCED.md).
|
||||
|
||||
## Triagers
|
||||
|
||||
## Collaborators
|
||||
|
||||
- Sean McArthur (@seanmonstar)
|
||||
- Steven Fackler (@sfackler)
|
||||
- Oliver Gould (@olix0r)
|
||||
- Eliza Weisman (@hawkw)
|
||||
- Lucio Franco (@LucioFranco)
|
||||
- Anthony Ramine (@nox)
|
||||
- David Pedersen (@davidpdrsn)
|
||||
- Adam Foltzer (@acfoltzer)
|
||||
|
||||
<details>
|
||||
<summary>Emeriti</summary>
|
||||
|
||||
### Collaborator emeriti
|
||||
|
||||
- Jonathan Reem (@reem)
|
||||
- Carl Lerche (@carllerche)
|
||||
|
||||
</details>
|
||||
|
||||
## Maintainers
|
||||
|
||||
- Sean McArthur (@seanmonstar)
|
||||
@@ -6,4 +6,4 @@ hyper. It is possible that an older compiler can work, but that is not
|
||||
guaranteed. We try to increase the MSRV responsibly, only when a significant
|
||||
new feature is needed.
|
||||
|
||||
The current MSRV is: **1.49**.
|
||||
The current MSRV is: **1.56**.
|
||||
|
||||
49
docs/PULL_REQUESTS.md
Normal file
49
docs/PULL_REQUESTS.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# Pull Requests
|
||||
|
||||
Pull requests are the way to submit changes to the hyper repository.
|
||||
|
||||
## Submitting a Pull Request
|
||||
|
||||
In most cases, it a good idea to discuss a potential change in an
|
||||
[issue](ISSUES.md). This will allow other contributors to provide guidance and
|
||||
feedback _before_ significant code work is done, and can increase the
|
||||
likelihood of getting the pull request merged.
|
||||
|
||||
### Tests
|
||||
|
||||
If the change being proposed alters code (as opposed to only documentation for
|
||||
example), it is either adding new functionality to hyper or it is fixing
|
||||
existing, broken functionality. In both of these cases, the pull request should
|
||||
include one or more tests to ensure that hyper does not regress in the future.
|
||||
|
||||
### Commits
|
||||
|
||||
Once code, tests, and documentation have been written, a commit needs to be
|
||||
made. Following the [commit guidelines](COMMITS.md) will help with the review
|
||||
process by making your change easier to understand, and makes it easier for
|
||||
hyper to produce a valuable changelog with each release.
|
||||
|
||||
However, if your message doesn't perfectly match the guidelines, **do not
|
||||
worry!** The person that eventually merges can easily fixup the message at that
|
||||
time.
|
||||
|
||||
### Opening the Pull Request
|
||||
|
||||
From within GitHub, open a new pull request from your personal branch.
|
||||
|
||||
Once opened, pull requests are usually reviewed within a few days.
|
||||
|
||||
### Discuss and Update
|
||||
|
||||
You will probably get feedback or requests for changes to your Pull Request.
|
||||
This is a big part of the submission process so don't be discouraged! Some
|
||||
contributors may sign off on the Pull Request right away, others may have more
|
||||
detailed comments or feedback. This is a necessary part of the process in order
|
||||
to evaluate whether the changes are correct and necessary.
|
||||
|
||||
Any community member can review a PR and you might get conflicting feedback.
|
||||
Keep an eye out for comments from code owners to provide guidance on
|
||||
conflicting feedback.
|
||||
|
||||
You don't need to close the PR and create a new one to address feedback. You
|
||||
may simply push new commits to the existing branch.
|
||||
404
docs/ROADMAP.md
Normal file
404
docs/ROADMAP.md
Normal file
@@ -0,0 +1,404 @@
|
||||
# hyper 1.0 Roadmap
|
||||
|
||||
## Goal
|
||||
|
||||
Align current hyper to the [hyper VISION][VISION].
|
||||
|
||||
The VISION outlines a decision-making framework, use-cases, and general shape
|
||||
of hyper. This roadmap describes the currently known problems with hyper, and
|
||||
then shows what changes are needed to make hyper 1.0 look more like what is in
|
||||
the VISION.
|
||||
|
||||
## Known Issues
|
||||
|
||||
|
||||
> **Note**: These known issues are as of hyper v0.14.x. After v1.0 is released,
|
||||
ideally these issues will have been solved. Keeping this history may be helpful
|
||||
to Future Us, though.
|
||||
|
||||
### Higher-level Client and Server problems
|
||||
|
||||
Both the higher-level `Client` and `Server` types have stability concerns.
|
||||
|
||||
For the `hyper::Server`:
|
||||
|
||||
- The `Accept` trait is complex, and too easy to get wrong. If used with TLS, a slow TLS handshake
|
||||
can affect all other new connections waiting for it to finish.
|
||||
- The `MakeService<&IO>` is confusing. The bounds are an assault on the eyes.
|
||||
- The `MakeService` API doesn't allow to easily annotate the HTTP connection with `tracing`.
|
||||
- Graceful shutdown doesn't give enough control.
|
||||
|
||||
|
||||
It's more common for people to simply use `hyper::server::conn` at this point,
|
||||
than to bother with the `hyper::Server`.
|
||||
|
||||
While the `hyper::Client` is much easier to use, problems still exist:
|
||||
|
||||
- The whole `Connect` design isn't stable.
|
||||
- ALPN and proxies can provide surprising extra configuration of connections.
|
||||
- Some `Connect` implementations may wish to view the path, in addition to the scheme, host, and port.
|
||||
- Wants `runtime` feature
|
||||
- The Pool could be made more general or composable. At the same time, more customization is
|
||||
desired, and it's not clear
|
||||
how to expose it yet.
|
||||
|
||||
|
||||
### Runtime woes
|
||||
|
||||
hyper has been able to support different runtimes, but it has sometimes awkward
|
||||
default support for Tokio.
|
||||
|
||||
- The `runtime` cargo-feature isn't additive
|
||||
- Built-in Tokio support can be confusing
|
||||
- Executors and Timers
|
||||
- The `runtime` feature currently enables a few options that require a timer, such as timeouts and
|
||||
keepalive intervals. It implicitly relies on Tokio's timer context. This can be quite confusing.
|
||||
- IO traits
|
||||
- Should we publicly depend on Tokio's traits?
|
||||
- `futures-io`?
|
||||
- Definitely nope.
|
||||
- Not stable. (0.3?)
|
||||
- No uninitialized memory.
|
||||
- Eventual `std` traits?
|
||||
- They've been in design for years.
|
||||
- We cannot base our schedule on them.
|
||||
- When they are stable, we can:
|
||||
- Provide a bridge in `hyper-util`.
|
||||
- Consider a 2.0 of hyper.
|
||||
- Define our own traits, provide util wrappers?
|
||||
|
||||
### Forwards-compatibility
|
||||
|
||||
There's a concern about forwards-compatibility. We want to be able to add
|
||||
support for new HTTP features without needing a new major version. While most
|
||||
of `http` and `hyper` are prepared for that, there's two potential problems.
|
||||
|
||||
- New frames on an HTTP stream (body)
|
||||
- Receiving a new frame type would require a new trait method
|
||||
- There's no way to implement a "receive unknown frame" that hyper doesn't know about.
|
||||
- Sending an unknown frame type would be even harder.
|
||||
- Besides being able to pass an "unknown" type through the trait, the user would need to be
|
||||
able to describe how that frame is encoded in HTTP/2/3.
|
||||
- New HTTP versions
|
||||
- HTTP/3 will require a new transport abstraction. It's not as simple as just using some
|
||||
`impl AsyncRead + AsyncWrite`. While HTTP/2 bundled the concept of stream creation internally,
|
||||
and thus could be managed wholly on top of a read-write transport, HTTP/3 is different. Stream
|
||||
creation is shifted to the QUIC protocol, and HTTP/3 needs to be able to use that directly.
|
||||
- This means the existing `Connection` types for both client and server will not be able to
|
||||
accept a QUIC transport so we can add HTTP/3 support.
|
||||
|
||||
### Errors
|
||||
|
||||
It's not easy to match for specific errors.
|
||||
|
||||
The `Error::source()` can leak an internal dependency. For example, a
|
||||
`hyper::Error` may wrap an `h2::Error`. Users can downcast the source at
|
||||
runtime, and hyper internally changing the version of its `h2` dependency can
|
||||
cause runtime breakage for users.
|
||||
|
||||
Formatting errors is in conflict with the current expected norm. The
|
||||
`fmt::Display` implementation for `hyper::Error` currently prints its own
|
||||
message, and then prints the message of any wrapped source error. The Errors
|
||||
Working Group currently recommends that errors only print their own message
|
||||
(link?). This conflict means that error "reporters", which crawl a source chain
|
||||
and print each error, has a lot of duplicated information.
|
||||
|
||||
```
|
||||
error fetching website: error trying to connect: tcp connect error: Connection refused (os error 61)
|
||||
tcp connect error: Connection refused (os error 61)
|
||||
Connection refused (os error 61)
|
||||
```
|
||||
|
||||
While there is a good reason for why hyper's `Error` types do this, at the very
|
||||
least, it _is_ unfortunate.
|
||||
|
||||
### You call hyper, or hyper calls you?
|
||||
|
||||
> Note: this problem space, of who calls whom, will be explored more deeply in
|
||||
> a future article.
|
||||
|
||||
At times, it's been wondered whether hyper should call user code, or if user
|
||||
code should call hyper. For instance, should a `Service` be called with a
|
||||
request when the connection receives one, or should the user always poll for
|
||||
the next request.
|
||||
|
||||
There's a similar question around sending a message body. Should hyper ask the
|
||||
body for more data to write, or should the user call a `write` method directly?
|
||||
|
||||
These both get at a root topic about [write
|
||||
observability](https://github.com/hyperium/hyper/issues/2181). How do you know
|
||||
when a response, or when body data, has been written successfully? This is
|
||||
desirable for metrics, or for triggering other side-effects.
|
||||
|
||||
The `Service` trait also has some other frequently mentioned issues. Does
|
||||
`poll_ready` pull its complexity weight for servers? What about returning
|
||||
errors, what does that mean? Ideally users would turn all errors into
|
||||
appropriate `http::Response`s. But in HTTP/2 and beyond, stream errors are
|
||||
different from HTTP Server Error responses. Could the `Service::Error` type do
|
||||
more to encourage best practices?
|
||||
|
||||
## Design
|
||||
|
||||
The goal is to get hyper closer to the [VISION][], using that to determine the
|
||||
best way to solve the known issues above. The main thrust of the proposed
|
||||
changes are to make hyper more **Flexible** and stable.
|
||||
|
||||
In order to keep hyper **Understandable**, however, the proposed changes *must*
|
||||
be accompanied by providing utilities that solve the common usage patterns,
|
||||
documentation explaining how to use the more flexible pieces, and guides on how
|
||||
to reach for the `hyper-util`ity belt.
|
||||
|
||||
The majority of the changes are smaller and can be contained to the *Public
|
||||
API* section, since they usually only apply to a single module or type. But the
|
||||
biggest changes are explained in detail here.
|
||||
|
||||
### Split per HTTP version
|
||||
|
||||
The existing `Connection` types, both for the client and server, abstract over
|
||||
HTTP version by requiring a generic `AsyncRead + AsyncWrite` transport type.
|
||||
But as we figure out HTTP/3, that needs to change. So to prepare now, the
|
||||
`Connection` types will be split up.
|
||||
|
||||
For example, there will now be `hyper::server::conn::http1::Connection` and
|
||||
`hyper::server::conn::http2::Connection` types.
|
||||
|
||||
These specific types will still have a very similar looking API that, as the
|
||||
VISION describes, provides **Correct** connection management as it pertains to
|
||||
HTTP.
|
||||
|
||||
There will be still be a type to wrap the different versions. It will no longer
|
||||
be generic over the transport type, to prepare for being able to wrap HTTP/3
|
||||
connections. Exactly how it will wrap, either by using internal trait objects,
|
||||
or an `enum Either` style, or using a `trait Connection` that each type
|
||||
implements, is something to be determined. It's likely that this "auto" type
|
||||
will start in `hyper-util`.
|
||||
|
||||
### Focus on the `Connection` level
|
||||
|
||||
As mentioned in the *Known Issues*, the higher-level `Client` and `Server` have
|
||||
stability and complexity problems. Therefore, for hyper 1.0, the main API will
|
||||
focus on the "lower-level" connection types. The `Client` and `Server` helpers
|
||||
will be moved to `hyper-util`.
|
||||
|
||||
## Public API
|
||||
|
||||
### body
|
||||
|
||||
The `Body` struct is removed. Its internal "variants" are [separated into
|
||||
distinct types](https://github.com/hyperium/hyper/issues/2345), and can start
|
||||
in either `hyper-util` or `http-body-util`.
|
||||
|
||||
The exported trait `HttpBody` is renamed to `Body`.
|
||||
|
||||
A single `Body` implementation in `hyper` is the one provided by receiving
|
||||
client responses and server requests. It has the name `Streaming`.
|
||||
|
||||
> **Unresolved**: Other names can be considered during implementation. Another
|
||||
> option is to not publicly name the implementation, but return `Response<impl
|
||||
Body>`s.
|
||||
|
||||
The `Body` trait will be experimented on to see about making it possible to
|
||||
return more frame types beyonds just data and trailers.
|
||||
|
||||
> **Unresolved**: What exactly this looks like will only be known after
|
||||
> experimentation.
|
||||
|
||||
### client
|
||||
|
||||
The high-level `hyper::Client` will be removed, along with the
|
||||
`hyper::client::connect` module. They will be explored more in `hyper-util`.
|
||||
|
||||
As described in *Design*, the `client::conn` module will gain `http1` and
|
||||
`http2` sub-modules, providing per-version `SendRequest`, `Connection`, and
|
||||
`Builder` structs. An `auto` version can be explored in `hyper-util`.
|
||||
|
||||
### error
|
||||
|
||||
The `hyper::Error` struct remains in place.
|
||||
|
||||
All errors returned from `Error::source()` are made opaque. They are wrapped an
|
||||
internal `Opaque` newtype that still allows printing, but prevents downcasting
|
||||
to the internal dependency.
|
||||
|
||||
A new `hyper::error::Code` struct is defined. It is an opaque struct, with
|
||||
associated constants defining various code variants.
|
||||
|
||||
> Alternative: define a non-exhaustive enum. It's not clear that this is
|
||||
> definitely better, though. Keeping it an opaque struct means we can add
|
||||
> secondary parts to the code in the future, or add bit flags, or similar
|
||||
> extensions.
|
||||
|
||||
The purpose of `Code` is to provide an abstraction over the kind of error that
|
||||
is encountered. The `Code` could be some behavior noticed inside hyper, such as
|
||||
an incomplete HTTP message. Or it can be "translated" from the underlying
|
||||
protocol, if it defines protocol level errors. For example, an
|
||||
`h2::Reason::CANCEL`.
|
||||
|
||||
### rt
|
||||
|
||||
The `Executor` trait stays in here.
|
||||
|
||||
Define a new trait `Timer`, which describes a way for users to provide a source
|
||||
of sleeping/timeout futures. Similar to `Executor`, a new generic is added to
|
||||
connection builders to provide a `Timer`.
|
||||
|
||||
### server
|
||||
|
||||
The higher-level `hyper::Server` struct, its related `Builder`, and the
|
||||
`Accept` trait are all removed.
|
||||
|
||||
The `AddrStream` struct will be completely removed, as it provides no value but
|
||||
causes binary bloat.
|
||||
|
||||
Similar to `client`, and as describe in the *Design*, the `conn` modules will
|
||||
be expanded to support `http1` and `http2` submodules. An `auto` version can be
|
||||
explored in `hyper-util`.
|
||||
|
||||
### service
|
||||
|
||||
A vendored and simplified `Service` trait will be explored.
|
||||
|
||||
The error type for `Service`s used for a server will explore having the return
|
||||
type changed from any error to one that can become a `hyper::error::Code`.
|
||||
|
||||
> **Unresolved**: Both of the above points are not set in stone. We will
|
||||
> explore and decide if they are the best outcome during development.
|
||||
|
||||
The `MakeService` pieces will be removed.
|
||||
|
||||
### Cargo Features
|
||||
|
||||
Remove the `stream` feature. The `Stream` trait is not stable, and we cannot
|
||||
depend on an unstable API.
|
||||
|
||||
Remove the `tcp` and `runtime` features. The automatic executor and timer parts
|
||||
are handled by providing implementations of `Executor` and `Timer`. The
|
||||
`connect` and `Accept` parts are also moving to `hyper-util`.
|
||||
|
||||
### Public Dependencies
|
||||
|
||||
- `http`
|
||||
- `http-body`
|
||||
- `bytes`
|
||||
|
||||
Cannot be public while "unstable":
|
||||
|
||||
- `tracing`
|
||||
|
||||
## `hyper-util`
|
||||
|
||||
|
||||
### body
|
||||
|
||||
A channel implementation of `Body` that has an API to know when the data has
|
||||
been successfully written is provided in `hyper_util::body::channel`.
|
||||
|
||||
### client
|
||||
|
||||
A `Pool` struct that implements `Service` is provided. It fills a similar role
|
||||
as the previous `hyper::Client`.
|
||||
|
||||
> **Note**: The `Pool` might be something that goes into the `tower` crate
|
||||
> instead. Or it might stay here as a slightly more specialized racing-connect
|
||||
> pool. We'll find out as we go.
|
||||
|
||||
A `connect` submodule that mostly mirrors the existing `hyper::client::connect`
|
||||
module is moved here. Connectors can be used as a source to provide `Service`s
|
||||
used by the `Pool`.
|
||||
|
||||
### rt
|
||||
|
||||
We can provide Tokio-backed implementations of `Executor` and `Timer`.
|
||||
|
||||
### server
|
||||
|
||||
A `GracefulShutdown` helper is provided, to allow for similar style of graceful
|
||||
shutdown as the previous `hyper::Server` did, but with better control.
|
||||
|
||||
# Appendix
|
||||
|
||||
## Unresolved Questions
|
||||
|
||||
There are some parts of the proposal which are not fully resolved. They are
|
||||
mentioned in Design and API sections above, but also collected here for easy
|
||||
finding. While they all have _plans_, they are more exploratory parts of the
|
||||
API, and thus they have a higher possibility of changing as we implement them.
|
||||
|
||||
The goal is to have these questions resolved and removed from the document by
|
||||
the time there is a [Release Candidate][timeline].
|
||||
|
||||
### Should there be `hyper::io` traits?
|
||||
|
||||
Depending on `tokio` just for `AsyncRead` and `AsyncWrite` is convenient, but
|
||||
can be confusing for users integrating hyper with other runtimes. It also ties
|
||||
our version directly to Tokio. We can consider having vendored traits, and
|
||||
providing Tokio wrappers in `hyper-util`.
|
||||
|
||||
### Should returned body types be `impl Body`?
|
||||
|
||||
### How could the `Body` trait prepare for unknown frames?
|
||||
|
||||
We will experiment with this, and keep track of those experiments in a
|
||||
dedicated issue. It might be possible to use something like this:
|
||||
|
||||
```rust
|
||||
pub trait Body {
|
||||
type Data;
|
||||
fn poll_frame(..) -> Result<Option<Frame<Self::Data>>>;
|
||||
}
|
||||
|
||||
pub struct Frame<T>(Kind<T>);
|
||||
|
||||
enum Kind<T> {
|
||||
Data(T),
|
||||
Trailers(HeaderMap),
|
||||
Unknown(Box<dyn FrameThingy>),
|
||||
}
|
||||
```
|
||||
|
||||
### Should there be a simplified `hyper::Service` trait, or should hyper depend on `tower-service`?
|
||||
|
||||
- There's still a few uncertain decisions around tower, such as if it should be
|
||||
changed to `async fn call`, and if `poll_ready` is the best way to handle
|
||||
backpressure.
|
||||
- It's not clear that the backpressure is something needed at the `Server`
|
||||
boundary, thus meaning we should remove `poll_ready` from hyper.
|
||||
- It's not 100% clear if we should keep the service pattern, or use a
|
||||
pull-based API. This will be explored in a future blog post.
|
||||
|
||||
## FAQ
|
||||
|
||||
### Why did you pick _that_ name? Why not this other better name?
|
||||
|
||||
Naming is hard. We certainly should solve it, but discussion for particular
|
||||
names for structs and traits should be scoped to the specific issues. This
|
||||
document is to define the shape of the library API.
|
||||
|
||||
### Should I publicly depend on `hyper-util`?
|
||||
|
||||
The `hyper-util` crate will not reach 1.0 when `hyper` does. Some types and
|
||||
traits are being moved to `hyper-util`. As with any pre-1.0 crate, you _can_
|
||||
publicly depend on it, but it is explicitly less stable.
|
||||
|
||||
In most cases, it's recommended to not publicly expose your dependency on
|
||||
`hyper-util`. If you depend on a trait, such as used by the moved higher-level
|
||||
`Client` or `Server`, it may be better for your users to define your own
|
||||
abstraction, and then make an internal adapter.
|
||||
|
||||
### Isn't this making hyper harder?
|
||||
|
||||
We are making hyper more **flexible**. As noted in the [VISION][], most use
|
||||
cases of hyper require it to be flexible. That _can_ mean that the exposed API
|
||||
is lower level, and that it feels more complicated. It should still be
|
||||
**understandable**.
|
||||
|
||||
But the hyper 1.0 effort is more than just the single `hyper` crate. Many
|
||||
useful helpers will be migrated to a `hyper-util` crate, and likely improved in
|
||||
the process. The [timeline][] also points out that we will have a significant
|
||||
documentation push. While the flexible pieces will be in hyper to compose how
|
||||
they need, we will also write guides for the [hyper.rs][] showing people how to
|
||||
accomplish the most common tasks.
|
||||
|
||||
[timeline]: https://seanmonstar.com/post/676912131372875776/hyper-10-timeline
|
||||
[VISION]: https://github.com/hyperium/hyper/pull/2772
|
||||
[hyper.rs]: https://hyper.rs
|
||||
100
docs/TENETS.md
Normal file
100
docs/TENETS.md
Normal file
@@ -0,0 +1,100 @@
|
||||
# Charter
|
||||
|
||||
> hyper is a protective and efficient HTTP library for all.
|
||||
|
||||
# Tenets
|
||||
|
||||
Tenets are guiding principles. They guide how decisions are made for the whole
|
||||
project. Ideally, we do all of them all the time. In some cases, though, we may
|
||||
be forced to decide between slightly penalizing one goal or another. In that
|
||||
case, we tend to support those goals that come earlier in the list over those
|
||||
that come later (but every case is different).
|
||||
|
||||
## 0. Open
|
||||
|
||||
hyper is open source, always. The success of hyper depends on the health of the
|
||||
community building and using it. All contributions are in the open. We don't
|
||||
maintain private versions, and don't include features that aren't useful to
|
||||
others.
|
||||
|
||||
[We prioritize kindness][CONDUCT], compassion and empathy towards all
|
||||
contributors. Technical skill is not a substitute for human decency.
|
||||
|
||||
[CONDUCT]: https://github.com/hyperium/hyper/blob/master/docs/CODE_OF_CONDUCT.md
|
||||
|
||||
### Examples
|
||||
|
||||
It's not usually hard for an open source library to stay open and also meet its
|
||||
other priorities. Here's some instances where being **Open** would be more
|
||||
important than **Correct** or **Fast**:
|
||||
|
||||
- Say an individual were to bring forward a contribution that makes hyper more
|
||||
correct, or faster, perhaps fixing some serious bug. But in doing so, they
|
||||
also insulted people, harassed other contributors or users, or shamed
|
||||
everyone for the previous code. They felt their contribution was "invaluable".
|
||||
We would not accept such a contribution, instead banning the user and
|
||||
rewriting the code amongst the kind collaborators of the project.
|
||||
|
||||
- Say someone brings a contribution that adds a new feature useful for
|
||||
performance or correctness, but their work accomplishes this by integrating
|
||||
hyper with a proprietary library. We would not accept such a contribution,
|
||||
because we don't want such a feature limited only to those users willing to
|
||||
compromise openness, and we don't want to bifurcate the ecosystem between those
|
||||
who make that compromise and those who don't.
|
||||
|
||||
## 1. Correct
|
||||
|
||||
hyper is a memory safe and precise implementation of the HTTP specification.
|
||||
Memory safety is vital in a core Internet technology. Following the HTTP
|
||||
specifications correctly protects users. It makes the software durable to the
|
||||
“real world”. Where feasible, hyper enforces correct usage.
|
||||
|
||||
This is more than just "don't write bugs". hyper actively protects the user.
|
||||
|
||||
### Examples
|
||||
|
||||
- Even though we follow the **HTTP/\*** specs, hyper doesn't blindly implement
|
||||
everything without considering if it is safe to do so.
|
||||
|
||||
## 2. Fast
|
||||
|
||||
A fast experience delights users. A faster network library means a faster
|
||||
application, resulting in delighting our users’ users. Whether with one request,
|
||||
or millions.
|
||||
|
||||
Being _fast_ means we improve throughput, drive down CPU usage, and improve
|
||||
sustainability.
|
||||
|
||||
Fast _enough_. We don't sacrifice sanity for speed.
|
||||
|
||||
## 3. HTTP/*
|
||||
|
||||
hyper is specifically focused on HTTP. Supporting new HTTP versions is in scope,
|
||||
but supporting separate protocols is not.
|
||||
|
||||
This also defines what the abstraction layer is: the API is designed around
|
||||
sending and receiving HTTP messages.
|
||||
|
||||
## 4. Flexible
|
||||
|
||||
hyper enables as many usecases as possible. It has no opinion on application
|
||||
structure, and makes few assumptions about its environment. This includes being
|
||||
portable to different operating systems.
|
||||
|
||||
### Examples
|
||||
|
||||
- While we choose safer defaults to be **Correct**, hyper includes options to
|
||||
_allow_ different behavior, when the user requires them.
|
||||
- Providing choice usually makes things more complex, so being **Flexible** does
|
||||
mean it's less _easy_. That can sometimes conflict with simplest way of making
|
||||
hyper **Understandable**.
|
||||
|
||||
## 5. Understandable
|
||||
|
||||
hyper is [no more complicated than it has to
|
||||
be](https://en.wikipedia.org/wiki/Occam%27s_razor). HTTP is not simple. It may
|
||||
not be as "easy" as 1-line to do everything, but it shouldn't be "hard" to find
|
||||
the answers.
|
||||
|
||||
From logical and misuse-resistant APIs, to stellar documentation, to transparent
|
||||
metrics.
|
||||
230
docs/VISION.md
Normal file
230
docs/VISION.md
Normal file
@@ -0,0 +1,230 @@
|
||||
# hyper Vision
|
||||
|
||||
## Purpose
|
||||
|
||||
This is an overview of what the shape of hyper looks like, but also somewhat
|
||||
zoomed out, so that the _vision_ can survive while the exact minute details
|
||||
might shift and change over time.
|
||||
|
||||
### Charter
|
||||
|
||||
> hyper is a protective and efficient HTTP library for all.
|
||||
|
||||
### Tenets
|
||||
|
||||
Tenets are guiding principles. They guide how decisions are made for the whole
|
||||
project. Ideally, we do all of them all the time. In some cases, though, we may
|
||||
be forced to decide between slightly penalizing one goal or another. In that
|
||||
case, we tend to support those goals that come earlier in the list over those
|
||||
that come later (but every case is different).
|
||||
|
||||
0. Open
|
||||
1. Correct
|
||||
2. Fast
|
||||
3. HTTP/\*
|
||||
4. Flexible
|
||||
5. Understandable
|
||||
|
||||
There's a lot more detail about each in [TENETS](./TENETS.md).
|
||||
|
||||
## Use Cases
|
||||
|
||||
Who are the *users* of hyper? How would they use hyper?
|
||||
|
||||
### Low-Level Client Library (curl, reqwest, aws-sdk)
|
||||
|
||||
These client libraries care that hyper is **Flexible**, since they are
|
||||
expressing their own opinion on how a more-featured HTTP client should act.
|
||||
This includes opinions on connection establishment, management, pooling, HTTP
|
||||
version options, and even runtimes.
|
||||
|
||||
curl's main reason for using hyper is that it is **Safe**.
|
||||
|
||||
### Web Server Frameworks (deno, axum)
|
||||
|
||||
These are using hyper's server feature to expose a different, higher-level API
|
||||
to users. Besides the obvious requirements, these require that hyper is
|
||||
**Fast**. Servers are costly, handling more requests faster is important to
|
||||
them.
|
||||
|
||||
That hyper is **Flexible** is also important, in that it needs to be flexible
|
||||
enough for them to build a server framework, and allow them to express their
|
||||
own opinions about API to their users.
|
||||
|
||||
### Services and Proxies (linkerd, cloudflare, fastly)
|
||||
|
||||
These are using hyper directly, likely both the client and server, in order to
|
||||
build efficient and powerful services, applications, and tools for their end
|
||||
users. They care greatly that hyper is **Correct**, since web traffic can
|
||||
stretch the limits of what is valid HTTP, and exercise less-common parts of the
|
||||
specifications.
|
||||
|
||||
They also require hyper to be **Fast**, for similar reasons that the web server
|
||||
frameworks do.
|
||||
|
||||
### New Rust Web Developers
|
||||
|
||||
These are developers who are either new to Rust, or new to web servers, and
|
||||
have reached for hyper to start with.
|
||||
|
||||
It's likely that these users don't have strong opinions about how an HTTP
|
||||
server or client should work, just that it _should_ handle all the things they
|
||||
normally assume it would. For these users, it would be best to quickly help
|
||||
them compare their own expectations with hyper's capabilities, and may
|
||||
suggest reaching for higher-level, _easier_ libraries instead.
|
||||
|
||||
Those that stick around after that recommendation are users that wish both to
|
||||
learn at a lower level, and to pick and choose what batteries they plug in to
|
||||
hyper as they move along. While they do care about the other tenets, that hyper
|
||||
is **Understandable** is of extra importance to them.
|
||||
|
||||
## The Library
|
||||
|
||||
So with all that context in mind, what does hyper, the library, actually look
|
||||
like? This doesn't highlight what _is_ and _isn't_ present. What currently
|
||||
needs to change to reach this vision is left to individual version roadmaps.
|
||||
|
||||
### Layers
|
||||
|
||||
In all cases, a user brings their own runtime and IO to work with hyper. The IO
|
||||
is provided to hyper, and hyper acts on top of it. hyper returns `Future`s that
|
||||
the user then decides how to poll, likely involving their runtime options.
|
||||
|
||||

|
||||
|
||||
|
||||
#### Protocol Codecs
|
||||
|
||||
hyper has dedicated codecs for the major HTTP versions. Each is internally
|
||||
designed to be **Correct** and **Fast** when it comes to encoding and decoding.
|
||||
|
||||
The individual codecs may be implemented as sub-crates, with a less-stable
|
||||
promise, to support the **Flexible** needs of some users who wish to build
|
||||
their own connection management, or customize encoding and decoding beyond what
|
||||
is officially supported.
|
||||
|
||||
#### Connection State Management
|
||||
|
||||
A **Correct** implementation includes more than just enforcing certain
|
||||
characters when encoding and decoding. Order of frames, and flags in certain
|
||||
frames can affect the state of the connection. Some examples of things enforced
|
||||
at this layer:
|
||||
|
||||
- If a message has a `content-length`, enforce only that many bytes are read or
|
||||
written.
|
||||
- Reading a `Response` before a `Request` is even written implies a mismatched
|
||||
reply that should be interpreted as an error.
|
||||
- The presence of some headers, such as `Connection: close`, or the absence of
|
||||
others, such as `content-length` and `transfer-encoding`, can mean that the
|
||||
connection should terminate after the current message.
|
||||
- HTTP/2 and HTTP/3 may send connection-level frames that don't pertain to any
|
||||
specific transaction, and must be read and handled regardless of if a user is
|
||||
currently checking for a message.
|
||||
|
||||
#### HTTP Role and Version Abstraction
|
||||
|
||||
This is the public API layer. Methods exposed are around sending and receiving
|
||||
`http::Request`s and `http::Response`s, not around framing specifics of the
|
||||
different versions. These are built around a client or server `Connection`
|
||||
interface.
|
||||
|
||||
By exposing this layer publicly, we take care of the **Correct** tenet, by not
|
||||
forcing the user to send the specific frames themselves. The API should be
|
||||
designed in a way that a user cannot easily (if at all) create an _incorrect_
|
||||
HTTP connection.
|
||||
|
||||
Motivated by the **Flexible** tenet, there _are_ version-specific options that
|
||||
can be configured at this level, and version-specific functionality can usually
|
||||
be handled via `http::Extensions`.
|
||||
|
||||
### Not quite stable, but utile (useful)
|
||||
|
||||
Beyond what is directly in the hyper crate, there are useful (utile) parts that
|
||||
may not meet hyper's stability promise. Developing, experimenting, and exposing
|
||||
those parts is the purpose of the `hyper-util` crate. That crate does not have
|
||||
the same stability level as hyper. However, the goal is that things that other
|
||||
libraries might want to expose as a public dependency do not live in
|
||||
`hyper-util` forever, but rather stabilize and get promoted into `hyper`.
|
||||
|
||||
Exactly what gets put into `hyper-util` presently is kept in the roadmap
|
||||
documents.
|
||||
|
||||
### Stability Promise
|
||||
|
||||
What even is hyper's stability promise? Does it mean we are "done"? No. Will we
|
||||
ever make breaking changes again? Probably. We'll still follow the [semantic
|
||||
versioning](https://semver.org).
|
||||
|
||||
Prior to 1.0, hyper has already only done breaking changes once a year. So 1
|
||||
year isn't much of a promise. We'll have significant more use and understanding
|
||||
after a few years, and that could prompt some redesign.
|
||||
|
||||
As of this writing, we'll promise that _major_ versions of hyper are stable for
|
||||
3 years. New features will come out in _minor_ versions frequently. If it is
|
||||
determined necessary to make breaking changes to the API, we'll save them for
|
||||
after the 3 years.
|
||||
|
||||
hyper also establishes a Minimum Supported Rust Version (MSRV). hyper will
|
||||
support Rust versions at least 6 months old. If a new Rust version is released
|
||||
with a feature hyper wishes to use, we won't do so until at least 6 months
|
||||
afterwards. hyper will only ever require a new Rust version as a _minor_
|
||||
release (1.x), not as a patch (1.x.y).
|
||||
|
||||
## Security
|
||||
|
||||
The security of hyper is a large part of what makes hyper _protective_. We make
|
||||
hyper secure via the combined efforts of being **Correct**, focusing on
|
||||
**HTTP/\***, and making it all **Understandable**.
|
||||
|
||||
### Memory Safety
|
||||
|
||||
Being **Correct** requires that hyper be memory-safe. Using the Rust language
|
||||
gets us most of the way there. But there is the ability to write `unsafe`
|
||||
Rust. Does being **Correct** mean that we can _never_ write `unsafe` code
|
||||
anywhere? Even if it helps make hyper **Fast**? We can, carefully.
|
||||
|
||||
How do we balance the two, so that hyper is secure?
|
||||
|
||||
hyper prefers not to have large modules of intertwined `unsafe` code. hyper
|
||||
does allow small `unsafe` blocks, no more than a few lines, where it's easier
|
||||
to verify that the `unsafe` code was written **Correctly**.
|
||||
|
||||
### Meticulous Testing
|
||||
|
||||
hyper's test suite grows and grows. There's a lot that needs to be right.
|
||||
Parsers, encoders, state machines. When easily isolated, those pieces have
|
||||
internal unit tests. But hyper also keeps a large list of growing integration
|
||||
tests that make sure all the parts are **Correct**.
|
||||
|
||||
Making writing new tests easy is a high priority. Investing in the testing
|
||||
infrastructure is a proven way to make sure hyper stays **Correct** and secure.
|
||||
|
||||
### Constant Fuzzing
|
||||
|
||||
One thing is to know specific cases to test for. But we can't know all the
|
||||
inputs or states that *might* cause a bug. That's why hyper has rounds of
|
||||
fuzzing built into its CI. It's also why hyper signs up for and uses resources
|
||||
to provide *constant*, around-the-clock fuzzing, always looking for something
|
||||
that hyper should be hardened against.
|
||||
|
||||
### Security Process
|
||||
|
||||
hyper has an outlined
|
||||
[SECURITY](https://github.com/hyperium/hyper/blob/master/SECURITY.md) process,
|
||||
so we can safely report and fix issues.
|
||||
|
||||
## Non-goals
|
||||
|
||||
After writing this up, it is easier to articulate what sorts of things many
|
||||
might associate with an HTTP library, but which are explicitly *not* for hyper.
|
||||
These are all things that definitely **out of scope**.
|
||||
|
||||
- TLS: We learned early that bundling TLS directly in hyper [has
|
||||
problems](https://github.com/hyperium/hyper/issues/985). People also have
|
||||
very strong opinions about which TLS implementation to use. The design of
|
||||
hyper allows users to bring their own TLS.
|
||||
- Routing
|
||||
- Cookies
|
||||
- Not-HTTP: WebSockets, or other protocols that are built next to HTTP. It
|
||||
should be possible to _use_ hyper to upgrade, but the actual next-protocol
|
||||
should be handled by a different library.
|
||||
4
docs/vision-arch.svg
Normal file
4
docs/vision-arch.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.7 KiB |
@@ -2,8 +2,9 @@
|
||||
#![warn(rust_2018_idioms)]
|
||||
use std::env;
|
||||
|
||||
use hyper::{body::HttpBody as _, Client};
|
||||
use hyper::{body::HttpBody as _, Body, Request};
|
||||
use tokio::io::{self, AsyncWriteExt as _};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
// A simple type alias so as to DRY.
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
@@ -33,9 +34,20 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
async fn fetch_url(url: hyper::Uri) -> Result<()> {
|
||||
let client = Client::new();
|
||||
let host = url.host().expect("uri has no host");
|
||||
let port = url.port_u16().unwrap_or(80);
|
||||
let addr = format!("{}:{}", host, port);
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
|
||||
let mut res = client.get(url).await?;
|
||||
let (mut sender, conn) = hyper::client::conn::handshake(stream).await?;
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
println!("Connection failed: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let req = Request::builder().uri(url).body(Body::empty()).unwrap();
|
||||
let mut res = sender.send_request(req).await?;
|
||||
|
||||
println!("Response: {}", res.status());
|
||||
println!("Headers: {:#?}\n", res.headers());
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
#![deny(warnings)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use hyper::body::Buf;
|
||||
use hyper::Client;
|
||||
use hyper::Body;
|
||||
use hyper::{body::Buf, Request};
|
||||
use serde::Deserialize;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
// A simple type alias so as to DRY.
|
||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
||||
@@ -22,10 +23,22 @@ async fn main() -> Result<()> {
|
||||
}
|
||||
|
||||
async fn fetch_json(url: hyper::Uri) -> Result<Vec<User>> {
|
||||
let client = Client::new();
|
||||
let host = url.host().expect("uri has no host");
|
||||
let port = url.port_u16().unwrap_or(80);
|
||||
let addr = format!("{}:{}", host, port);
|
||||
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
|
||||
let (mut sender, conn) = hyper::client::conn::handshake(stream).await?;
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
println!("Connection failed: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
// Fetch the url...
|
||||
let res = client.get(url).await?;
|
||||
let req = Request::builder().uri(url).body(Body::empty()).unwrap();
|
||||
let res = sender.send_request(req).await?;
|
||||
|
||||
// asynchronously aggregate the chunks of the body
|
||||
let body = hyper::body::aggregate(res).await?;
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use futures_util::TryStreamExt;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
/// This is our service handler. It receives a Request, routes on its
|
||||
/// path, and returns a Future of a Response.
|
||||
@@ -16,16 +19,17 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
||||
// Simply echo the body back to the client.
|
||||
(&Method::POST, "/echo") => Ok(Response::new(req.into_body())),
|
||||
|
||||
// TODO: Fix this, broken in PR #2896
|
||||
// Convert to uppercase before sending back to client using a stream.
|
||||
(&Method::POST, "/echo/uppercase") => {
|
||||
let chunk_stream = req.into_body().map_ok(|chunk| {
|
||||
chunk
|
||||
.iter()
|
||||
.map(|byte| byte.to_ascii_uppercase())
|
||||
.collect::<Vec<u8>>()
|
||||
});
|
||||
Ok(Response::new(Body::wrap_stream(chunk_stream)))
|
||||
}
|
||||
// (&Method::POST, "/echo/uppercase") => {
|
||||
// let chunk_stream = req.into_body().map_ok(|chunk| {
|
||||
// chunk
|
||||
// .iter()
|
||||
// .map(|byte| byte.to_ascii_uppercase())
|
||||
// .collect::<Vec<u8>>()
|
||||
// });
|
||||
// Ok(Response::new(Body::wrap_stream(chunk_stream)))
|
||||
// }
|
||||
|
||||
// Reverse the entire body before sending back to the client.
|
||||
//
|
||||
@@ -51,15 +55,17 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let addr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
let service = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(echo)) });
|
||||
|
||||
let server = Server::bind(&addr).serve(service);
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
server.await?;
|
||||
|
||||
Ok(())
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new().serve_connection(stream, service_fn(echo)).await {
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,51 +1,63 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Client, Error, Server};
|
||||
use hyper::{server::conn::Http, service::service_fn};
|
||||
use std::net::SocketAddr;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let in_addr = ([127, 0, 0, 1], 3001).into();
|
||||
let in_addr: SocketAddr = ([127, 0, 0, 1], 3001).into();
|
||||
let out_addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
let client_main = Client::new();
|
||||
|
||||
let out_addr_clone = out_addr.clone();
|
||||
|
||||
// The closure inside `make_service_fn` is run for each connection,
|
||||
// creating a 'service' to handle requests for that specific connection.
|
||||
let make_service = make_service_fn(move |_| {
|
||||
let client = client_main.clone();
|
||||
|
||||
async move {
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
Ok::<_, Error>(service_fn(move |mut req| {
|
||||
let uri_string = format!(
|
||||
"http://{}{}",
|
||||
out_addr_clone,
|
||||
req.uri()
|
||||
.path_and_query()
|
||||
.map(|x| x.as_str())
|
||||
.unwrap_or("/")
|
||||
);
|
||||
let uri = uri_string.parse().unwrap();
|
||||
*req.uri_mut() = uri;
|
||||
client.request(req)
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&in_addr).serve(make_service);
|
||||
let listener = TcpListener::bind(in_addr).await?;
|
||||
|
||||
println!("Listening on http://{}", in_addr);
|
||||
println!("Proxying on http://{}", out_addr);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
let service = service_fn(move |mut req| {
|
||||
let uri_string = format!(
|
||||
"http://{}{}",
|
||||
out_addr_clone,
|
||||
req.uri()
|
||||
.path_and_query()
|
||||
.map(|x| x.as_str())
|
||||
.unwrap_or("/")
|
||||
);
|
||||
let uri = uri_string.parse().unwrap();
|
||||
*req.uri_mut() = uri;
|
||||
|
||||
let host = req.uri().host().expect("uri has no host");
|
||||
let port = req.uri().port_u16().unwrap_or(80);
|
||||
let addr = format!("{}:{}", host, port);
|
||||
|
||||
async move {
|
||||
let client_stream = TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
let (mut sender, conn) = hyper::client::conn::handshake(client_stream).await?;
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
println!("Connection failed: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
sender.send_request(req).await
|
||||
}
|
||||
});
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new().serve_connection(stream, service).await {
|
||||
println!("Failed to servce connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Request, Response};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
Ok(Response::new(Body::from("Hello World!")))
|
||||
@@ -13,22 +16,20 @@ async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
// For every connection, we must make a `Service` to handle all
|
||||
// incoming HTTP requests on said connection.
|
||||
let make_svc = make_service_fn(|_conn| {
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
async { Ok::<_, Infallible>(service_fn(hello)) }
|
||||
});
|
||||
|
||||
let addr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
let server = Server::bind(&addr).serve(make_svc);
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
server.await?;
|
||||
|
||||
Ok(())
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.serve_connection(stream, service_fn(hello))
|
||||
.await
|
||||
{
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::convert::Infallible;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::client::conn::Builder;
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper::{Body, Client, Method, Request, Response, Server};
|
||||
use hyper::{Body, Method, Request, Response};
|
||||
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
type HttpClient = Client<hyper::client::HttpConnector>;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
// To try this example:
|
||||
// 1. cargo run --example http_proxy
|
||||
@@ -19,32 +18,29 @@ type HttpClient = Client<hyper::client::HttpConnector>;
|
||||
// 3. send requests
|
||||
// $ curl -i https://www.some_domain.com/
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8100));
|
||||
|
||||
let client = Client::builder()
|
||||
.http1_title_case_headers(true)
|
||||
.http1_preserve_header_case(true)
|
||||
.build_http();
|
||||
|
||||
let make_service = make_service_fn(move |_| {
|
||||
let client = client.clone();
|
||||
async move { Ok::<_, Infallible>(service_fn(move |req| proxy(client.clone(), req))) }
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr)
|
||||
.http1_preserve_header_case(true)
|
||||
.http1_title_case_headers(true)
|
||||
.serve(make_service);
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.http1_preserve_header_case(true)
|
||||
.http1_title_case_headers(true)
|
||||
.serve_connection(stream, service_fn(proxy))
|
||||
.await
|
||||
{
|
||||
println!("Failed to serve connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
||||
async fn proxy(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
||||
println!("req: {:?}", req);
|
||||
|
||||
if Method::CONNECT == req.method() {
|
||||
@@ -82,7 +78,24 @@ async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>,
|
||||
Ok(resp)
|
||||
}
|
||||
} else {
|
||||
client.request(req).await
|
||||
let host = req.uri().host().expect("uri has no host");
|
||||
let port = req.uri().port_u16().unwrap_or(80);
|
||||
let addr = format!("{}:{}", host, port);
|
||||
|
||||
let stream = TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
let (mut sender, conn) = Builder::new()
|
||||
.http1_preserve_header_case(true)
|
||||
.http1_title_case_headers(true)
|
||||
.handshake(stream)
|
||||
.await?;
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
println!("Connection failed: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
sender.send_request(req).await
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
#![deny(warnings)]
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use futures_util::future::join;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Request, Response};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
static INDEX1: &[u8] = b"The 1st service!";
|
||||
static INDEX2: &[u8] = b"The 2nd service!";
|
||||
@@ -20,16 +24,40 @@ async fn index2(_: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr1 = ([127, 0, 0, 1], 1337).into();
|
||||
let addr2 = ([127, 0, 0, 1], 1338).into();
|
||||
let addr1: SocketAddr = ([127, 0, 0, 1], 1337).into();
|
||||
let addr2: SocketAddr = ([127, 0, 0, 1], 1338).into();
|
||||
|
||||
let srv1 = Server::bind(&addr1).serve(make_service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(service_fn(index1))
|
||||
}));
|
||||
let srv1 = async move {
|
||||
let listener = TcpListener::bind(addr1).await.unwrap();
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
|
||||
let srv2 = Server::bind(&addr2).serve(make_service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(service_fn(index2))
|
||||
}));
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.serve_connection(stream, service_fn(index1))
|
||||
.await
|
||||
{
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
let srv2 = async move {
|
||||
let listener = TcpListener::bind(addr2).await.unwrap();
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.serve_connection(stream, service_fn(index2))
|
||||
.await
|
||||
{
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
println!("Listening on http://{} and http://{}", addr1, addr2);
|
||||
|
||||
|
||||
@@ -1,10 +1,13 @@
|
||||
// #![deny(warnings)] // FIXME: https://github.com/rust-lang/rust/issues/62411
|
||||
#![warn(rust_2018_idioms)]
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::net::SocketAddr;
|
||||
use url::form_urlencoded;
|
||||
|
||||
static INDEX: &[u8] = b"<html><body><form action=\"post\" method=\"post\">Name: <input type=\"text\" name=\"name\"><br>Number: <input type=\"text\" name=\"number\"><br><input type=\"submit\"></body></html>";
|
||||
@@ -102,15 +105,20 @@ async fn param_example(req: Request<Body>) -> Result<Response<Body>, hyper::Erro
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = ([127, 0, 0, 1], 1337).into();
|
||||
|
||||
let server = Server::bind(&addr).serve(make_service_fn(|_| async {
|
||||
Ok::<_, hyper::Error>(service_fn(param_example))
|
||||
}));
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 1337).into();
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
server.await?;
|
||||
|
||||
Ok(())
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.serve_connection(stream, service_fn(param_example))
|
||||
.await
|
||||
{
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,30 +1,36 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use tokio::fs::File;
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use tokio_util::codec::{BytesCodec, FramedRead};
|
||||
use hyper::server::conn::Http;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Method, Request, Response, Result, Server, StatusCode};
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Method, Request, Response, Result, StatusCode};
|
||||
|
||||
static INDEX: &str = "examples/send_file_index.html";
|
||||
static NOTFOUND: &[u8] = b"Not Found";
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = "127.0.0.1:1337".parse().unwrap();
|
||||
|
||||
let make_service =
|
||||
make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(response_examples)) });
|
||||
|
||||
let server = Server::bind(&addr).serve(make_service);
|
||||
let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap();
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.serve_connection(stream, service_fn(response_examples))
|
||||
.await
|
||||
{
|
||||
println!("Failed to serve connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -48,11 +54,8 @@ fn not_found() -> Response<Body> {
|
||||
}
|
||||
|
||||
async fn simple_file_send(filename: &str) -> Result<Response<Body>> {
|
||||
// Serve a file by asynchronously reading it by chunks using tokio-util crate.
|
||||
|
||||
if let Ok(file) = File::open(filename).await {
|
||||
let stream = FramedRead::new(file, BytesCodec::new());
|
||||
let body = Body::wrap_stream(stream);
|
||||
if let Ok(contents) = tokio::fs::read(filename).await {
|
||||
let body = contents.into();
|
||||
return Ok(Response::new(body));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::Service;
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use hyper::{Body, Request, Response};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use std::future::Future;
|
||||
use std::net::SocketAddr;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
@@ -9,13 +12,23 @@ type Counter = i32;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||
let addr = ([127, 0, 0, 1], 3000).into();
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
let server = Server::bind(&addr).serve(MakeSvc { counter: 81818 });
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
server.await?;
|
||||
Ok(())
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.serve_connection(stream, Svc { counter: 81818 })
|
||||
.await
|
||||
{
|
||||
println!("Failed to serve connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct Svc {
|
||||
@@ -54,23 +67,3 @@ impl Service<Request<Body>> for Svc {
|
||||
Box::pin(async { res })
|
||||
}
|
||||
}
|
||||
|
||||
struct MakeSvc {
|
||||
counter: Counter,
|
||||
}
|
||||
|
||||
impl<T> Service<T> for MakeSvc {
|
||||
type Response = Svc;
|
||||
type Error = hyper::Error;
|
||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||
|
||||
fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, _: T) -> Self::Future {
|
||||
let counter = self.counter.clone();
|
||||
let fut = async move { Ok(Svc { counter }) };
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use hyper::server::conn::Http;
|
||||
use std::cell::Cell;
|
||||
use std::net::SocketAddr;
|
||||
use std::rc::Rc;
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
use hyper::body::{Bytes, HttpBody};
|
||||
use hyper::header::{HeaderMap, HeaderValue};
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Error, Response, Server};
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Error, Response};
|
||||
use std::marker::PhantomData;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
@@ -46,7 +48,7 @@ impl HttpBody for Body {
|
||||
}
|
||||
}
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
// Configure a runtime that runs everything on the current thread
|
||||
@@ -57,43 +59,39 @@ fn main() {
|
||||
|
||||
// Combine it with a `LocalSet, which means it can spawn !Send futures...
|
||||
let local = tokio::task::LocalSet::new();
|
||||
local.block_on(&rt, run());
|
||||
local.block_on(&rt, run())
|
||||
}
|
||||
|
||||
async fn run() {
|
||||
let addr = ([127, 0, 0, 1], 3000).into();
|
||||
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
// Using a !Send request counter is fine on 1 thread...
|
||||
let counter = Rc::new(Cell::new(0));
|
||||
|
||||
let make_service = make_service_fn(move |_| {
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
// For each connection, clone the counter to use in our service...
|
||||
let cnt = counter.clone();
|
||||
|
||||
async move {
|
||||
Ok::<_, Error>(service_fn(move |_| {
|
||||
let prev = cnt.get();
|
||||
cnt.set(prev + 1);
|
||||
let value = cnt.get();
|
||||
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) }
|
||||
}))
|
||||
}
|
||||
});
|
||||
let service = service_fn(move |_| {
|
||||
let prev = cnt.get();
|
||||
cnt.set(prev + 1);
|
||||
let value = cnt.get();
|
||||
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) }
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).executor(LocalExec).serve(make_service);
|
||||
|
||||
// Just shows that with_graceful_shutdown compiles with !Send,
|
||||
// !Sync HttpBody.
|
||||
let (_tx, rx) = oneshot::channel::<()>();
|
||||
let server = server.with_graceful_shutdown(async move {
|
||||
rx.await.ok();
|
||||
});
|
||||
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
// The server would block on current thread to await !Send futures.
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
tokio::task::spawn_local(async move {
|
||||
if let Err(err) = Http::new()
|
||||
.with_executor(LocalExec)
|
||||
.serve_connection(stream, service)
|
||||
.await
|
||||
{
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,52 +1,46 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{
|
||||
atomic::{AtomicUsize, Ordering},
|
||||
Arc,
|
||||
};
|
||||
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Error, Response, Server};
|
||||
use hyper::{server::conn::Http, service::service_fn};
|
||||
use hyper::{Body, Error, Response};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() {
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = ([127, 0, 0, 1], 3000).into();
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||
|
||||
// For the most basic of state, we just share a counter, that increments
|
||||
// with each request, and we send its value back in the response.
|
||||
let counter = Arc::new(AtomicUsize::new(0));
|
||||
|
||||
// The closure inside `make_service_fn` is run for each connection,
|
||||
// creating a 'service' to handle requests for that specific connection.
|
||||
let make_service = make_service_fn(move |_| {
|
||||
// While the state was moved into the make_service closure,
|
||||
// we need to clone it here because this closure is called
|
||||
// once for every connection.
|
||||
//
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
// Each connection could send multiple requests, so
|
||||
// the `Service` needs a clone to handle later requests.
|
||||
let counter = counter.clone();
|
||||
|
||||
async move {
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
Ok::<_, Error>(service_fn(move |_req| {
|
||||
// Get the current count, and also increment by 1, in a single
|
||||
// atomic operation.
|
||||
let count = counter.fetch_add(1, Ordering::AcqRel);
|
||||
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) }
|
||||
}))
|
||||
// This is the `Service` that will handle the connection.
|
||||
// `service_fn` is a helper to convert a function that
|
||||
// returns a Response into a `Service`.
|
||||
let service = service_fn(move |_req| {
|
||||
// Get the current count, and also increment by 1, in a single
|
||||
// atomic operation.
|
||||
let count = counter.fetch_add(1, Ordering::AcqRel);
|
||||
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) }
|
||||
});
|
||||
|
||||
if let Err(err) = Http::new().serve_connection(stream, service).await {
|
||||
println!("Error serving connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).serve(make_service);
|
||||
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +0,0 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use hyper::client::conn::Builder;
|
||||
use hyper::client::connect::HttpConnector;
|
||||
use hyper::client::service::Connect;
|
||||
use hyper::service::Service;
|
||||
use hyper::{Body, Request};
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let mut mk_svc = Connect::new(HttpConnector::new(), Builder::new());
|
||||
|
||||
let uri = "http://127.0.0.1:8080".parse::<http::Uri>()?;
|
||||
|
||||
let mut svc = mk_svc.call(uri.clone()).await?;
|
||||
|
||||
let body = Body::empty();
|
||||
|
||||
let req = Request::get(uri).body(body)?;
|
||||
let res = svc.call(req).await?;
|
||||
|
||||
println!("RESPONSE={:?}", res);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,10 +1,13 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::net::SocketAddr;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use futures_util::future;
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::Service;
|
||||
use hyper::{Body, Request, Response, Server};
|
||||
use hyper::{Body, Request, Response};
|
||||
use tokio::net::TcpListener;
|
||||
|
||||
const ROOT: &str = "/";
|
||||
|
||||
@@ -36,33 +39,22 @@ impl Service<Request<Body>> for Svc {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct MakeSvc;
|
||||
|
||||
impl<T> Service<T> for MakeSvc {
|
||||
type Response = Svc;
|
||||
type Error = std::io::Error;
|
||||
type Future = future::Ready<Result<Self::Response, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Ok(()).into()
|
||||
}
|
||||
|
||||
fn call(&mut self, _: T) -> Self::Future {
|
||||
future::ok(Svc)
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::main]
|
||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = "127.0.0.1:1337".parse().unwrap();
|
||||
|
||||
let server = Server::bind(&addr).serve(MakeSvc);
|
||||
let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap();
|
||||
|
||||
let listener = TcpListener::bind(addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
|
||||
server.await?;
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
Ok(())
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = Http::new().serve_connection(stream, Svc).await {
|
||||
println!("Failed to serve connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,13 +3,15 @@
|
||||
// Note: `hyper::upgrade` docs link to this upgrade.
|
||||
use std::str;
|
||||
|
||||
use hyper::server::conn::Http;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::sync::oneshot;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
use tokio::sync::watch;
|
||||
|
||||
use hyper::header::{HeaderValue, UPGRADE};
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::service::service_fn;
|
||||
use hyper::upgrade::Upgraded;
|
||||
use hyper::{Body, Client, Request, Response, Server, StatusCode};
|
||||
use hyper::{Body, Request, Response, StatusCode};
|
||||
use std::net::SocketAddr;
|
||||
|
||||
// A simple type alias so as to DRY.
|
||||
@@ -92,7 +94,17 @@ async fn client_upgrade_request(addr: SocketAddr) -> Result<()> {
|
||||
.body(Body::empty())
|
||||
.unwrap();
|
||||
|
||||
let res = Client::new().request(req).await?;
|
||||
let stream = TcpStream::connect(addr).await?;
|
||||
let (mut sender, conn) = hyper::client::conn::handshake(stream).await?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
println!("Connection failed: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let res = sender.send_request(req).await?;
|
||||
|
||||
if res.status() != StatusCode::SWITCHING_PROTOCOLS {
|
||||
panic!("Our server didn't upgrade: {}", res.status());
|
||||
}
|
||||
@@ -114,28 +126,52 @@ async fn main() {
|
||||
// For this example, we just make a server and our own client to talk to
|
||||
// it, so the exact port isn't important. Instead, let the OS give us an
|
||||
// unused port.
|
||||
let addr = ([127, 0, 0, 1], 0).into();
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 0).into();
|
||||
|
||||
let make_service =
|
||||
make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(server_upgrade)) });
|
||||
|
||||
let server = Server::bind(&addr).serve(make_service);
|
||||
let listener = TcpListener::bind(addr).await.expect("failed to bind");
|
||||
|
||||
// We need the assigned address for the client to send it messages.
|
||||
let addr = server.local_addr();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
|
||||
// For this example, a oneshot is used to signal that after 1 request,
|
||||
// the server should be shutdown.
|
||||
let (tx, rx) = oneshot::channel::<()>();
|
||||
let server = server.with_graceful_shutdown(async move {
|
||||
rx.await.ok();
|
||||
});
|
||||
let (tx, mut rx) = watch::channel(false);
|
||||
|
||||
// Spawn server on the default executor,
|
||||
// which is usually a thread-pool from tokio default runtime.
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(e) = server.await {
|
||||
eprintln!("server error: {}", e);
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = listener.accept() => {
|
||||
let (stream, _) = res.expect("Failed to accept");
|
||||
|
||||
let mut rx = rx.clone();
|
||||
tokio::task::spawn(async move {
|
||||
let conn = Http::new().serve_connection(stream, service_fn(server_upgrade));
|
||||
|
||||
// Don't forget to enable upgrades on the connection.
|
||||
let mut conn = conn.with_upgrades();
|
||||
|
||||
let mut conn = Pin::new(&mut conn);
|
||||
|
||||
tokio::select! {
|
||||
res = &mut conn => {
|
||||
if let Err(err) = res {
|
||||
println!("Error serving connection: {:?}", err);
|
||||
return;
|
||||
}
|
||||
}
|
||||
// Continue polling the connection after enabling graceful shutdown.
|
||||
_ = rx.changed() => {
|
||||
conn.graceful_shutdown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
_ = rx.changed() => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -147,5 +183,5 @@ async fn main() {
|
||||
|
||||
// Complete the oneshot so that the server stops
|
||||
// listening and the process can close down.
|
||||
let _ = tx.send(());
|
||||
let _ = tx.send(true);
|
||||
}
|
||||
|
||||
@@ -1,10 +1,12 @@
|
||||
#![deny(warnings)]
|
||||
|
||||
use std::net::SocketAddr;
|
||||
|
||||
use bytes::Buf;
|
||||
use futures_util::{stream, StreamExt};
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{header, Body, Client, Method, Request, Response, Server, StatusCode};
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{header, Body, Method, Request, Response, StatusCode};
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
type GenericError = Box<dyn std::error::Error + Send + Sync>;
|
||||
type Result<T> = std::result::Result<T, GenericError>;
|
||||
@@ -15,7 +17,7 @@ static NOTFOUND: &[u8] = b"Not Found";
|
||||
static POST_DATA: &str = r#"{"original": "data"}"#;
|
||||
static URL: &str = "http://127.0.0.1:1337/json_api";
|
||||
|
||||
async fn client_request_response(client: &Client<HttpConnector>) -> Result<Response<Body>> {
|
||||
async fn client_request_response() -> Result<Response<Body>> {
|
||||
let req = Request::builder()
|
||||
.method(Method::POST)
|
||||
.uri(URL)
|
||||
@@ -23,19 +25,23 @@ async fn client_request_response(client: &Client<HttpConnector>) -> Result<Respo
|
||||
.body(POST_DATA.into())
|
||||
.unwrap();
|
||||
|
||||
let web_res = client.request(req).await?;
|
||||
// Compare the JSON we sent (before) with what we received (after):
|
||||
let before = stream::once(async {
|
||||
Ok(format!(
|
||||
"<b>POST request body</b>: {}<br><b>Response</b>: ",
|
||||
POST_DATA,
|
||||
)
|
||||
.into())
|
||||
});
|
||||
let after = web_res.into_body();
|
||||
let body = Body::wrap_stream(before.chain(after));
|
||||
let host = req.uri().host().expect("uri has no host");
|
||||
let port = req.uri().port_u16().expect("uri has no port");
|
||||
let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
|
||||
|
||||
Ok(Response::new(body))
|
||||
let (mut sender, conn) = hyper::client::conn::handshake(stream).await?;
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
println!("Connection error: {:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let web_res = sender.send_request(req).await?;
|
||||
|
||||
let res_body = web_res.into_body();
|
||||
|
||||
Ok(Response::new(res_body))
|
||||
}
|
||||
|
||||
async fn api_post_response(req: Request<Body>) -> Result<Response<Body>> {
|
||||
@@ -69,13 +75,10 @@ async fn api_get_response() -> Result<Response<Body>> {
|
||||
Ok(res)
|
||||
}
|
||||
|
||||
async fn response_examples(
|
||||
req: Request<Body>,
|
||||
client: Client<HttpConnector>,
|
||||
) -> Result<Response<Body>> {
|
||||
async fn response_examples(req: Request<Body>) -> Result<Response<Body>> {
|
||||
match (req.method(), req.uri().path()) {
|
||||
(&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())),
|
||||
(&Method::GET, "/test.html") => client_request_response(&client).await,
|
||||
(&Method::GET, "/test.html") => client_request_response().await,
|
||||
(&Method::POST, "/json_api") => api_post_response(req).await,
|
||||
(&Method::GET, "/json_api") => api_get_response().await,
|
||||
_ => {
|
||||
@@ -92,27 +95,19 @@ async fn response_examples(
|
||||
async fn main() -> Result<()> {
|
||||
pretty_env_logger::init();
|
||||
|
||||
let addr = "127.0.0.1:1337".parse().unwrap();
|
||||
|
||||
// Share a `Client` with all `Service`s
|
||||
let client = Client::new();
|
||||
|
||||
let new_service = make_service_fn(move |_| {
|
||||
// Move a clone of `client` into the `service_fn`.
|
||||
let client = client.clone();
|
||||
async {
|
||||
Ok::<_, GenericError>(service_fn(move |req| {
|
||||
// Clone again to ensure that client outlives this closure.
|
||||
response_examples(req, client.to_owned())
|
||||
}))
|
||||
}
|
||||
});
|
||||
|
||||
let server = Server::bind(&addr).serve(new_service);
|
||||
let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap();
|
||||
|
||||
let listener = TcpListener::bind(&addr).await?;
|
||||
println!("Listening on http://{}", addr);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await?;
|
||||
|
||||
server.await?;
|
||||
tokio::task::spawn(async move {
|
||||
let service = service_fn(move |req| response_examples(req));
|
||||
|
||||
Ok(())
|
||||
if let Err(err) = Http::new().serve_connection(stream, service).await {
|
||||
println!("Failed to serve connection: {:?}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
178
src/body/body.rs
178
src/body/body.rs
@@ -1,23 +1,15 @@
|
||||
use std::borrow::Cow;
|
||||
#[cfg(feature = "stream")]
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_channel::mpsc;
|
||||
use futures_channel::oneshot;
|
||||
use futures_core::Stream; // for mpsc::Receiver
|
||||
#[cfg(feature = "stream")]
|
||||
use futures_util::TryStreamExt;
|
||||
use http::HeaderMap;
|
||||
use http_body::{Body as HttpBody, SizeHint};
|
||||
|
||||
use super::DecodedLength;
|
||||
#[cfg(feature = "stream")]
|
||||
use crate::common::sync_wrapper::SyncWrapper;
|
||||
use crate::common::Future;
|
||||
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||
use crate::common::Never;
|
||||
use crate::common::{task, watch, Pin, Poll};
|
||||
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||
use crate::proto::h2::ping;
|
||||
@@ -35,9 +27,6 @@ type TrailersSender = oneshot::Sender<HeaderMap>;
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct Body {
|
||||
kind: Kind,
|
||||
/// Keep the extra bits in an `Option<Box<Extra>>`, so that
|
||||
/// Body stays small in the common case (no extras needed).
|
||||
extra: Option<Box<Extra>>,
|
||||
}
|
||||
|
||||
enum Kind {
|
||||
@@ -56,40 +45,6 @@ enum Kind {
|
||||
},
|
||||
#[cfg(feature = "ffi")]
|
||||
Ffi(crate::ffi::UserBody),
|
||||
#[cfg(feature = "stream")]
|
||||
Wrapped(
|
||||
SyncWrapper<
|
||||
Pin<Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>>,
|
||||
>,
|
||||
),
|
||||
}
|
||||
|
||||
struct Extra {
|
||||
/// Allow the client to pass a future to delay the `Body` from returning
|
||||
/// EOF. This allows the `Client` to try to put the idle connection
|
||||
/// back into the pool before the body is "finished".
|
||||
///
|
||||
/// The reason for this is so that creating a new request after finishing
|
||||
/// streaming the body of a response could sometimes result in creating
|
||||
/// a brand new connection, since the pool didn't know about the idle
|
||||
/// connection yet.
|
||||
delayed_eof: Option<DelayEof>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||
type DelayEofUntil = oneshot::Receiver<Never>;
|
||||
|
||||
enum DelayEof {
|
||||
/// Initial state, stream hasn't seen EOF yet.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
NotEof(DelayEofUntil),
|
||||
/// Transitions to this state once we've seen `poll` try to
|
||||
/// return EOF (`None`). This future is then polled, and
|
||||
/// when it completes, the Body finally returns EOF (`None`).
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
Eof(DelayEofUntil),
|
||||
}
|
||||
|
||||
/// A sender half created through [`Body::channel()`].
|
||||
@@ -164,41 +119,8 @@ impl Body {
|
||||
(tx, rx)
|
||||
}
|
||||
|
||||
/// Wrap a futures `Stream` in a box inside `Body`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use hyper::Body;
|
||||
/// let chunks: Vec<Result<_, std::io::Error>> = vec![
|
||||
/// Ok("hello"),
|
||||
/// Ok(" "),
|
||||
/// Ok("world"),
|
||||
/// ];
|
||||
///
|
||||
/// let stream = futures_util::stream::iter(chunks);
|
||||
///
|
||||
/// let body = Body::wrap_stream(stream);
|
||||
/// ```
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This function requires enabling the `stream` feature in your
|
||||
/// `Cargo.toml`.
|
||||
#[cfg(feature = "stream")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
|
||||
pub fn wrap_stream<S, O, E>(stream: S) -> Body
|
||||
where
|
||||
S: Stream<Item = Result<O, E>> + Send + 'static,
|
||||
O: Into<Bytes> + 'static,
|
||||
E: Into<Box<dyn StdError + Send + Sync>> + 'static,
|
||||
{
|
||||
let mapped = stream.map_ok(Into::into).map_err(Into::into);
|
||||
Body::new(Kind::Wrapped(SyncWrapper::new(Box::pin(mapped))))
|
||||
}
|
||||
|
||||
fn new(kind: Kind) -> Body {
|
||||
Body { kind, extra: None }
|
||||
Body { kind }
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||
@@ -221,62 +143,6 @@ impl Body {
|
||||
body
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
pub(crate) fn delayed_eof(&mut self, fut: DelayEofUntil) {
|
||||
self.extra_mut().delayed_eof = Some(DelayEof::NotEof(fut));
|
||||
}
|
||||
|
||||
fn take_delayed_eof(&mut self) -> Option<DelayEof> {
|
||||
self.extra
|
||||
.as_mut()
|
||||
.and_then(|extra| extra.delayed_eof.take())
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
fn extra_mut(&mut self) -> &mut Extra {
|
||||
self.extra
|
||||
.get_or_insert_with(|| Box::new(Extra { delayed_eof: None }))
|
||||
}
|
||||
|
||||
fn poll_eof(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<crate::Result<Bytes>>> {
|
||||
match self.take_delayed_eof() {
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
Some(DelayEof::NotEof(mut delay)) => match self.poll_inner(cx) {
|
||||
ok @ Poll::Ready(Some(Ok(..))) | ok @ Poll::Pending => {
|
||||
self.extra_mut().delayed_eof = Some(DelayEof::NotEof(delay));
|
||||
ok
|
||||
}
|
||||
Poll::Ready(None) => match Pin::new(&mut delay).poll(cx) {
|
||||
Poll::Ready(Ok(never)) => match never {},
|
||||
Poll::Pending => {
|
||||
self.extra_mut().delayed_eof = Some(DelayEof::Eof(delay));
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Ready(Err(_done)) => Poll::Ready(None),
|
||||
},
|
||||
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
|
||||
},
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
Some(DelayEof::Eof(mut delay)) => match Pin::new(&mut delay).poll(cx) {
|
||||
Poll::Ready(Ok(never)) => match never {},
|
||||
Poll::Pending => {
|
||||
self.extra_mut().delayed_eof = Some(DelayEof::Eof(delay));
|
||||
Poll::Pending
|
||||
}
|
||||
Poll::Ready(Err(_done)) => Poll::Ready(None),
|
||||
},
|
||||
#[cfg(any(
|
||||
not(any(feature = "http1", feature = "http2")),
|
||||
not(feature = "client")
|
||||
))]
|
||||
Some(delay_eof) => match delay_eof {},
|
||||
None => self.poll_inner(cx),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
pub(crate) fn as_ffi_mut(&mut self) -> &mut crate::ffi::UserBody {
|
||||
match self.kind {
|
||||
@@ -329,12 +195,6 @@ impl Body {
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
Kind::Ffi(ref mut body) => body.poll_data(cx),
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
Kind::Wrapped(ref mut s) => match ready!(s.get_mut().as_mut().poll_next(cx)) {
|
||||
Some(res) => Poll::Ready(Some(res.map_err(crate::Error::new_body))),
|
||||
None => Poll::Ready(None),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -364,7 +224,7 @@ impl HttpBody for Body {
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||
self.poll_eof(cx)
|
||||
self.poll_inner(cx)
|
||||
}
|
||||
|
||||
fn poll_trailers(
|
||||
@@ -405,8 +265,6 @@ impl HttpBody for Body {
|
||||
Kind::H2 { recv: ref h2, .. } => h2.is_end_stream(),
|
||||
#[cfg(feature = "ffi")]
|
||||
Kind::Ffi(..) => false,
|
||||
#[cfg(feature = "stream")]
|
||||
Kind::Wrapped(..) => false,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -426,8 +284,6 @@ impl HttpBody for Body {
|
||||
match self.kind {
|
||||
Kind::Once(Some(ref val)) => SizeHint::with_exact(val.len() as u64),
|
||||
Kind::Once(None) => SizeHint::with_exact(0),
|
||||
#[cfg(feature = "stream")]
|
||||
Kind::Wrapped(..) => SizeHint::default(),
|
||||
Kind::Chan { content_length, .. } => opt_len!(content_length),
|
||||
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||
Kind::H2 { content_length, .. } => opt_len!(content_length),
|
||||
@@ -457,33 +313,6 @@ impl fmt::Debug for Body {
|
||||
}
|
||||
}
|
||||
|
||||
/// # Optional
|
||||
///
|
||||
/// This function requires enabling the `stream` feature in your
|
||||
/// `Cargo.toml`.
|
||||
#[cfg(feature = "stream")]
|
||||
impl Stream for Body {
|
||||
type Item = crate::Result<Bytes>;
|
||||
|
||||
fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> {
|
||||
HttpBody::poll_data(self, cx)
|
||||
}
|
||||
}
|
||||
|
||||
/// # Optional
|
||||
///
|
||||
/// This function requires enabling the `stream` feature in your
|
||||
/// `Cargo.toml`.
|
||||
#[cfg(feature = "stream")]
|
||||
impl From<Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>> for Body {
|
||||
#[inline]
|
||||
fn from(
|
||||
stream: Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>,
|
||||
) -> Body {
|
||||
Body::new(Kind::Wrapped(SyncWrapper::new(stream.into())))
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bytes> for Body {
|
||||
#[inline]
|
||||
fn from(chunk: Bytes) -> Body {
|
||||
@@ -690,6 +519,7 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
#[tokio::test]
|
||||
async fn channel_abort() {
|
||||
let (tx, mut rx) = Body::channel();
|
||||
@@ -700,6 +530,7 @@ mod tests {
|
||||
assert!(err.is_body_write_aborted(), "{:?}", err);
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
#[tokio::test]
|
||||
async fn channel_abort_when_buffer_is_full() {
|
||||
let (mut tx, mut rx) = Body::channel();
|
||||
@@ -726,6 +557,7 @@ mod tests {
|
||||
assert_eq!(chunk2, "chunk 2");
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
#[tokio::test]
|
||||
async fn channel_empty() {
|
||||
let (_, mut rx) = Body::channel();
|
||||
|
||||
@@ -17,17 +17,11 @@ use super::HttpBody;
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||
/// # async fn doc() -> hyper::Result<()> {
|
||||
/// use hyper::{body::HttpBody};
|
||||
///
|
||||
/// # let request = hyper::Request::builder()
|
||||
/// # .method(hyper::Method::POST)
|
||||
/// # .uri("http://httpbin.org/post")
|
||||
/// # .header("content-type", "application/json")
|
||||
/// # .body(hyper::Body::from(r#"{"library":"hyper"}"#)).unwrap();
|
||||
/// # let client = hyper::Client::new();
|
||||
/// let response = client.request(request).await?;
|
||||
/// # use hyper::{Body, Response};
|
||||
/// # use hyper::body::HttpBody;
|
||||
/// #
|
||||
/// let response = Response::new(Body::from("response body"));
|
||||
///
|
||||
/// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024;
|
||||
///
|
||||
|
||||
1503
src/client/client.rs
1503
src/client/client.rs
File diff suppressed because it is too large
Load Diff
504
src/client/conn/http1.rs
Normal file
504
src/client/conn/http1.rs
Normal file
@@ -0,0 +1,504 @@
|
||||
//! HTTP/1 client connections
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::sync::Arc;
|
||||
|
||||
use http::{Request, Response};
|
||||
use httparse::ParserConfig;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::Body;
|
||||
use crate::body::HttpBody;
|
||||
use crate::common::{
|
||||
exec::{BoxSendFuture, Exec},
|
||||
task, Future, Pin, Poll,
|
||||
};
|
||||
use crate::upgrade::Upgraded;
|
||||
use crate::proto;
|
||||
use crate::rt::Executor;
|
||||
use super::super::dispatch;
|
||||
|
||||
type Dispatcher<T, B> =
|
||||
proto::dispatch::Dispatcher<proto::dispatch::Client<B>, B, T, proto::h1::ClientTransaction>;
|
||||
|
||||
/// The sender side of an established connection.
|
||||
pub struct SendRequest<B> {
|
||||
dispatch: dispatch::Sender<Request<B>, Response<Body>>,
|
||||
}
|
||||
|
||||
/// A future that processes all HTTP state for the IO object.
|
||||
///
|
||||
/// In most cases, this should just be spawned into an executor, so that it
|
||||
/// can process incoming and outgoing messages, notice hangups, and the like.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
inner: Option<Dispatcher<T, B>>,
|
||||
}
|
||||
|
||||
/// A builder to configure an HTTP connection.
|
||||
///
|
||||
/// After setting options, the builder is used to create a handshake future.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Builder {
|
||||
pub(super) exec: Exec,
|
||||
h09_responses: bool,
|
||||
h1_parser_config: ParserConfig,
|
||||
h1_writev: Option<bool>,
|
||||
h1_title_case_headers: bool,
|
||||
h1_preserve_header_case: bool,
|
||||
#[cfg(feature = "ffi")]
|
||||
h1_preserve_header_order: bool,
|
||||
h1_read_buf_exact_size: Option<usize>,
|
||||
h1_max_buf_size: Option<usize>,
|
||||
}
|
||||
|
||||
/// Returns a handshake future over some IO.
|
||||
///
|
||||
/// This is a shortcut for `Builder::new().handshake(io)`.
|
||||
/// See [`client::conn`](crate::client::conn) for more.
|
||||
pub async fn handshake<T>(
|
||||
io: T,
|
||||
) -> crate::Result<(SendRequest<crate::Body>, Connection<T, crate::Body>)>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
Builder::new().handshake(io).await
|
||||
}
|
||||
|
||||
// ===== impl SendRequest
|
||||
|
||||
impl<B> SendRequest<B> {
|
||||
/// Polls to determine whether this sender can be used yet for a request.
|
||||
///
|
||||
/// If the associated connection is closed, this returns an Error.
|
||||
pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||
self.dispatch.poll_ready(cx)
|
||||
}
|
||||
|
||||
/*
|
||||
pub(super) async fn when_ready(self) -> crate::Result<Self> {
|
||||
let mut me = Some(self);
|
||||
future::poll_fn(move |cx| {
|
||||
ready!(me.as_mut().unwrap().poll_ready(cx))?;
|
||||
Poll::Ready(Ok(me.take().unwrap()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) fn is_ready(&self) -> bool {
|
||||
self.dispatch.is_ready()
|
||||
}
|
||||
|
||||
pub(super) fn is_closed(&self) -> bool {
|
||||
self.dispatch.is_closed()
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl<B> SendRequest<B>
|
||||
where
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
/// Sends a `Request` on the associated connection.
|
||||
///
|
||||
/// Returns a future that if successful, yields the `Response`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// There are some key differences in what automatic things the `Client`
|
||||
/// does for you that will not be done here:
|
||||
///
|
||||
/// - `Client` requires absolute-form `Uri`s, since the scheme and
|
||||
/// authority are needed to connect. They aren't required here.
|
||||
/// - Since the `Client` requires absolute-form `Uri`s, it can add
|
||||
/// the `Host` header based on it. You must add a `Host` header yourself
|
||||
/// before calling this method.
|
||||
/// - Since absolute-form `Uri`s are not required, if received, they will
|
||||
/// be serialized as-is.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use http::header::HOST;
|
||||
/// # use hyper::client::conn::SendRequest;
|
||||
/// # use hyper::Body;
|
||||
/// use hyper::Request;
|
||||
///
|
||||
/// # async fn doc(mut tx: SendRequest<Body>) -> hyper::Result<()> {
|
||||
/// // build a Request
|
||||
/// let req = Request::builder()
|
||||
/// .uri("/foo/bar")
|
||||
/// .header(HOST, "hyper.rs")
|
||||
/// .body(Body::empty())
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // send it and await a Response
|
||||
/// let res = tx.send_request(req).await?;
|
||||
/// // assert the Response
|
||||
/// assert!(res.status().is_success());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn send_request(&mut self, req: Request<B>) -> impl Future<Output = crate::Result<Response<Body>>> {
|
||||
let sent = self.dispatch.send(req);
|
||||
|
||||
async move {
|
||||
match sent {
|
||||
Ok(rx) => match rx.await {
|
||||
Ok(Ok(resp)) => Ok(resp),
|
||||
Ok(Err(err)) => Err(err),
|
||||
// this is definite bug if it happens, but it shouldn't happen!
|
||||
Err(_canceled) => panic!("dispatch dropped without returning error"),
|
||||
}
|
||||
Err(_req) => {
|
||||
tracing::debug!("connection was not ready");
|
||||
|
||||
Err(crate::Error::new_canceled().with("connection was not ready"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub(super) fn send_request_retryable(
|
||||
&mut self,
|
||||
req: Request<B>,
|
||||
) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> + Unpin
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
match self.dispatch.try_send(req) {
|
||||
Ok(rx) => {
|
||||
Either::Left(rx.then(move |res| {
|
||||
match res {
|
||||
Ok(Ok(res)) => future::ok(res),
|
||||
Ok(Err(err)) => future::err(err),
|
||||
// this is definite bug if it happens, but it shouldn't happen!
|
||||
Err(_) => panic!("dispatch dropped without returning error"),
|
||||
}
|
||||
}))
|
||||
}
|
||||
Err(req) => {
|
||||
tracing::debug!("connection was not ready");
|
||||
let err = crate::Error::new_canceled().with("connection was not ready");
|
||||
Either::Right(future::err((err, Some(req))))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for SendRequest<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SendRequest").finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Connection
|
||||
|
||||
impl<T, B> fmt::Debug for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + fmt::Debug + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Connection").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> Future for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
type Output = crate::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
match ready!(Pin::new(self.inner.as_mut().unwrap()).poll(cx))? {
|
||||
proto::Dispatched::Shutdown => Poll::Ready(Ok(())),
|
||||
proto::Dispatched::Upgrade(pending) => match self.inner.take() {
|
||||
Some(h1) => {
|
||||
let (io, buf, _) = h1.into_inner();
|
||||
pending.fulfill(Upgraded::new(io, buf));
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
_ => {
|
||||
drop(pending);
|
||||
unreachable!("Upgraded twice");
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Builder
|
||||
|
||||
impl Builder {
|
||||
/// Creates a new connection builder.
|
||||
#[inline]
|
||||
pub fn new() -> Builder {
|
||||
Builder {
|
||||
exec: Exec::Default,
|
||||
h09_responses: false,
|
||||
h1_writev: None,
|
||||
h1_read_buf_exact_size: None,
|
||||
h1_parser_config: Default::default(),
|
||||
h1_title_case_headers: false,
|
||||
h1_preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
h1_preserve_header_order: false,
|
||||
h1_max_buf_size: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide an executor to execute background HTTP2 tasks.
|
||||
pub fn executor<E>(&mut self, exec: E) -> &mut Builder
|
||||
where
|
||||
E: Executor<BoxSendFuture> + Send + Sync + 'static,
|
||||
{
|
||||
self.exec = Exec::Executor(Arc::new(exec));
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/0.9 responses should be tolerated.
|
||||
///
|
||||
/// Default is false.
|
||||
pub fn http09_responses(&mut self, enabled: bool) -> &mut Builder {
|
||||
self.h09_responses = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections will accept spaces between header names
|
||||
/// and the colon that follow them in responses.
|
||||
///
|
||||
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
|
||||
/// to say about it:
|
||||
///
|
||||
/// > No whitespace is allowed between the header field-name and colon. In
|
||||
/// > the past, differences in the handling of such whitespace have led to
|
||||
/// > security vulnerabilities in request routing and response handling. A
|
||||
/// > server MUST reject any received request message that contains
|
||||
/// > whitespace between a header field-name and colon with a response code
|
||||
/// > of 400 (Bad Request). A proxy MUST remove any such whitespace from a
|
||||
/// > response message before forwarding the message downstream.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
///
|
||||
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||
pub fn http1_allow_spaces_after_header_name_in_responses(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
) -> &mut Builder {
|
||||
self.h1_parser_config
|
||||
.allow_spaces_after_header_name_in_responses(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections will accept obsolete line folding for
|
||||
/// header values.
|
||||
///
|
||||
/// Newline codepoints (`\r` and `\n`) will be transformed to spaces when
|
||||
/// parsing.
|
||||
///
|
||||
/// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has
|
||||
/// to say about it:
|
||||
///
|
||||
/// > A server that receives an obs-fold in a request message that is not
|
||||
/// > within a message/http container MUST either reject the message by
|
||||
/// > sending a 400 (Bad Request), preferably with a representation
|
||||
/// > explaining that obsolete line folding is unacceptable, or replace
|
||||
/// > each received obs-fold with one or more SP octets prior to
|
||||
/// > interpreting the field value or forwarding the message downstream.
|
||||
///
|
||||
/// > A proxy or gateway that receives an obs-fold in a response message
|
||||
/// > that is not within a message/http container MUST either discard the
|
||||
/// > message and replace it with a 502 (Bad Gateway) response, preferably
|
||||
/// > with a representation explaining that unacceptable line folding was
|
||||
/// > received, or replace each received obs-fold with one or more SP
|
||||
/// > octets prior to interpreting the field value or forwarding the
|
||||
/// > message downstream.
|
||||
///
|
||||
/// > A user agent that receives an obs-fold in a response message that is
|
||||
/// > not within a message/http container MUST replace each received
|
||||
/// > obs-fold with one or more SP octets prior to interpreting the field
|
||||
/// > value.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
///
|
||||
/// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4
|
||||
pub fn http1_allow_obsolete_multiline_headers_in_responses(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
) -> &mut Builder {
|
||||
self.h1_parser_config
|
||||
.allow_obsolete_multiline_headers_in_responses(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||
/// or always flatten into a single buffer.
|
||||
///
|
||||
/// Note that setting this to false may mean more copies of body data,
|
||||
/// but may also improve performance when an IO transport doesn't
|
||||
/// support vectored writes well, such as most TLS implementations.
|
||||
///
|
||||
/// Setting this to true will force hyper to use queued strategy
|
||||
/// which may eliminate unnecessary cloning on some TLS backends
|
||||
///
|
||||
/// Default is `auto`. In this mode hyper will try to guess which
|
||||
/// mode to use
|
||||
pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder {
|
||||
self.h1_writev = Some(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections will write header names as title case at
|
||||
/// the socket level.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
pub fn http1_title_case_headers(&mut self, enabled: bool) -> &mut Builder {
|
||||
self.h1_title_case_headers = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to support preserving original header cases.
|
||||
///
|
||||
/// Currently, this will record the original cases received, and store them
|
||||
/// in a private extension on the `Response`. It will also look for and use
|
||||
/// such an extension in any provided `Request`.
|
||||
///
|
||||
/// Since the relevant extension is still private, there is no way to
|
||||
/// interact with the original cases. The only effect this can have now is
|
||||
/// to forward the cases in a proxy-like fashion.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
pub fn http1_preserve_header_case(&mut self, enabled: bool) -> &mut Builder {
|
||||
self.h1_preserve_header_case = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to support preserving original header order.
|
||||
///
|
||||
/// Currently, this will record the order in which headers are received, and store this
|
||||
/// ordering in a private extension on the `Response`. It will also look for and use
|
||||
/// such an extension in any provided `Request`.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
#[cfg(feature = "ffi")]
|
||||
pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder {
|
||||
self.h1_preserve_header_order = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the exact size of the read buffer to *always* use.
|
||||
///
|
||||
/// Note that setting this option unsets the `http1_max_buf_size` option.
|
||||
///
|
||||
/// Default is an adaptive read buffer.
|
||||
pub fn http1_read_buf_exact_size(&mut self, sz: Option<usize>) -> &mut Builder {
|
||||
self.h1_read_buf_exact_size = sz;
|
||||
self.h1_max_buf_size = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum buffer size for the connection.
|
||||
///
|
||||
/// Default is ~400kb.
|
||||
///
|
||||
/// Note that setting this option unsets the `http1_read_exact_buf_size` option.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_max_buf_size(&mut self, max: usize) -> &mut Self {
|
||||
assert!(
|
||||
max >= proto::h1::MINIMUM_MAX_BUFFER_SIZE,
|
||||
"the max_buf_size cannot be smaller than the minimum that h1 specifies."
|
||||
);
|
||||
|
||||
self.h1_max_buf_size = Some(max);
|
||||
self.h1_read_buf_exact_size = None;
|
||||
self
|
||||
}
|
||||
|
||||
/// Constructs a connection with the configured options and IO.
|
||||
/// See [`client::conn`](crate::client::conn) for more.
|
||||
///
|
||||
/// Note, if [`Connection`] is not `await`-ed, [`SendRequest`] will
|
||||
/// do nothing.
|
||||
pub fn handshake<T, B>(
|
||||
&self,
|
||||
io: T,
|
||||
) -> impl Future<Output = crate::Result<(SendRequest<B>, Connection<T, B>)>>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
let opts = self.clone();
|
||||
|
||||
async move {
|
||||
tracing::trace!("client handshake HTTP/1");
|
||||
|
||||
let (tx, rx) = dispatch::channel();
|
||||
let mut conn = proto::Conn::new(io);
|
||||
conn.set_h1_parser_config(opts.h1_parser_config);
|
||||
if let Some(writev) = opts.h1_writev {
|
||||
if writev {
|
||||
conn.set_write_strategy_queue();
|
||||
} else {
|
||||
conn.set_write_strategy_flatten();
|
||||
}
|
||||
}
|
||||
if opts.h1_title_case_headers {
|
||||
conn.set_title_case_headers();
|
||||
}
|
||||
if opts.h1_preserve_header_case {
|
||||
conn.set_preserve_header_case();
|
||||
}
|
||||
#[cfg(feature = "ffi")]
|
||||
if opts.h1_preserve_header_order {
|
||||
conn.set_preserve_header_order();
|
||||
}
|
||||
if opts.h09_responses {
|
||||
conn.set_h09_responses();
|
||||
}
|
||||
|
||||
if let Some(sz) = opts.h1_read_buf_exact_size {
|
||||
conn.set_read_buf_exact_size(sz);
|
||||
}
|
||||
if let Some(max) = opts.h1_max_buf_size {
|
||||
conn.set_max_buf_size(max);
|
||||
}
|
||||
let cd = proto::h1::dispatch::Client::new(rx);
|
||||
let proto = proto::h1::Dispatcher::new(cd, conn);
|
||||
|
||||
Ok((
|
||||
SendRequest { dispatch: tx },
|
||||
Connection { inner: Some(proto) },
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
436
src/client/conn/http2.rs
Normal file
436
src/client/conn/http2.rs
Normal file
@@ -0,0 +1,436 @@
|
||||
//! HTTP/2 client connections
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
#[cfg(feature = "runtime")]
|
||||
use std::time::Duration;
|
||||
|
||||
use http::{Request, Response};
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use crate::Body;
|
||||
use crate::body::HttpBody;
|
||||
use crate::common::{
|
||||
exec::{BoxSendFuture, Exec},
|
||||
task, Future, Pin, Poll,
|
||||
};
|
||||
use crate::proto;
|
||||
use crate::rt::Executor;
|
||||
use super::super::dispatch;
|
||||
|
||||
/// The sender side of an established connection.
|
||||
pub struct SendRequest<B> {
|
||||
dispatch: dispatch::UnboundedSender<Request<B>, Response<Body>>,
|
||||
}
|
||||
|
||||
/// A future that processes all HTTP state for the IO object.
|
||||
///
|
||||
/// In most cases, this should just be spawned into an executor, so that it
|
||||
/// can process incoming and outgoing messages, notice hangups, and the like.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
pub struct Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
inner: (PhantomData<T>, proto::h2::ClientTask<B>),
|
||||
}
|
||||
|
||||
/// A builder to configure an HTTP connection.
|
||||
///
|
||||
/// After setting options, the builder is used to create a handshake future.
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Builder {
|
||||
pub(super) exec: Exec,
|
||||
h2_builder: proto::h2::client::Config,
|
||||
}
|
||||
|
||||
/// Returns a handshake future over some IO.
|
||||
///
|
||||
/// This is a shortcut for `Builder::new().handshake(io)`.
|
||||
/// See [`client::conn`](crate::client::conn) for more.
|
||||
pub async fn handshake<T>(
|
||||
io: T,
|
||||
) -> crate::Result<(SendRequest<crate::Body>, Connection<T, crate::Body>)>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
{
|
||||
Builder::new().handshake(io).await
|
||||
}
|
||||
|
||||
// ===== impl SendRequest
|
||||
|
||||
impl<B> SendRequest<B> {
|
||||
/// Polls to determine whether this sender can be used yet for a request.
|
||||
///
|
||||
/// If the associated connection is closed, this returns an Error.
|
||||
pub fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||
if self.is_closed() {
|
||||
Poll::Ready(Err(crate::Error::new_closed()))
|
||||
} else {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub(super) async fn when_ready(self) -> crate::Result<Self> {
|
||||
let mut me = Some(self);
|
||||
future::poll_fn(move |cx| {
|
||||
ready!(me.as_mut().unwrap().poll_ready(cx))?;
|
||||
Poll::Ready(Ok(me.take().unwrap()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) fn is_ready(&self) -> bool {
|
||||
self.dispatch.is_ready()
|
||||
}
|
||||
*/
|
||||
|
||||
pub(super) fn is_closed(&self) -> bool {
|
||||
self.dispatch.is_closed()
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> SendRequest<B>
|
||||
where
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
/// Sends a `Request` on the associated connection.
|
||||
///
|
||||
/// Returns a future that if successful, yields the `Response`.
|
||||
///
|
||||
/// # Note
|
||||
///
|
||||
/// There are some key differences in what automatic things the `Client`
|
||||
/// does for you that will not be done here:
|
||||
///
|
||||
/// - `Client` requires absolute-form `Uri`s, since the scheme and
|
||||
/// authority are needed to connect. They aren't required here.
|
||||
/// - Since the `Client` requires absolute-form `Uri`s, it can add
|
||||
/// the `Host` header based on it. You must add a `Host` header yourself
|
||||
/// before calling this method.
|
||||
/// - Since absolute-form `Uri`s are not required, if received, they will
|
||||
/// be serialized as-is.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # use http::header::HOST;
|
||||
/// # use hyper::client::conn::SendRequest;
|
||||
/// # use hyper::Body;
|
||||
/// use hyper::Request;
|
||||
///
|
||||
/// # async fn doc(mut tx: SendRequest<Body>) -> hyper::Result<()> {
|
||||
/// // build a Request
|
||||
/// let req = Request::builder()
|
||||
/// .uri("/foo/bar")
|
||||
/// .header(HOST, "hyper.rs")
|
||||
/// .body(Body::empty())
|
||||
/// .unwrap();
|
||||
///
|
||||
/// // send it and await a Response
|
||||
/// let res = tx.send_request(req).await?;
|
||||
/// // assert the Response
|
||||
/// assert!(res.status().is_success());
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn send_request(&mut self, req: Request<B>) -> impl Future<Output = crate::Result<Response<Body>>> {
|
||||
let sent = self.dispatch.send(req);
|
||||
|
||||
async move {
|
||||
match sent {
|
||||
Ok(rx) => match rx.await {
|
||||
Ok(Ok(resp)) => Ok(resp),
|
||||
Ok(Err(err)) => Err(err),
|
||||
// this is definite bug if it happens, but it shouldn't happen!
|
||||
Err(_canceled) => panic!("dispatch dropped without returning error"),
|
||||
}
|
||||
Err(_req) => {
|
||||
tracing::debug!("connection was not ready");
|
||||
|
||||
Err(crate::Error::new_canceled().with("connection was not ready"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
pub(super) fn send_request_retryable(
|
||||
&mut self,
|
||||
req: Request<B>,
|
||||
) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> + Unpin
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
match self.dispatch.try_send(req) {
|
||||
Ok(rx) => {
|
||||
Either::Left(rx.then(move |res| {
|
||||
match res {
|
||||
Ok(Ok(res)) => future::ok(res),
|
||||
Ok(Err(err)) => future::err(err),
|
||||
// this is definite bug if it happens, but it shouldn't happen!
|
||||
Err(_) => panic!("dispatch dropped without returning error"),
|
||||
}
|
||||
}))
|
||||
}
|
||||
Err(req) => {
|
||||
tracing::debug!("connection was not ready");
|
||||
let err = crate::Error::new_canceled().with("connection was not ready");
|
||||
Either::Right(future::err((err, Some(req))))
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
}
|
||||
|
||||
impl<B> fmt::Debug for SendRequest<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("SendRequest").finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Connection
|
||||
|
||||
impl<T, B> fmt::Debug for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + fmt::Debug + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Connection").finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, B> Future for Connection<T, B>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
B: HttpBody + Send + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
type Output = crate::Result<()>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
match ready!(Pin::new(&mut self.inner.1).poll(cx))? {
|
||||
proto::Dispatched::Shutdown => Poll::Ready(Ok(())),
|
||||
#[cfg(feature = "http1")]
|
||||
proto::Dispatched::Upgrade(_pending) => unreachable!("http2 cannot upgrade"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Builder
|
||||
|
||||
impl Builder {
|
||||
/// Creates a new connection builder.
|
||||
#[inline]
|
||||
pub fn new() -> Builder {
|
||||
Builder {
|
||||
exec: Exec::Default,
|
||||
h2_builder: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Provide an executor to execute background HTTP2 tasks.
|
||||
pub fn executor<E>(&mut self, exec: E) -> &mut Builder
|
||||
where
|
||||
E: Executor<BoxSendFuture> + Send + Sync + 'static,
|
||||
{
|
||||
self.exec = Exec::Executor(Arc::new(exec));
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
|
||||
/// stream-level flow control.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
///
|
||||
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_initial_stream_window_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
|
||||
if let Some(sz) = sz.into() {
|
||||
self.h2_builder.adaptive_window = false;
|
||||
self.h2_builder.initial_stream_window_size = sz;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the max connection-level flow control for HTTP2
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_initial_connection_window_size(
|
||||
&mut self,
|
||||
sz: impl Into<Option<u32>>,
|
||||
) -> &mut Self {
|
||||
if let Some(sz) = sz.into() {
|
||||
self.h2_builder.adaptive_window = false;
|
||||
self.h2_builder.initial_conn_window_size = sz;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether to use an adaptive flow control.
|
||||
///
|
||||
/// Enabling this will override the limits set in
|
||||
/// `http2_initial_stream_window_size` and
|
||||
/// `http2_initial_connection_window_size`.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_adaptive_window(&mut self, enabled: bool) -> &mut Self {
|
||||
use proto::h2::SPEC_WINDOW_SIZE;
|
||||
|
||||
self.h2_builder.adaptive_window = enabled;
|
||||
if enabled {
|
||||
self.h2_builder.initial_conn_window_size = SPEC_WINDOW_SIZE;
|
||||
self.h2_builder.initial_stream_window_size = SPEC_WINDOW_SIZE;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum frame size to use for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_frame_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
|
||||
if let Some(sz) = sz.into() {
|
||||
self.h2_builder.max_frame_size = sz;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an interval for HTTP2 Ping frames should be sent to keep a
|
||||
/// connection alive.
|
||||
///
|
||||
/// Pass `None` to disable HTTP2 keep-alive.
|
||||
///
|
||||
/// Default is currently disabled.
|
||||
///
|
||||
/// # Cargo Feature
|
||||
///
|
||||
/// Requires the `runtime` cargo feature to be enabled.
|
||||
#[cfg(feature = "runtime")]
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_keep_alive_interval(
|
||||
&mut self,
|
||||
interval: impl Into<Option<Duration>>,
|
||||
) -> &mut Self {
|
||||
self.h2_builder.keep_alive_interval = interval.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
|
||||
///
|
||||
/// If the ping is not acknowledged within the timeout, the connection will
|
||||
/// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
|
||||
///
|
||||
/// Default is 20 seconds.
|
||||
///
|
||||
/// # Cargo Feature
|
||||
///
|
||||
/// Requires the `runtime` cargo feature to be enabled.
|
||||
#[cfg(feature = "runtime")]
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self {
|
||||
self.h2_builder.keep_alive_timeout = timeout;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether HTTP2 keep-alive should apply while the connection is idle.
|
||||
///
|
||||
/// If disabled, keep-alive pings are only sent while there are open
|
||||
/// request/responses streams. If enabled, pings are also sent when no
|
||||
/// streams are active. Does nothing if `http2_keep_alive_interval` is
|
||||
/// disabled.
|
||||
///
|
||||
/// Default is `false`.
|
||||
///
|
||||
/// # Cargo Feature
|
||||
///
|
||||
/// Requires the `runtime` cargo feature to be enabled.
|
||||
#[cfg(feature = "runtime")]
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_keep_alive_while_idle(&mut self, enabled: bool) -> &mut Self {
|
||||
self.h2_builder.keep_alive_while_idle = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum number of HTTP2 concurrent locally reset streams.
|
||||
///
|
||||
/// See the documentation of [`h2::client::Builder::max_concurrent_reset_streams`] for more
|
||||
/// details.
|
||||
///
|
||||
/// The default value is determined by the `h2` crate.
|
||||
///
|
||||
/// [`h2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_concurrent_reset_streams(&mut self, max: usize) -> &mut Self {
|
||||
self.h2_builder.max_concurrent_reset_streams = Some(max);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum write buffer size for each HTTP/2 stream.
|
||||
///
|
||||
/// Default is currently 1MB, but may change.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The value must be no larger than `u32::MAX`.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_send_buf_size(&mut self, max: usize) -> &mut Self {
|
||||
assert!(max <= std::u32::MAX as usize);
|
||||
self.h2_builder.max_send_buffer_size = max;
|
||||
self
|
||||
}
|
||||
|
||||
/// Constructs a connection with the configured options and IO.
|
||||
/// See [`client::conn`](crate::client::conn) for more.
|
||||
///
|
||||
/// Note, if [`Connection`] is not `await`-ed, [`SendRequest`] will
|
||||
/// do nothing.
|
||||
pub fn handshake<T, B>(
|
||||
&self,
|
||||
io: T,
|
||||
) -> impl Future<Output = crate::Result<(SendRequest<B>, Connection<T, B>)>>
|
||||
where
|
||||
T: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
B: HttpBody + 'static,
|
||||
B::Data: Send,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
let opts = self.clone();
|
||||
|
||||
async move {
|
||||
tracing::trace!("client handshake HTTP/1");
|
||||
|
||||
let (tx, rx) = dispatch::channel();
|
||||
let h2 =
|
||||
proto::h2::client::handshake(io, rx, &opts.h2_builder, opts.exec)
|
||||
.await?;
|
||||
Ok((
|
||||
SendRequest { dispatch: tx.unbound() },
|
||||
Connection { inner: (PhantomData, h2) },
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,7 +63,7 @@ use std::sync::Arc;
|
||||
use std::time::Duration;
|
||||
|
||||
use bytes::Bytes;
|
||||
use futures_util::future::{self, Either, FutureExt as _};
|
||||
use futures_util::future;
|
||||
use httparse::ParserConfig;
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
@@ -84,6 +84,11 @@ use crate::rt::Executor;
|
||||
use crate::upgrade::Upgraded;
|
||||
use crate::{Body, Request, Response};
|
||||
|
||||
#[cfg(feature = "http1")]
|
||||
pub mod http1;
|
||||
#[cfg(feature = "http2")]
|
||||
pub mod http2;
|
||||
|
||||
#[cfg(feature = "http1")]
|
||||
type Http1Dispatcher<T, B> =
|
||||
proto::dispatch::Dispatcher<proto::dispatch::Client<B>, B, T, proto::h1::ClientTransaction>;
|
||||
@@ -156,6 +161,8 @@ pub struct Builder {
|
||||
h1_writev: Option<bool>,
|
||||
h1_title_case_headers: bool,
|
||||
h1_preserve_header_case: bool,
|
||||
#[cfg(feature = "ffi")]
|
||||
h1_preserve_header_order: bool,
|
||||
h1_read_buf_exact_size: Option<usize>,
|
||||
h1_max_buf_size: Option<usize>,
|
||||
#[cfg(feature = "ffi")]
|
||||
@@ -207,16 +214,6 @@ pub struct Parts<T> {
|
||||
_inner: (),
|
||||
}
|
||||
|
||||
// ========== internal client api
|
||||
|
||||
// A `SendRequest` that can be cloned to send HTTP2 requests.
|
||||
// private for now, probably not a great idea of a type...
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
#[cfg(feature = "http2")]
|
||||
pub(super) struct Http2SendRequest<B> {
|
||||
dispatch: dispatch::UnboundedSender<Request<B>, Response<Body>>,
|
||||
}
|
||||
|
||||
// ===== impl SendRequest
|
||||
|
||||
impl<B> SendRequest<B> {
|
||||
@@ -226,30 +223,6 @@ impl<B> SendRequest<B> {
|
||||
pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||
self.dispatch.poll_ready(cx)
|
||||
}
|
||||
|
||||
pub(super) async fn when_ready(self) -> crate::Result<Self> {
|
||||
let mut me = Some(self);
|
||||
future::poll_fn(move |cx| {
|
||||
ready!(me.as_mut().unwrap().poll_ready(cx))?;
|
||||
Poll::Ready(Ok(me.take().unwrap()))
|
||||
})
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) fn is_ready(&self) -> bool {
|
||||
self.dispatch.is_ready()
|
||||
}
|
||||
|
||||
pub(super) fn is_closed(&self) -> bool {
|
||||
self.dispatch.is_closed()
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
pub(super) fn into_http2(self) -> Http2SendRequest<B> {
|
||||
Http2SendRequest {
|
||||
dispatch: self.dispatch.unbound(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> SendRequest<B>
|
||||
@@ -309,32 +282,6 @@ where
|
||||
|
||||
ResponseFuture { inner }
|
||||
}
|
||||
|
||||
pub(super) fn send_request_retryable(
|
||||
&mut self,
|
||||
req: Request<B>,
|
||||
) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> + Unpin
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
match self.dispatch.try_send(req) {
|
||||
Ok(rx) => {
|
||||
Either::Left(rx.then(move |res| {
|
||||
match res {
|
||||
Ok(Ok(res)) => future::ok(res),
|
||||
Ok(Err(err)) => future::err(err),
|
||||
// this is definite bug if it happens, but it shouldn't happen!
|
||||
Err(_) => panic!("dispatch dropped without returning error"),
|
||||
}
|
||||
}))
|
||||
}
|
||||
Err(req) => {
|
||||
debug!("connection was not ready");
|
||||
let err = crate::Error::new_canceled().with("connection was not ready");
|
||||
Either::Right(future::err((err, Some(req))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<B> Service<Request<B>> for SendRequest<B>
|
||||
@@ -360,67 +307,6 @@ impl<B> fmt::Debug for SendRequest<B> {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Http2SendRequest
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<B> Http2SendRequest<B> {
|
||||
pub(super) fn is_ready(&self) -> bool {
|
||||
self.dispatch.is_ready()
|
||||
}
|
||||
|
||||
pub(super) fn is_closed(&self) -> bool {
|
||||
self.dispatch.is_closed()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<B> Http2SendRequest<B>
|
||||
where
|
||||
B: HttpBody + 'static,
|
||||
{
|
||||
pub(super) fn send_request_retryable(
|
||||
&mut self,
|
||||
req: Request<B>,
|
||||
) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>>
|
||||
where
|
||||
B: Send,
|
||||
{
|
||||
match self.dispatch.try_send(req) {
|
||||
Ok(rx) => {
|
||||
Either::Left(rx.then(move |res| {
|
||||
match res {
|
||||
Ok(Ok(res)) => future::ok(res),
|
||||
Ok(Err(err)) => future::err(err),
|
||||
// this is definite bug if it happens, but it shouldn't happen!
|
||||
Err(_) => panic!("dispatch dropped without returning error"),
|
||||
}
|
||||
}))
|
||||
}
|
||||
Err(req) => {
|
||||
debug!("connection was not ready");
|
||||
let err = crate::Error::new_canceled().with("connection was not ready");
|
||||
Either::Right(future::err((err, Some(req))))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<B> fmt::Debug for Http2SendRequest<B> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Http2SendRequest").finish()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<B> Clone for Http2SendRequest<B> {
|
||||
fn clone(&self) -> Self {
|
||||
Http2SendRequest {
|
||||
dispatch: self.dispatch.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Connection
|
||||
|
||||
impl<T, B> Connection<T, B>
|
||||
@@ -492,7 +378,7 @@ where
|
||||
///
|
||||
/// This setting is configured by the server peer by sending the
|
||||
/// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
|
||||
/// This method returns the currently acknowledged value recieved from the
|
||||
/// This method returns the currently acknowledged value received from the
|
||||
/// remote.
|
||||
///
|
||||
/// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
||||
@@ -558,6 +444,8 @@ impl Builder {
|
||||
h1_parser_config: Default::default(),
|
||||
h1_title_case_headers: false,
|
||||
h1_preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
h1_preserve_header_order: false,
|
||||
h1_max_buf_size: None,
|
||||
#[cfg(feature = "ffi")]
|
||||
h1_headers_raw: false,
|
||||
@@ -704,6 +592,21 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to support preserving original header order.
|
||||
///
|
||||
/// Currently, this will record the order in which headers are received, and store this
|
||||
/// ordering in a private extension on the `Response`. It will also look for and use
|
||||
/// such an extension in any provided `Request`.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
#[cfg(feature = "ffi")]
|
||||
pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder {
|
||||
self.h1_preserve_header_order = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the exact size of the read buffer to *always* use.
|
||||
///
|
||||
/// Note that setting this option unsets the `http1_max_buf_size` option.
|
||||
@@ -812,10 +715,14 @@ impl Builder {
|
||||
/// Sets the maximum frame size to use for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_frame_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
|
||||
self.h2_builder.max_frame_size = sz.into();
|
||||
if let Some(sz) = sz.into() {
|
||||
self.h2_builder.max_frame_size = sz;
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
@@ -908,46 +815,6 @@ impl Builder {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum concurrent streams to use for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_concurrent_streams(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
|
||||
self.h2_builder.max_concurrent_streams = sz.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the max header list size to use for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_header_list_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
|
||||
self.h2_builder.max_header_list_size = sz.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables and disables the push feature for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_enable_push(&mut self, sz: impl Into<Option<bool>>) -> &mut Self {
|
||||
self.h2_builder.enable_push = sz.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the header table size to use for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_header_table_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self {
|
||||
self.h2_builder.header_table_size = sz.into();
|
||||
self
|
||||
}
|
||||
|
||||
/// Constructs a connection with the configured options and IO.
|
||||
/// See [`client::conn`](crate::client::conn) for more.
|
||||
///
|
||||
@@ -987,6 +854,10 @@ impl Builder {
|
||||
if opts.h1_preserve_header_case {
|
||||
conn.set_preserve_header_case();
|
||||
}
|
||||
#[cfg(feature = "ffi")]
|
||||
if opts.h1_preserve_header_order {
|
||||
conn.set_preserve_header_order();
|
||||
}
|
||||
if opts.h09_responses {
|
||||
conn.set_h09_responses();
|
||||
}
|
||||
@@ -1,425 +0,0 @@
|
||||
//! DNS Resolution used by the `HttpConnector`.
|
||||
//!
|
||||
//! This module contains:
|
||||
//!
|
||||
//! - A [`GaiResolver`](GaiResolver) that is the default resolver for the
|
||||
//! `HttpConnector`.
|
||||
//! - The `Name` type used as an argument to custom resolvers.
|
||||
//!
|
||||
//! # Resolvers are `Service`s
|
||||
//!
|
||||
//! A resolver is just a
|
||||
//! `Service<Name, Response = impl Iterator<Item = SocketAddr>>`.
|
||||
//!
|
||||
//! A simple resolver that ignores the name and always returns a specific
|
||||
//! address:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! use std::{convert::Infallible, iter, net::SocketAddr};
|
||||
//!
|
||||
//! let resolver = tower::service_fn(|_name| async {
|
||||
//! Ok::<_, Infallible>(iter::once(SocketAddr::from(([127, 0, 0, 1], 8080))))
|
||||
//! });
|
||||
//! ```
|
||||
use std::error::Error;
|
||||
use std::future::Future;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
||||
use std::pin::Pin;
|
||||
use std::str::FromStr;
|
||||
use std::task::{self, Poll};
|
||||
use std::{fmt, io, vec};
|
||||
|
||||
use tokio::task::JoinHandle;
|
||||
use tower_service::Service;
|
||||
use tracing::debug;
|
||||
|
||||
pub(super) use self::sealed::Resolve;
|
||||
|
||||
/// A domain name to resolve into IP addresses.
|
||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||
pub struct Name {
|
||||
host: Box<str>,
|
||||
}
|
||||
|
||||
/// A resolver using blocking `getaddrinfo` calls in a threadpool.
|
||||
#[derive(Clone)]
|
||||
pub struct GaiResolver {
|
||||
_priv: (),
|
||||
}
|
||||
|
||||
/// An iterator of IP addresses returned from `getaddrinfo`.
|
||||
pub struct GaiAddrs {
|
||||
inner: SocketAddrs,
|
||||
}
|
||||
|
||||
/// A future to resolve a name returned by `GaiResolver`.
|
||||
pub struct GaiFuture {
|
||||
inner: JoinHandle<Result<SocketAddrs, io::Error>>,
|
||||
}
|
||||
|
||||
impl Name {
|
||||
pub(super) fn new(host: Box<str>) -> Name {
|
||||
Name { host }
|
||||
}
|
||||
|
||||
/// View the hostname as a string slice.
|
||||
pub fn as_str(&self) -> &str {
|
||||
&self.host
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Debug::fmt(&self.host, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Display for Name {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
fmt::Display::fmt(&self.host, f)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Name {
|
||||
type Err = InvalidNameError;
|
||||
|
||||
fn from_str(host: &str) -> Result<Self, Self::Err> {
|
||||
// Possibly add validation later
|
||||
Ok(Name::new(host.into()))
|
||||
}
|
||||
}
|
||||
|
||||
/// Error indicating a given string was not a valid domain name.
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidNameError(());
|
||||
|
||||
impl fmt::Display for InvalidNameError {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.write_str("Not a valid domain name")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for InvalidNameError {}
|
||||
|
||||
impl GaiResolver {
|
||||
/// Construct a new `GaiResolver`.
|
||||
pub fn new() -> Self {
|
||||
GaiResolver { _priv: () }
|
||||
}
|
||||
}
|
||||
|
||||
impl Service<Name> for GaiResolver {
|
||||
type Response = GaiAddrs;
|
||||
type Error = io::Error;
|
||||
type Future = GaiFuture;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, name: Name) -> Self::Future {
|
||||
let blocking = tokio::task::spawn_blocking(move || {
|
||||
debug!("resolving host={:?}", name.host);
|
||||
(&*name.host, 0)
|
||||
.to_socket_addrs()
|
||||
.map(|i| SocketAddrs { iter: i })
|
||||
});
|
||||
|
||||
GaiFuture { inner: blocking }
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for GaiResolver {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("GaiResolver")
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for GaiFuture {
|
||||
type Output = Result<GaiAddrs, io::Error>;
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.inner).poll(cx).map(|res| match res {
|
||||
Ok(Ok(addrs)) => Ok(GaiAddrs { inner: addrs }),
|
||||
Ok(Err(err)) => Err(err),
|
||||
Err(join_err) => {
|
||||
if join_err.is_cancelled() {
|
||||
Err(io::Error::new(io::ErrorKind::Interrupted, join_err))
|
||||
} else {
|
||||
panic!("gai background task failed: {:?}", join_err)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for GaiFuture {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("GaiFuture")
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for GaiFuture {
|
||||
fn drop(&mut self) {
|
||||
self.inner.abort();
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for GaiAddrs {
|
||||
type Item = SocketAddr;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.inner.next()
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for GaiAddrs {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.pad("GaiAddrs")
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct SocketAddrs {
|
||||
iter: vec::IntoIter<SocketAddr>,
|
||||
}
|
||||
|
||||
impl SocketAddrs {
|
||||
pub(super) fn new(addrs: Vec<SocketAddr>) -> Self {
|
||||
SocketAddrs {
|
||||
iter: addrs.into_iter(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn try_parse(host: &str, port: u16) -> Option<SocketAddrs> {
|
||||
if let Ok(addr) = host.parse::<Ipv4Addr>() {
|
||||
let addr = SocketAddrV4::new(addr, port);
|
||||
return Some(SocketAddrs {
|
||||
iter: vec![SocketAddr::V4(addr)].into_iter(),
|
||||
});
|
||||
}
|
||||
if let Ok(addr) = host.parse::<Ipv6Addr>() {
|
||||
let addr = SocketAddrV6::new(addr, port, 0, 0);
|
||||
return Some(SocketAddrs {
|
||||
iter: vec![SocketAddr::V6(addr)].into_iter(),
|
||||
});
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn filter(self, predicate: impl FnMut(&SocketAddr) -> bool) -> SocketAddrs {
|
||||
SocketAddrs::new(self.iter.filter(predicate).collect())
|
||||
}
|
||||
|
||||
pub(super) fn split_by_preference(
|
||||
self,
|
||||
local_addr_ipv4: Option<Ipv4Addr>,
|
||||
local_addr_ipv6: Option<Ipv6Addr>,
|
||||
) -> (SocketAddrs, SocketAddrs) {
|
||||
match (local_addr_ipv4, local_addr_ipv6) {
|
||||
(Some(_), None) => (self.filter(SocketAddr::is_ipv4), SocketAddrs::new(vec![])),
|
||||
(None, Some(_)) => (self.filter(SocketAddr::is_ipv6), SocketAddrs::new(vec![])),
|
||||
_ => {
|
||||
let preferring_v6 = self
|
||||
.iter
|
||||
.as_slice()
|
||||
.first()
|
||||
.map(SocketAddr::is_ipv6)
|
||||
.unwrap_or(false);
|
||||
|
||||
let (preferred, fallback) = self
|
||||
.iter
|
||||
.partition::<Vec<_>, _>(|addr| addr.is_ipv6() == preferring_v6);
|
||||
|
||||
(SocketAddrs::new(preferred), SocketAddrs::new(fallback))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn is_empty(&self) -> bool {
|
||||
self.iter.as_slice().is_empty()
|
||||
}
|
||||
|
||||
pub(super) fn len(&self) -> usize {
|
||||
self.iter.as_slice().len()
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterator for SocketAddrs {
|
||||
type Item = SocketAddr;
|
||||
#[inline]
|
||||
fn next(&mut self) -> Option<SocketAddr> {
|
||||
self.iter.next()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
/// A resolver using `getaddrinfo` calls via the `tokio_executor::threadpool::blocking` API.
|
||||
///
|
||||
/// Unlike the `GaiResolver` this will not spawn dedicated threads, but only works when running on the
|
||||
/// multi-threaded Tokio runtime.
|
||||
#[cfg(feature = "runtime")]
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct TokioThreadpoolGaiResolver(());
|
||||
|
||||
/// The future returned by `TokioThreadpoolGaiResolver`.
|
||||
#[cfg(feature = "runtime")]
|
||||
#[derive(Debug)]
|
||||
pub struct TokioThreadpoolGaiFuture {
|
||||
name: Name,
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl TokioThreadpoolGaiResolver {
|
||||
/// Creates a new DNS resolver that will use tokio threadpool's blocking
|
||||
/// feature.
|
||||
///
|
||||
/// **Requires** its futures to be run on the threadpool runtime.
|
||||
pub fn new() -> Self {
|
||||
TokioThreadpoolGaiResolver(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl Service<Name> for TokioThreadpoolGaiResolver {
|
||||
type Response = GaiAddrs;
|
||||
type Error = io::Error;
|
||||
type Future = TokioThreadpoolGaiFuture;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, name: Name) -> Self::Future {
|
||||
TokioThreadpoolGaiFuture { name }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "runtime")]
|
||||
impl Future for TokioThreadpoolGaiFuture {
|
||||
type Output = Result<GaiAddrs, io::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
match ready!(tokio_executor::threadpool::blocking(|| (
|
||||
self.name.as_str(),
|
||||
0
|
||||
)
|
||||
.to_socket_addrs()))
|
||||
{
|
||||
Ok(Ok(iter)) => Poll::Ready(Ok(GaiAddrs {
|
||||
inner: IpAddrs { iter },
|
||||
})),
|
||||
Ok(Err(e)) => Poll::Ready(Err(e)),
|
||||
// a BlockingError, meaning not on a tokio_executor::threadpool :(
|
||||
Err(e) => Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, e))),
|
||||
}
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
mod sealed {
|
||||
use super::{SocketAddr, Name};
|
||||
use crate::common::{task, Future, Poll};
|
||||
use tower_service::Service;
|
||||
|
||||
// "Trait alias" for `Service<Name, Response = Addrs>`
|
||||
pub trait Resolve {
|
||||
type Addrs: Iterator<Item = SocketAddr>;
|
||||
type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
|
||||
type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||
fn resolve(&mut self, name: Name) -> Self::Future;
|
||||
}
|
||||
|
||||
impl<S> Resolve for S
|
||||
where
|
||||
S: Service<Name>,
|
||||
S::Response: Iterator<Item = SocketAddr>,
|
||||
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||
{
|
||||
type Addrs = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Service::poll_ready(self, cx)
|
||||
}
|
||||
|
||||
fn resolve(&mut self, name: Name) -> Self::Future {
|
||||
Service::call(self, name)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn resolve<R>(resolver: &mut R, name: Name) -> Result<R::Addrs, R::Error>
|
||||
where
|
||||
R: Resolve,
|
||||
{
|
||||
futures_util::future::poll_fn(|cx| resolver.poll_ready(cx)).await?;
|
||||
resolver.resolve(name).await
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||
|
||||
#[test]
|
||||
fn test_ip_addrs_split_by_preference() {
|
||||
let ip_v4 = Ipv4Addr::new(127, 0, 0, 1);
|
||||
let ip_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
|
||||
let v4_addr = (ip_v4, 80).into();
|
||||
let v6_addr = (ip_v6, 80).into();
|
||||
|
||||
let (mut preferred, mut fallback) = SocketAddrs {
|
||||
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||
}
|
||||
.split_by_preference(None, None);
|
||||
assert!(preferred.next().unwrap().is_ipv4());
|
||||
assert!(fallback.next().unwrap().is_ipv6());
|
||||
|
||||
let (mut preferred, mut fallback) = SocketAddrs {
|
||||
iter: vec![v6_addr, v4_addr].into_iter(),
|
||||
}
|
||||
.split_by_preference(None, None);
|
||||
assert!(preferred.next().unwrap().is_ipv6());
|
||||
assert!(fallback.next().unwrap().is_ipv4());
|
||||
|
||||
let (mut preferred, mut fallback) = SocketAddrs {
|
||||
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||
}
|
||||
.split_by_preference(Some(ip_v4), Some(ip_v6));
|
||||
assert!(preferred.next().unwrap().is_ipv4());
|
||||
assert!(fallback.next().unwrap().is_ipv6());
|
||||
|
||||
let (mut preferred, mut fallback) = SocketAddrs {
|
||||
iter: vec![v6_addr, v4_addr].into_iter(),
|
||||
}
|
||||
.split_by_preference(Some(ip_v4), Some(ip_v6));
|
||||
assert!(preferred.next().unwrap().is_ipv6());
|
||||
assert!(fallback.next().unwrap().is_ipv4());
|
||||
|
||||
let (mut preferred, fallback) = SocketAddrs {
|
||||
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||
}
|
||||
.split_by_preference(Some(ip_v4), None);
|
||||
assert!(preferred.next().unwrap().is_ipv4());
|
||||
assert!(fallback.is_empty());
|
||||
|
||||
let (mut preferred, fallback) = SocketAddrs {
|
||||
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||
}
|
||||
.split_by_preference(None, Some(ip_v6));
|
||||
assert!(preferred.next().unwrap().is_ipv6());
|
||||
assert!(fallback.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_name_from_str() {
|
||||
const DOMAIN: &str = "test.example.com";
|
||||
let name = Name::from_str(DOMAIN).expect("Should be a valid domain");
|
||||
assert_eq!(name.as_str(), DOMAIN);
|
||||
assert_eq!(name.to_string(), DOMAIN);
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,412 +0,0 @@
|
||||
//! Connectors used by the `Client`.
|
||||
//!
|
||||
//! This module contains:
|
||||
//!
|
||||
//! - A default [`HttpConnector`][] that does DNS resolution and establishes
|
||||
//! connections over TCP.
|
||||
//! - Types to build custom connectors.
|
||||
//!
|
||||
//! # Connectors
|
||||
//!
|
||||
//! A "connector" is a [`Service`][] that takes a [`Uri`][] destination, and
|
||||
//! its `Response` is some type implementing [`AsyncRead`][], [`AsyncWrite`][],
|
||||
//! and [`Connection`][].
|
||||
//!
|
||||
//! ## Custom Connectors
|
||||
//!
|
||||
//! A simple connector that ignores the `Uri` destination and always returns
|
||||
//! a TCP connection to the same address could be written like this:
|
||||
//!
|
||||
//! ```rust,ignore
|
||||
//! let connector = tower::service_fn(|_dst| async {
|
||||
//! tokio::net::TcpStream::connect("127.0.0.1:1337")
|
||||
//! })
|
||||
//! ```
|
||||
//!
|
||||
//! Or, fully written out:
|
||||
//!
|
||||
//! ```
|
||||
//! # #[cfg(feature = "runtime")]
|
||||
//! # mod rt {
|
||||
//! use std::{future::Future, net::SocketAddr, pin::Pin, task::{self, Poll}};
|
||||
//! use hyper::{service::Service, Uri};
|
||||
//! use tokio::net::TcpStream;
|
||||
//!
|
||||
//! #[derive(Clone)]
|
||||
//! struct LocalConnector;
|
||||
//!
|
||||
//! impl Service<Uri> for LocalConnector {
|
||||
//! type Response = TcpStream;
|
||||
//! type Error = std::io::Error;
|
||||
//! // We can't "name" an `async` generated future.
|
||||
//! type Future = Pin<Box<
|
||||
//! dyn Future<Output = Result<Self::Response, Self::Error>> + Send
|
||||
//! >>;
|
||||
//!
|
||||
//! fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
//! // This connector is always ready, but others might not be.
|
||||
//! Poll::Ready(Ok(()))
|
||||
//! }
|
||||
//!
|
||||
//! fn call(&mut self, _: Uri) -> Self::Future {
|
||||
//! Box::pin(TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 1337))))
|
||||
//! }
|
||||
//! }
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//! It's worth noting that for `TcpStream`s, the [`HttpConnector`][] is a
|
||||
//! better starting place to extend from.
|
||||
//!
|
||||
//! Using either of the above connector examples, it can be used with the
|
||||
//! `Client` like this:
|
||||
//!
|
||||
//! ```
|
||||
//! # #[cfg(feature = "runtime")]
|
||||
//! # fn rt () {
|
||||
//! # let connector = hyper::client::HttpConnector::new();
|
||||
//! // let connector = ...
|
||||
//!
|
||||
//! let client = hyper::Client::builder()
|
||||
//! .build::<_, hyper::Body>(connector);
|
||||
//! # }
|
||||
//! ```
|
||||
//!
|
||||
//!
|
||||
//! [`HttpConnector`]: HttpConnector
|
||||
//! [`Service`]: crate::service::Service
|
||||
//! [`Uri`]: ::http::Uri
|
||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||
//! [`Connection`]: Connection
|
||||
use std::fmt;
|
||||
|
||||
use ::http::Extensions;
|
||||
|
||||
cfg_feature! {
|
||||
#![feature = "tcp"]
|
||||
|
||||
pub use self::http::{HttpConnector, HttpInfo};
|
||||
|
||||
pub mod dns;
|
||||
mod http;
|
||||
}
|
||||
|
||||
cfg_feature! {
|
||||
#![any(feature = "http1", feature = "http2")]
|
||||
|
||||
pub use self::sealed::Connect;
|
||||
}
|
||||
|
||||
/// Describes a type returned by a connector.
|
||||
pub trait Connection {
|
||||
/// Return metadata describing the connection.
|
||||
fn connected(&self) -> Connected;
|
||||
}
|
||||
|
||||
/// Extra information about the connected transport.
|
||||
///
|
||||
/// This can be used to inform recipients about things like if ALPN
|
||||
/// was used, or if connected to an HTTP proxy.
|
||||
#[derive(Debug)]
|
||||
pub struct Connected {
|
||||
pub(super) alpn: Alpn,
|
||||
pub(super) is_proxied: bool,
|
||||
pub(super) extra: Option<Extra>,
|
||||
}
|
||||
|
||||
pub(super) struct Extra(Box<dyn ExtraInner>);
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||
pub(super) enum Alpn {
|
||||
H2,
|
||||
None,
|
||||
}
|
||||
|
||||
impl Connected {
|
||||
/// Create new `Connected` type with empty metadata.
|
||||
pub fn new() -> Connected {
|
||||
Connected {
|
||||
alpn: Alpn::None,
|
||||
is_proxied: false,
|
||||
extra: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Set whether the connected transport is to an HTTP proxy.
|
||||
///
|
||||
/// This setting will affect if HTTP/1 requests written on the transport
|
||||
/// will have the request-target in absolute-form or origin-form:
|
||||
///
|
||||
/// - When `proxy(false)`:
|
||||
///
|
||||
/// ```http
|
||||
/// GET /guide HTTP/1.1
|
||||
/// ```
|
||||
///
|
||||
/// - When `proxy(true)`:
|
||||
///
|
||||
/// ```http
|
||||
/// GET http://hyper.rs/guide HTTP/1.1
|
||||
/// ```
|
||||
///
|
||||
/// Default is `false`.
|
||||
pub fn proxy(mut self, is_proxied: bool) -> Connected {
|
||||
self.is_proxied = is_proxied;
|
||||
self
|
||||
}
|
||||
|
||||
/// Determines if the connected transport is to an HTTP proxy.
|
||||
pub fn is_proxied(&self) -> bool {
|
||||
self.is_proxied
|
||||
}
|
||||
|
||||
/// Set extra connection information to be set in the extensions of every `Response`.
|
||||
pub fn extra<T: Clone + Send + Sync + 'static>(mut self, extra: T) -> Connected {
|
||||
if let Some(prev) = self.extra {
|
||||
self.extra = Some(Extra(Box::new(ExtraChain(prev.0, extra))));
|
||||
} else {
|
||||
self.extra = Some(Extra(Box::new(ExtraEnvelope(extra))));
|
||||
}
|
||||
self
|
||||
}
|
||||
|
||||
/// Copies the extra connection information into an `Extensions` map.
|
||||
pub fn get_extras(&self, extensions: &mut Extensions) {
|
||||
if let Some(extra) = &self.extra {
|
||||
extra.set(extensions);
|
||||
}
|
||||
}
|
||||
|
||||
/// Set that the connected transport negotiated HTTP/2 as its next protocol.
|
||||
pub fn negotiated_h2(mut self) -> Connected {
|
||||
self.alpn = Alpn::H2;
|
||||
self
|
||||
}
|
||||
|
||||
/// Determines if the connected transport negotiated HTTP/2 as its next protocol.
|
||||
pub fn is_negotiated_h2(&self) -> bool {
|
||||
self.alpn == Alpn::H2
|
||||
}
|
||||
|
||||
// Don't public expose that `Connected` is `Clone`, unsure if we want to
|
||||
// keep that contract...
|
||||
#[cfg(feature = "http2")]
|
||||
pub(super) fn clone(&self) -> Connected {
|
||||
Connected {
|
||||
alpn: self.alpn.clone(),
|
||||
is_proxied: self.is_proxied,
|
||||
extra: self.extra.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Extra =====
|
||||
|
||||
impl Extra {
|
||||
pub(super) fn set(&self, res: &mut Extensions) {
|
||||
self.0.set(res);
|
||||
}
|
||||
}
|
||||
|
||||
impl Clone for Extra {
|
||||
fn clone(&self) -> Extra {
|
||||
Extra(self.0.clone_box())
|
||||
}
|
||||
}
|
||||
|
||||
impl fmt::Debug for Extra {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("Extra").finish()
|
||||
}
|
||||
}
|
||||
|
||||
trait ExtraInner: Send + Sync {
|
||||
fn clone_box(&self) -> Box<dyn ExtraInner>;
|
||||
fn set(&self, res: &mut Extensions);
|
||||
}
|
||||
|
||||
// This indirection allows the `Connected` to have a type-erased "extra" value,
|
||||
// while that type still knows its inner extra type. This allows the correct
|
||||
// TypeId to be used when inserting into `res.extensions_mut()`.
|
||||
#[derive(Clone)]
|
||||
struct ExtraEnvelope<T>(T);
|
||||
|
||||
impl<T> ExtraInner for ExtraEnvelope<T>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ExtraInner> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn set(&self, res: &mut Extensions) {
|
||||
res.insert(self.0.clone());
|
||||
}
|
||||
}
|
||||
|
||||
struct ExtraChain<T>(Box<dyn ExtraInner>, T);
|
||||
|
||||
impl<T: Clone> Clone for ExtraChain<T> {
|
||||
fn clone(&self) -> Self {
|
||||
ExtraChain(self.0.clone_box(), self.1.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> ExtraInner for ExtraChain<T>
|
||||
where
|
||||
T: Clone + Send + Sync + 'static,
|
||||
{
|
||||
fn clone_box(&self) -> Box<dyn ExtraInner> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn set(&self, res: &mut Extensions) {
|
||||
self.0.set(res);
|
||||
res.insert(self.1.clone());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pub(super) mod sealed {
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use ::http::Uri;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::Connection;
|
||||
use crate::common::{Future, Unpin};
|
||||
|
||||
/// Connect to a destination, returning an IO transport.
|
||||
///
|
||||
/// A connector receives a [`Uri`](::http::Uri) and returns a `Future` of the
|
||||
/// ready connection.
|
||||
///
|
||||
/// # Trait Alias
|
||||
///
|
||||
/// This is really just an *alias* for the `tower::Service` trait, with
|
||||
/// additional bounds set for convenience *inside* hyper. You don't actually
|
||||
/// implement this trait, but `tower::Service<Uri>` instead.
|
||||
// The `Sized` bound is to prevent creating `dyn Connect`, since they cannot
|
||||
// fit the `Connect` bounds because of the blanket impl for `Service`.
|
||||
pub trait Connect: Sealed + Sized {
|
||||
#[doc(hidden)]
|
||||
type _Svc: ConnectSvc;
|
||||
#[doc(hidden)]
|
||||
fn connect(self, internal_only: Internal, dst: Uri) -> <Self::_Svc as ConnectSvc>::Future;
|
||||
}
|
||||
|
||||
pub trait ConnectSvc {
|
||||
type Connection: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static;
|
||||
type Error: Into<Box<dyn StdError + Send + Sync>>;
|
||||
type Future: Future<Output = Result<Self::Connection, Self::Error>> + Unpin + Send + 'static;
|
||||
|
||||
fn connect(self, internal_only: Internal, dst: Uri) -> Self::Future;
|
||||
}
|
||||
|
||||
impl<S, T> Connect for S
|
||||
where
|
||||
S: tower_service::Service<Uri, Response = T> + Send + 'static,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S::Future: Unpin + Send,
|
||||
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
||||
{
|
||||
type _Svc = S;
|
||||
|
||||
fn connect(self, _: Internal, dst: Uri) -> crate::service::Oneshot<S, Uri> {
|
||||
crate::service::oneshot(self, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> ConnectSvc for S
|
||||
where
|
||||
S: tower_service::Service<Uri, Response = T> + Send + 'static,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S::Future: Unpin + Send,
|
||||
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
||||
{
|
||||
type Connection = T;
|
||||
type Error = S::Error;
|
||||
type Future = crate::service::Oneshot<S, Uri>;
|
||||
|
||||
fn connect(self, _: Internal, dst: Uri) -> Self::Future {
|
||||
crate::service::oneshot(self, dst)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T> Sealed for S
|
||||
where
|
||||
S: tower_service::Service<Uri, Response = T> + Send,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S::Future: Unpin + Send,
|
||||
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
||||
{
|
||||
}
|
||||
|
||||
pub trait Sealed {}
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Internal;
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Connected;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Ex1(usize);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Ex2(&'static str);
|
||||
|
||||
#[derive(Clone, Debug, PartialEq)]
|
||||
struct Ex3(&'static str);
|
||||
|
||||
#[test]
|
||||
fn test_connected_extra() {
|
||||
let c1 = Connected::new().extra(Ex1(41));
|
||||
|
||||
let mut ex = ::http::Extensions::new();
|
||||
|
||||
assert_eq!(ex.get::<Ex1>(), None);
|
||||
|
||||
c1.extra.as_ref().expect("c1 extra").set(&mut ex);
|
||||
|
||||
assert_eq!(ex.get::<Ex1>(), Some(&Ex1(41)));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_connected_extra_chain() {
|
||||
// If a user composes connectors and at each stage, there's "extra"
|
||||
// info to attach, it shouldn't override the previous extras.
|
||||
|
||||
let c1 = Connected::new()
|
||||
.extra(Ex1(45))
|
||||
.extra(Ex2("zoom"))
|
||||
.extra(Ex3("pew pew"));
|
||||
|
||||
let mut ex1 = ::http::Extensions::new();
|
||||
|
||||
assert_eq!(ex1.get::<Ex1>(), None);
|
||||
assert_eq!(ex1.get::<Ex2>(), None);
|
||||
assert_eq!(ex1.get::<Ex3>(), None);
|
||||
|
||||
c1.extra.as_ref().expect("c1 extra").set(&mut ex1);
|
||||
|
||||
assert_eq!(ex1.get::<Ex1>(), Some(&Ex1(45)));
|
||||
assert_eq!(ex1.get::<Ex2>(), Some(&Ex2("zoom")));
|
||||
assert_eq!(ex1.get::<Ex3>(), Some(&Ex3("pew pew")));
|
||||
|
||||
// Just like extensions, inserting the same type overrides previous type.
|
||||
let c2 = Connected::new()
|
||||
.extra(Ex1(33))
|
||||
.extra(Ex2("hiccup"))
|
||||
.extra(Ex1(99));
|
||||
|
||||
let mut ex2 = ::http::Extensions::new();
|
||||
|
||||
c2.extra.as_ref().expect("c2 extra").set(&mut ex2);
|
||||
|
||||
assert_eq!(ex2.get::<Ex1>(), Some(&Ex1(99)));
|
||||
assert_eq!(ex2.get::<Ex2>(), Some(&Ex2("hiccup")));
|
||||
}
|
||||
}
|
||||
@@ -1,13 +1,13 @@
|
||||
#[cfg(feature = "http2")]
|
||||
use std::future::Future;
|
||||
|
||||
use futures_util::FutureExt;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
use crate::common::Pin;
|
||||
use crate::common::{task, Poll};
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) type RetryPromise<T, U> = oneshot::Receiver<Result<U, (crate::Error, Option<T>)>>;
|
||||
pub(crate) type Promise<T> = oneshot::Receiver<Result<T, crate::Error>>;
|
||||
|
||||
@@ -59,13 +59,16 @@ impl<T, U> Sender<T, U> {
|
||||
.map_err(|_| crate::Error::new_closed())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn is_ready(&self) -> bool {
|
||||
self.giver.is_wanting()
|
||||
}
|
||||
|
||||
/*
|
||||
pub(crate) fn is_closed(&self) -> bool {
|
||||
self.giver.is_canceled()
|
||||
}
|
||||
*/
|
||||
|
||||
fn can_send(&mut self) -> bool {
|
||||
if self.giver.give() || !self.buffered_once {
|
||||
@@ -80,6 +83,7 @@ impl<T, U> Sender<T, U> {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> {
|
||||
if !self.can_send() {
|
||||
return Err(val);
|
||||
@@ -113,14 +117,17 @@ impl<T, U> Sender<T, U> {
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
impl<T, U> UnboundedSender<T, U> {
|
||||
/*
|
||||
pub(crate) fn is_ready(&self) -> bool {
|
||||
!self.giver.is_canceled()
|
||||
}
|
||||
*/
|
||||
|
||||
pub(crate) fn is_closed(&self) -> bool {
|
||||
self.giver.is_canceled()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.inner
|
||||
@@ -128,6 +135,14 @@ impl<T, U> UnboundedSender<T, U> {
|
||||
.map(move |_| rx)
|
||||
.map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0)
|
||||
}
|
||||
|
||||
pub(crate) fn send(&mut self, val: T) -> Result<Promise<U>, T> {
|
||||
let (tx, rx) = oneshot::channel();
|
||||
self.inner
|
||||
.send(Envelope(Some((val, Callback::NoRetry(tx)))))
|
||||
.map(move |_| rx)
|
||||
.map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
@@ -169,6 +184,7 @@ impl<T, U> Receiver<T, U> {
|
||||
|
||||
#[cfg(feature = "http1")]
|
||||
pub(crate) fn try_recv(&mut self) -> Option<(T, Callback<T, U>)> {
|
||||
use futures_util::FutureExt;
|
||||
match self.inner.recv().now_or_never() {
|
||||
Some(Some(mut env)) => env.0.take(),
|
||||
_ => None,
|
||||
@@ -198,6 +214,7 @@ impl<T, U> Drop for Envelope<T, U> {
|
||||
}
|
||||
|
||||
pub(crate) enum Callback<T, U> {
|
||||
#[allow(unused)]
|
||||
Retry(oneshot::Sender<Result<U, (crate::Error, Option<T>)>>),
|
||||
NoRetry(oneshot::Sender<Result<U, crate::Error>>),
|
||||
}
|
||||
@@ -301,6 +318,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
#[tokio::test]
|
||||
async fn drop_receiver_sends_cancel_errors() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
@@ -323,6 +341,7 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(not(miri))]
|
||||
#[tokio::test]
|
||||
async fn sender_checks_for_want_on_send() {
|
||||
let (mut tx, mut rx) = channel::<Custom, ()>();
|
||||
@@ -363,7 +382,6 @@ mod tests {
|
||||
use crate::{Body, Request, Response};
|
||||
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>();
|
||||
@@ -386,7 +404,6 @@ mod tests {
|
||||
#[bench]
|
||||
fn giver_queue_not_ready(b: &mut test::Bencher) {
|
||||
let rt = tokio::runtime::Builder::new_current_thread()
|
||||
.enable_all()
|
||||
.build()
|
||||
.unwrap();
|
||||
let (_tx, mut rx) = channel::<i32, ()>();
|
||||
|
||||
@@ -1,68 +1,18 @@
|
||||
//! HTTP Client
|
||||
//!
|
||||
//! There are two levels of APIs provided for construct HTTP clients:
|
||||
//!
|
||||
//! - The higher-level [`Client`](Client) type.
|
||||
//! - The lower-level [`conn`](conn) module.
|
||||
//!
|
||||
//! # Client
|
||||
//!
|
||||
//! The [`Client`](Client) is the main way to send HTTP requests to a server.
|
||||
//! The default `Client` provides these things on top of the lower-level API:
|
||||
//!
|
||||
//! - A default **connector**, able to resolve hostnames and connect to
|
||||
//! destinations over plain-text TCP.
|
||||
//! - A **pool** of existing connections, allowing better performance when
|
||||
//! making multiple requests to the same hostname.
|
||||
//! - Automatic setting of the `Host` header, based on the request `Uri`.
|
||||
//! - Automatic request **retries** when a pooled connection is closed by the
|
||||
//! server before any bytes have been written.
|
||||
//!
|
||||
//! Many of these features can configured, by making use of
|
||||
//! [`Client::builder`](Client::builder).
|
||||
//! hyper provides HTTP over a single connection. See the [`conn`](conn) module.
|
||||
//!
|
||||
//! ## Example
|
||||
//!
|
||||
//! For a small example program simply fetching a URL, take a look at the
|
||||
//! [full client example](https://github.com/hyperium/hyper/blob/master/examples/client.rs).
|
||||
//!
|
||||
//! ```
|
||||
//! # #[cfg(all(feature = "tcp", feature = "client", any(feature = "http1", feature = "http2")))]
|
||||
//! # async fn fetch_httpbin() -> hyper::Result<()> {
|
||||
//! use hyper::{body::HttpBody as _, Client, Uri};
|
||||
//!
|
||||
//! let client = Client::new();
|
||||
//!
|
||||
//! // Make a GET /ip to 'http://httpbin.org'
|
||||
//! let res = client.get(Uri::from_static("http://httpbin.org/ip")).await?;
|
||||
//!
|
||||
//! // And then, if the request gets a response...
|
||||
//! println!("status: {}", res.status());
|
||||
//!
|
||||
//! // Concatenate the body stream into a single buffer...
|
||||
//! let buf = hyper::body::to_bytes(res).await?;
|
||||
//!
|
||||
//! println!("body: {:?}", buf);
|
||||
//! # Ok(())
|
||||
//! # }
|
||||
//! # fn main () {}
|
||||
//! ```
|
||||
|
||||
#[cfg(feature = "tcp")]
|
||||
pub use self::connect::HttpConnector;
|
||||
|
||||
pub mod connect;
|
||||
#[cfg(all(test, feature = "runtime"))]
|
||||
mod tests;
|
||||
|
||||
cfg_feature! {
|
||||
#![any(feature = "http1", feature = "http2")]
|
||||
|
||||
pub use self::client::{Builder, Client, ResponseFuture};
|
||||
|
||||
mod client;
|
||||
pub mod conn;
|
||||
pub(super) mod dispatch;
|
||||
mod pool;
|
||||
pub mod service;
|
||||
}
|
||||
|
||||
1044
src/client/pool.rs
1044
src/client/pool.rs
File diff suppressed because it is too large
Load Diff
@@ -1,89 +0,0 @@
|
||||
//! Utilities used to interact with the Tower ecosystem.
|
||||
//!
|
||||
//! This module provides `Connect` which hook-ins into the Tower ecosystem.
|
||||
|
||||
use std::error::Error as StdError;
|
||||
use std::future::Future;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use tracing::debug;
|
||||
|
||||
use super::conn::{Builder, SendRequest};
|
||||
use crate::{
|
||||
body::HttpBody,
|
||||
common::{task, Pin, Poll},
|
||||
service::{MakeConnection, Service},
|
||||
};
|
||||
|
||||
/// Creates a connection via `SendRequest`.
|
||||
///
|
||||
/// This accepts a `hyper::client::conn::Builder` and provides
|
||||
/// a `MakeService` implementation to create connections from some
|
||||
/// target `T`.
|
||||
#[derive(Debug)]
|
||||
pub struct Connect<C, B, T> {
|
||||
inner: C,
|
||||
builder: Builder,
|
||||
_pd: PhantomData<fn(T, B)>,
|
||||
}
|
||||
|
||||
impl<C, B, T> Connect<C, B, T> {
|
||||
/// Create a new `Connect` with some inner connector `C` and a connection
|
||||
/// builder.
|
||||
pub fn new(inner: C, builder: Builder) -> Self {
|
||||
Self {
|
||||
inner,
|
||||
builder,
|
||||
_pd: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<C, B, T> Service<T> for Connect<C, B, T>
|
||||
where
|
||||
C: MakeConnection<T>,
|
||||
C::Connection: Unpin + Send + 'static,
|
||||
C::Future: Send + 'static,
|
||||
C::Error: Into<Box<dyn StdError + Send + Sync>> + Send,
|
||||
B: HttpBody + Unpin + Send + 'static,
|
||||
B::Data: Send + Unpin,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
type Response = SendRequest<B>;
|
||||
type Error = crate::Error;
|
||||
type Future =
|
||||
Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
self.inner
|
||||
.poll_ready(cx)
|
||||
.map_err(|e| crate::Error::new(crate::error::Kind::Connect).with(e.into()))
|
||||
}
|
||||
|
||||
fn call(&mut self, req: T) -> Self::Future {
|
||||
let builder = self.builder.clone();
|
||||
let io = self.inner.make_connection(req);
|
||||
|
||||
let fut = async move {
|
||||
match io.await {
|
||||
Ok(io) => match builder.handshake(io).await {
|
||||
Ok((sr, conn)) => {
|
||||
builder.exec.execute(async move {
|
||||
if let Err(e) = conn.await {
|
||||
debug!("connection error: {:?}", e);
|
||||
}
|
||||
});
|
||||
Ok(sr)
|
||||
}
|
||||
Err(e) => Err(e),
|
||||
},
|
||||
Err(e) => {
|
||||
let err = crate::Error::new(crate::error::Kind::Connect).with(e.into());
|
||||
Err(err)
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
Box::pin(fut)
|
||||
}
|
||||
}
|
||||
@@ -1,28 +1,3 @@
|
||||
use std::io;
|
||||
|
||||
use futures_util::future;
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use super::Client;
|
||||
|
||||
#[tokio::test]
|
||||
async fn client_connect_uri_argument() {
|
||||
let connector = tower::service_fn(|dst: http::Uri| {
|
||||
assert_eq!(dst.scheme(), Some(&http::uri::Scheme::HTTP));
|
||||
assert_eq!(dst.host(), Some("example.local"));
|
||||
assert_eq!(dst.port(), None);
|
||||
assert_eq!(dst.path(), "/", "path should be removed");
|
||||
|
||||
future::err::<TcpStream, _>(io::Error::new(io::ErrorKind::Other, "expect me"))
|
||||
});
|
||||
|
||||
let client = Client::builder().build::<_, crate::Body>(connector);
|
||||
let _ = client
|
||||
.get("http://example.local/and/a/path".parse().unwrap())
|
||||
.await
|
||||
.expect_err("response should fail");
|
||||
}
|
||||
|
||||
/*
|
||||
// FIXME: re-implement tests with `async/await`
|
||||
#[test]
|
||||
|
||||
@@ -1,217 +0,0 @@
|
||||
use std::mem;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::sync::watch;
|
||||
|
||||
use super::{task, Future, Pin, Poll};
|
||||
|
||||
pub(crate) fn channel() -> (Signal, Watch) {
|
||||
let (tx, rx) = watch::channel(());
|
||||
(Signal { tx }, Watch { rx })
|
||||
}
|
||||
|
||||
pub(crate) struct Signal {
|
||||
tx: watch::Sender<()>,
|
||||
}
|
||||
|
||||
pub(crate) struct Draining(Pin<Box<dyn Future<Output = ()> + Send + Sync>>);
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct Watch {
|
||||
rx: watch::Receiver<()>,
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Watching<F, FN> {
|
||||
#[pin]
|
||||
future: F,
|
||||
state: State<FN>,
|
||||
watch: Pin<Box<dyn Future<Output = ()> + Send + Sync>>,
|
||||
_rx: watch::Receiver<()>,
|
||||
}
|
||||
}
|
||||
|
||||
enum State<F> {
|
||||
Watch(F),
|
||||
Draining,
|
||||
}
|
||||
|
||||
impl Signal {
|
||||
pub(crate) fn drain(self) -> Draining {
|
||||
let _ = self.tx.send(());
|
||||
Draining(Box::pin(async move { self.tx.closed().await }))
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Draining {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
Pin::new(&mut self.as_mut().0).poll(cx)
|
||||
}
|
||||
}
|
||||
|
||||
impl Watch {
|
||||
pub(crate) fn watch<F, FN>(self, future: F, on_drain: FN) -> Watching<F, FN>
|
||||
where
|
||||
F: Future,
|
||||
FN: FnOnce(Pin<&mut F>),
|
||||
{
|
||||
let Self { mut rx } = self;
|
||||
let _rx = rx.clone();
|
||||
Watching {
|
||||
future,
|
||||
state: State::Watch(on_drain),
|
||||
watch: Box::pin(async move {
|
||||
let _ = rx.changed().await;
|
||||
}),
|
||||
// Keep the receiver alive until the future completes, so that
|
||||
// dropping it can signal that draining has completed.
|
||||
_rx,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, FN> Future for Watching<F, FN>
|
||||
where
|
||||
F: Future,
|
||||
FN: FnOnce(Pin<&mut F>),
|
||||
{
|
||||
type Output = F::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut me = self.project();
|
||||
loop {
|
||||
match mem::replace(me.state, State::Draining) {
|
||||
State::Watch(on_drain) => {
|
||||
match Pin::new(&mut me.watch).poll(cx) {
|
||||
Poll::Ready(()) => {
|
||||
// Drain has been triggered!
|
||||
on_drain(me.future.as_mut());
|
||||
}
|
||||
Poll::Pending => {
|
||||
*me.state = State::Watch(on_drain);
|
||||
return me.future.poll(cx);
|
||||
}
|
||||
}
|
||||
}
|
||||
State::Draining => return me.future.poll(cx),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
struct TestMe {
|
||||
draining: bool,
|
||||
finished: bool,
|
||||
poll_cnt: usize,
|
||||
}
|
||||
|
||||
impl Future for TestMe {
|
||||
type Output = ();
|
||||
|
||||
fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
self.poll_cnt += 1;
|
||||
if self.finished {
|
||||
Poll::Ready(())
|
||||
} else {
|
||||
Poll::Pending
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watch() {
|
||||
let mut mock = tokio_test::task::spawn(());
|
||||
mock.enter(|cx, _| {
|
||||
let (tx, rx) = channel();
|
||||
let fut = TestMe {
|
||||
draining: false,
|
||||
finished: false,
|
||||
poll_cnt: 0,
|
||||
};
|
||||
|
||||
let mut watch = rx.watch(fut, |mut fut| {
|
||||
fut.draining = true;
|
||||
});
|
||||
|
||||
assert_eq!(watch.future.poll_cnt, 0);
|
||||
|
||||
// First poll should poll the inner future
|
||||
assert!(Pin::new(&mut watch).poll(cx).is_pending());
|
||||
assert_eq!(watch.future.poll_cnt, 1);
|
||||
|
||||
// Second poll should poll the inner future again
|
||||
assert!(Pin::new(&mut watch).poll(cx).is_pending());
|
||||
assert_eq!(watch.future.poll_cnt, 2);
|
||||
|
||||
let mut draining = tx.drain();
|
||||
// Drain signaled, but needs another poll to be noticed.
|
||||
assert!(!watch.future.draining);
|
||||
assert_eq!(watch.future.poll_cnt, 2);
|
||||
|
||||
// Now, poll after drain has been signaled.
|
||||
assert!(Pin::new(&mut watch).poll(cx).is_pending());
|
||||
assert_eq!(watch.future.poll_cnt, 3);
|
||||
assert!(watch.future.draining);
|
||||
|
||||
// Draining is not ready until watcher completes
|
||||
assert!(Pin::new(&mut draining).poll(cx).is_pending());
|
||||
|
||||
// Finishing up the watch future
|
||||
watch.future.finished = true;
|
||||
assert!(Pin::new(&mut watch).poll(cx).is_ready());
|
||||
assert_eq!(watch.future.poll_cnt, 4);
|
||||
drop(watch);
|
||||
|
||||
assert!(Pin::new(&mut draining).poll(cx).is_ready());
|
||||
})
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn watch_clones() {
|
||||
let mut mock = tokio_test::task::spawn(());
|
||||
mock.enter(|cx, _| {
|
||||
let (tx, rx) = channel();
|
||||
|
||||
let fut1 = TestMe {
|
||||
draining: false,
|
||||
finished: false,
|
||||
poll_cnt: 0,
|
||||
};
|
||||
let fut2 = TestMe {
|
||||
draining: false,
|
||||
finished: false,
|
||||
poll_cnt: 0,
|
||||
};
|
||||
|
||||
let watch1 = rx.clone().watch(fut1, |mut fut| {
|
||||
fut.draining = true;
|
||||
});
|
||||
let watch2 = rx.watch(fut2, |mut fut| {
|
||||
fut.draining = true;
|
||||
});
|
||||
|
||||
let mut draining = tx.drain();
|
||||
|
||||
// Still 2 outstanding watchers
|
||||
assert!(Pin::new(&mut draining).poll(cx).is_pending());
|
||||
|
||||
// drop 1 for whatever reason
|
||||
drop(watch1);
|
||||
|
||||
// Still not ready, 1 other watcher still pending
|
||||
assert!(Pin::new(&mut draining).poll(cx).is_pending());
|
||||
|
||||
drop(watch2);
|
||||
|
||||
// Now all watchers are gone, draining is complete
|
||||
assert!(Pin::new(&mut draining).poll(cx).is_ready());
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -3,28 +3,17 @@ use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
use crate::body::Body;
|
||||
#[cfg(feature = "server")]
|
||||
use crate::body::HttpBody;
|
||||
#[cfg(all(feature = "http2", feature = "server"))]
|
||||
use crate::proto::h2::server::H2Stream;
|
||||
use crate::rt::Executor;
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
use crate::server::conn::spawn_all::{NewSvcTask, Watcher};
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
use crate::service::HttpService;
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
pub trait ConnStreamExec<F, B: HttpBody>: Clone {
|
||||
fn execute_h2stream(&mut self, fut: H2Stream<F, B>);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
pub trait NewSvcExec<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>>: Clone {
|
||||
fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>);
|
||||
}
|
||||
|
||||
pub(crate) type BoxSendFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
|
||||
|
||||
// Either the user provides an executor for background tasks, or we use
|
||||
@@ -44,13 +33,13 @@ impl Exec {
|
||||
{
|
||||
match *self {
|
||||
Exec::Default => {
|
||||
#[cfg(feature = "tcp")]
|
||||
#[cfg(feature = "runtime")]
|
||||
{
|
||||
tokio::task::spawn(fut);
|
||||
}
|
||||
#[cfg(not(feature = "tcp"))]
|
||||
|
||||
#[cfg(not(feature = "runtime"))]
|
||||
{
|
||||
// If no runtime, we need an executor!
|
||||
panic!("executor must be set")
|
||||
}
|
||||
}
|
||||
@@ -78,18 +67,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
impl<I, N, S, E, W> NewSvcExec<I, N, S, E, W> for Exec
|
||||
where
|
||||
NewSvcTask<I, N, S, E, W>: Future<Output = ()> + Send + 'static,
|
||||
S: HttpService<Body>,
|
||||
W: Watcher<I, S, E>,
|
||||
{
|
||||
fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>) {
|
||||
self.execute(fut)
|
||||
}
|
||||
}
|
||||
|
||||
// ==== impl Executor =====
|
||||
|
||||
#[cfg(feature = "server")]
|
||||
@@ -104,19 +81,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
impl<I, N, S, E, W> NewSvcExec<I, N, S, E, W> for E
|
||||
where
|
||||
E: Executor<NewSvcTask<I, N, S, E, W>> + Clone,
|
||||
NewSvcTask<I, N, S, E, W>: Future<Output = ()>,
|
||||
S: HttpService<Body>,
|
||||
W: Watcher<I, S, E>,
|
||||
{
|
||||
fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>) {
|
||||
self.execute(fut)
|
||||
}
|
||||
}
|
||||
|
||||
// If http2 is not enable, we just have a stub here, so that the trait bounds
|
||||
// that *would* have been needed are still checked. Why?
|
||||
//
|
||||
|
||||
@@ -60,7 +60,7 @@ where
|
||||
// TODO: There should be a way to do following two lines cleaner...
|
||||
buf.put_slice(&prefix[..copy_len]);
|
||||
prefix.advance(copy_len);
|
||||
// Put back whats left
|
||||
// Put back what's left
|
||||
if !prefix.is_empty() {
|
||||
self.pre = Some(prefix);
|
||||
}
|
||||
|
||||
@@ -1,76 +0,0 @@
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use super::{task, Future, Pin, Poll};
|
||||
|
||||
pub(crate) trait Started: Future {
|
||||
fn started(&self) -> bool;
|
||||
}
|
||||
|
||||
pub(crate) fn lazy<F, R>(func: F) -> Lazy<F, R>
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
R: Future + Unpin,
|
||||
{
|
||||
Lazy {
|
||||
inner: Inner::Init { func },
|
||||
}
|
||||
}
|
||||
|
||||
// FIXME: allow() required due to `impl Trait` leaking types to this lint
|
||||
pin_project! {
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub(crate) struct Lazy<F, R> {
|
||||
#[pin]
|
||||
inner: Inner<F, R>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = InnerProj]
|
||||
#[project_replace = InnerProjReplace]
|
||||
enum Inner<F, R> {
|
||||
Init { func: F },
|
||||
Fut { #[pin] fut: R },
|
||||
Empty,
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, R> Started for Lazy<F, R>
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
R: Future,
|
||||
{
|
||||
fn started(&self) -> bool {
|
||||
match self.inner {
|
||||
Inner::Init { .. } => false,
|
||||
Inner::Fut { .. } | Inner::Empty => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<F, R> Future for Lazy<F, R>
|
||||
where
|
||||
F: FnOnce() -> R,
|
||||
R: Future,
|
||||
{
|
||||
type Output = R::Output;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut this = self.project();
|
||||
|
||||
if let InnerProj::Fut { fut } = this.inner.as_mut().project() {
|
||||
return fut.poll(cx);
|
||||
}
|
||||
|
||||
match this.inner.as_mut().project_replace(Inner::Empty) {
|
||||
InnerProjReplace::Init { func } => {
|
||||
this.inner.set(Inner::Fut { fut: func() });
|
||||
if let InnerProj::Fut { fut } = this.inner.project() {
|
||||
return fut.poll(cx);
|
||||
}
|
||||
unreachable!()
|
||||
}
|
||||
_ => unreachable!("lazy state wrong"),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -10,24 +10,13 @@ macro_rules! ready {
|
||||
pub(crate) mod buf;
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
pub(crate) mod date;
|
||||
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||
pub(crate) mod drain;
|
||||
#[cfg(any(feature = "http1", feature = "http2", feature = "server"))]
|
||||
pub(crate) mod exec;
|
||||
pub(crate) mod io;
|
||||
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||
mod lazy;
|
||||
mod never;
|
||||
#[cfg(any(
|
||||
feature = "stream",
|
||||
all(feature = "client", any(feature = "http1", feature = "http2"))
|
||||
))]
|
||||
pub(crate) mod sync_wrapper;
|
||||
pub(crate) mod task;
|
||||
pub(crate) mod watch;
|
||||
|
||||
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||
pub(crate) use self::lazy::{lazy, Started as Lazy};
|
||||
#[cfg(any(feature = "http1", feature = "http2", feature = "runtime"))]
|
||||
pub(crate) use self::never::Never;
|
||||
pub(crate) use self::task::Poll;
|
||||
|
||||
@@ -1,110 +0,0 @@
|
||||
/*
|
||||
* This is a copy of the sync_wrapper crate.
|
||||
*/
|
||||
|
||||
/// A mutual exclusion primitive that relies on static type information only
|
||||
///
|
||||
/// In some cases synchronization can be proven statically: whenever you hold an exclusive `&mut`
|
||||
/// reference, the Rust type system ensures that no other part of the program can hold another
|
||||
/// reference to the data. Therefore it is safe to access it even if the current thread obtained
|
||||
/// this reference via a channel. Whenever this is the case, the overhead of allocating and locking
|
||||
/// a [`Mutex`] can be avoided by using this static version.
|
||||
///
|
||||
/// One example where this is often applicable is [`Future`], which requires an exclusive reference
|
||||
/// for its [`poll`] method: While a given `Future` implementation may not be safe to access by
|
||||
/// multiple threads concurrently, the executor can only run the `Future` on one thread at any
|
||||
/// given time, making it [`Sync`] in practice as long as the implementation is `Send`. You can
|
||||
/// therefore use the sync wrapper to prove that your data structure is `Sync` even though it
|
||||
/// contains such a `Future`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||
/// use std::future::Future;
|
||||
///
|
||||
/// struct MyThing {
|
||||
/// future: SyncWrapper<Box<dyn Future<Output = String> + Send>>,
|
||||
/// }
|
||||
///
|
||||
/// impl MyThing {
|
||||
/// // all accesses to `self.future` now require an exclusive reference or ownership
|
||||
/// }
|
||||
///
|
||||
/// fn assert_sync<T: Sync>() {}
|
||||
///
|
||||
/// assert_sync::<MyThing>();
|
||||
/// ```
|
||||
///
|
||||
/// [`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
|
||||
/// [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
|
||||
/// [`poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#method.poll
|
||||
/// [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html
|
||||
#[repr(transparent)]
|
||||
pub(crate) struct SyncWrapper<T>(T);
|
||||
|
||||
impl<T> SyncWrapper<T> {
|
||||
/// Creates a new SyncWrapper containing the given value.
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||
///
|
||||
/// let wrapped = SyncWrapper::new(42);
|
||||
/// ```
|
||||
pub(crate) fn new(value: T) -> Self {
|
||||
Self(value)
|
||||
}
|
||||
|
||||
/// Acquires a reference to the protected value.
|
||||
///
|
||||
/// This is safe because it requires an exclusive reference to the wrapper. Therefore this method
|
||||
/// neither panics nor does it return an error. This is in contrast to [`Mutex::get_mut`] which
|
||||
/// returns an error if another thread panicked while holding the lock. It is not recommended
|
||||
/// to send an exclusive reference to a potentially damaged value to another thread for further
|
||||
/// processing.
|
||||
///
|
||||
/// [`Mutex::get_mut`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.get_mut
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||
///
|
||||
/// let mut wrapped = SyncWrapper::new(42);
|
||||
/// let value = wrapped.get_mut();
|
||||
/// *value = 0;
|
||||
/// assert_eq!(*wrapped.get_mut(), 0);
|
||||
/// ```
|
||||
pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||
&mut self.0
|
||||
}
|
||||
|
||||
/// Consumes this wrapper, returning the underlying data.
|
||||
///
|
||||
/// This is safe because it requires ownership of the wrapper, aherefore this method will neither
|
||||
/// panic nor does it return an error. This is in contrast to [`Mutex::into_inner`] which
|
||||
/// returns an error if another thread panicked while holding the lock. It is not recommended
|
||||
/// to send an exclusive reference to a potentially damaged value to another thread for further
|
||||
/// processing.
|
||||
///
|
||||
/// [`Mutex::into_inner`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.into_inner
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// ```ignore
|
||||
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||
///
|
||||
/// let mut wrapped = SyncWrapper::new(42);
|
||||
/// assert_eq!(wrapped.into_inner(), 42);
|
||||
/// ```
|
||||
#[allow(dead_code)]
|
||||
pub(crate) fn into_inner(self) -> T {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
// this is safe because the only operations permitted on this data structure require exclusive
|
||||
// access or ownership
|
||||
unsafe impl<T: Send> Sync for SyncWrapper<T> {}
|
||||
86
src/error.rs
86
src/error.rs
@@ -34,21 +34,14 @@ pub(super) enum Kind {
|
||||
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
Io,
|
||||
/// Error occurred while connecting.
|
||||
#[allow(unused)]
|
||||
Connect,
|
||||
/// Error creating a TcpListener.
|
||||
#[cfg(all(feature = "tcp", feature = "server"))]
|
||||
Listen,
|
||||
/// Error accepting on an Incoming stream.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
Accept,
|
||||
/// User took too long to send headers
|
||||
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
|
||||
HeaderTimeout,
|
||||
/// Error while reading a body from connection.
|
||||
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
Body,
|
||||
/// Error while writing a body to connection.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
@@ -96,10 +89,6 @@ pub(super) enum User {
|
||||
Body,
|
||||
/// The user aborted writing of the outgoing body.
|
||||
BodyWriteAborted,
|
||||
/// Error calling user's MakeService.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
MakeService,
|
||||
/// Error from future of user's Service.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
Service,
|
||||
@@ -109,22 +98,10 @@ pub(super) enum User {
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
UnexpectedHeader,
|
||||
/// User tried to create a Request with bad version.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
UnsupportedVersion,
|
||||
/// User tried to create a CONNECT Request with the Client.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
UnsupportedRequestMethod,
|
||||
/// User tried to respond with a 1xx (not 101) response code.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg(feature = "server")]
|
||||
UnsupportedStatusCode,
|
||||
/// User tried to send a Request with Client with non-absolute URI.
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
AbsoluteUriRequired,
|
||||
|
||||
/// User tried polling for an upgrade that doesn't exist.
|
||||
NoUpgrade,
|
||||
@@ -181,11 +158,6 @@ impl Error {
|
||||
matches!(self.inner.kind, Kind::ChannelClosed)
|
||||
}
|
||||
|
||||
/// Returns true if this was an error from `Connect`.
|
||||
pub fn is_connect(&self) -> bool {
|
||||
matches!(self.inner.kind, Kind::Connect)
|
||||
}
|
||||
|
||||
/// Returns true if the connection closed before a message could complete.
|
||||
pub fn is_incomplete_message(&self) -> bool {
|
||||
matches!(self.inner.kind, Kind::IncompleteMessage)
|
||||
@@ -278,23 +250,11 @@ impl Error {
|
||||
Error::new(Kind::Listen).with(cause)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
pub(super) fn new_accept<E: Into<Cause>>(cause: E) -> Error {
|
||||
Error::new(Kind::Accept).with(cause)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
pub(super) fn new_connect<E: Into<Cause>>(cause: E) -> Error {
|
||||
Error::new(Kind::Connect).with(cause)
|
||||
}
|
||||
|
||||
pub(super) fn new_closed() -> Error {
|
||||
Error::new(Kind::ChannelClosed)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error {
|
||||
Error::new(Kind::Body).with(cause)
|
||||
}
|
||||
@@ -323,30 +283,12 @@ impl Error {
|
||||
Error::new(Kind::HeaderTimeout)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
pub(super) fn new_user_unsupported_version() -> Error {
|
||||
Error::new_user(User::UnsupportedVersion)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
pub(super) fn new_user_unsupported_request_method() -> Error {
|
||||
Error::new_user(User::UnsupportedRequestMethod)
|
||||
}
|
||||
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg(feature = "server")]
|
||||
pub(super) fn new_user_unsupported_status_code() -> Error {
|
||||
Error::new_user(User::UnsupportedStatusCode)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
pub(super) fn new_user_absolute_uri_required() -> Error {
|
||||
Error::new_user(User::AbsoluteUriRequired)
|
||||
}
|
||||
|
||||
pub(super) fn new_user_no_upgrade() -> Error {
|
||||
Error::new_user(User::NoUpgrade)
|
||||
}
|
||||
@@ -356,12 +298,6 @@ impl Error {
|
||||
Error::new_user(User::ManualUpgrade)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
pub(super) fn new_user_make_service<E: Into<Cause>>(cause: E) -> Error {
|
||||
Error::new_user(User::MakeService).with(cause)
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error {
|
||||
Error::new_user(User::Service).with(cause)
|
||||
@@ -431,16 +367,12 @@ impl Error {
|
||||
#[cfg(feature = "http1")]
|
||||
Kind::UnexpectedMessage => "received unexpected message from connection",
|
||||
Kind::ChannelClosed => "channel closed",
|
||||
Kind::Connect => "error trying to connect",
|
||||
Kind::Canceled => "operation was canceled",
|
||||
#[cfg(all(feature = "server", feature = "tcp"))]
|
||||
Kind::Listen => "error creating server listener",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
Kind::Accept => "error accepting connection",
|
||||
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
|
||||
Kind::HeaderTimeout => "read header from client timeout",
|
||||
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
Kind::Body => "error reading a body from connection",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
Kind::BodyWrite => "error writing a body to connection",
|
||||
@@ -455,27 +387,15 @@ impl Error {
|
||||
Kind::User(User::Body) => "error from user's HttpBody stream",
|
||||
Kind::User(User::BodyWriteAborted) => "user body write aborted",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
Kind::User(User::MakeService) => "error from user's MakeService",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
Kind::User(User::Service) => "error from user's Service",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "server")]
|
||||
Kind::User(User::UnexpectedHeader) => "user sent unexpected header",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
Kind::User(User::UnsupportedVersion) => "request has unsupported HTTP version",
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
Kind::User(User::UnsupportedRequestMethod) => "request has unsupported HTTP method",
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg(feature = "server")]
|
||||
Kind::User(User::UnsupportedStatusCode) => {
|
||||
"response has 1xx status code, not supported by server"
|
||||
}
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg(feature = "client")]
|
||||
Kind::User(User::AbsoluteUriRequired) => "client requires absolute-form URIs",
|
||||
Kind::User(User::NoUpgrade) => "no upgrade available",
|
||||
#[cfg(feature = "http1")]
|
||||
Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use",
|
||||
|
||||
107
src/ext.rs
107
src/ext.rs
@@ -1,12 +1,21 @@
|
||||
//! HTTP extensions.
|
||||
|
||||
use bytes::Bytes;
|
||||
#[cfg(any(feature = "http1", feature = "ffi"))]
|
||||
use http::header::HeaderName;
|
||||
#[cfg(feature = "http1")]
|
||||
use http::header::{HeaderName, IntoHeaderName, ValueIter};
|
||||
use http::header::{IntoHeaderName, ValueIter};
|
||||
use http::HeaderMap;
|
||||
#[cfg(feature = "ffi")]
|
||||
use std::collections::HashMap;
|
||||
#[cfg(feature = "http2")]
|
||||
use std::fmt;
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "ffi"))]
|
||||
mod h1_reason_phrase;
|
||||
#[cfg(any(feature = "http1", feature = "ffi"))]
|
||||
pub use h1_reason_phrase::ReasonPhrase;
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
/// Represents the `:protocol` pseudo-header used by
|
||||
/// the [Extended CONNECT Protocol].
|
||||
@@ -120,3 +129,99 @@ impl HeaderCaseMap {
|
||||
self.0.append(name, orig);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
#[derive(Clone, Debug)]
|
||||
/// Hashmap<Headername, numheaders with that name>
|
||||
pub(crate) struct OriginalHeaderOrder {
|
||||
/// Stores how many entries a Headername maps to. This is used
|
||||
/// for accounting.
|
||||
num_entries: HashMap<HeaderName, usize>,
|
||||
/// Stores the ordering of the headers. ex: `vec[i] = (headerName, idx)`,
|
||||
/// The vector is ordered such that the ith element
|
||||
/// represents the ith header that came in off the line.
|
||||
/// The `HeaderName` and `idx` are then used elsewhere to index into
|
||||
/// the multi map that stores the header values.
|
||||
entry_order: Vec<(HeaderName, usize)>,
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "http1", feature = "ffi"))]
|
||||
impl OriginalHeaderOrder {
|
||||
pub(crate) fn default() -> Self {
|
||||
OriginalHeaderOrder {
|
||||
num_entries: HashMap::new(),
|
||||
entry_order: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn insert(&mut self, name: HeaderName) {
|
||||
if !self.num_entries.contains_key(&name) {
|
||||
let idx = 0;
|
||||
self.num_entries.insert(name.clone(), 1);
|
||||
self.entry_order.push((name, idx));
|
||||
}
|
||||
// Replacing an already existing element does not
|
||||
// change ordering, so we only care if its the first
|
||||
// header name encountered
|
||||
}
|
||||
|
||||
pub(crate) fn append<N>(&mut self, name: N)
|
||||
where
|
||||
N: IntoHeaderName + Into<HeaderName> + Clone,
|
||||
{
|
||||
let name: HeaderName = name.into();
|
||||
let idx;
|
||||
if self.num_entries.contains_key(&name) {
|
||||
idx = self.num_entries[&name];
|
||||
*self.num_entries.get_mut(&name).unwrap() += 1;
|
||||
} else {
|
||||
idx = 0;
|
||||
self.num_entries.insert(name.clone(), 1);
|
||||
}
|
||||
self.entry_order.push((name, idx));
|
||||
}
|
||||
|
||||
// No doc test is run here because `RUSTFLAGS='--cfg hyper_unstable_ffi'`
|
||||
// is needed to compile. Once ffi is stablized `no_run` should be removed
|
||||
// here.
|
||||
/// This returns an iterator that provides header names and indexes
|
||||
/// in the original order received.
|
||||
///
|
||||
/// # Examples
|
||||
/// ```no_run
|
||||
/// use hyper::ext::OriginalHeaderOrder;
|
||||
/// use hyper::header::{HeaderName, HeaderValue, HeaderMap};
|
||||
///
|
||||
/// let mut h_order = OriginalHeaderOrder::default();
|
||||
/// let mut h_map = Headermap::new();
|
||||
///
|
||||
/// let name1 = b"Set-CookiE";
|
||||
/// let value1 = b"a=b";
|
||||
/// h_map.append(name1);
|
||||
/// h_order.append(name1);
|
||||
///
|
||||
/// let name2 = b"Content-Encoding";
|
||||
/// let value2 = b"gzip";
|
||||
/// h_map.append(name2, value2);
|
||||
/// h_order.append(name2);
|
||||
///
|
||||
/// let name3 = b"SET-COOKIE";
|
||||
/// let value3 = b"c=d";
|
||||
/// h_map.append(name3, value3);
|
||||
/// h_order.append(name3)
|
||||
///
|
||||
/// let mut iter = h_order.get_in_order()
|
||||
///
|
||||
/// let (name, idx) = iter.next();
|
||||
/// assert_eq!(b"a=b", h_map.get_all(name).nth(idx).unwrap());
|
||||
///
|
||||
/// let (name, idx) = iter.next();
|
||||
/// assert_eq!(b"gzip", h_map.get_all(name).nth(idx).unwrap());
|
||||
///
|
||||
/// let (name, idx) = iter.next();
|
||||
/// assert_eq!(b"c=d", h_map.get_all(name).nth(idx).unwrap());
|
||||
/// ```
|
||||
pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> {
|
||||
self.entry_order.iter()
|
||||
}
|
||||
}
|
||||
|
||||
221
src/ext/h1_reason_phrase.rs
Normal file
221
src/ext/h1_reason_phrase.rs
Normal file
@@ -0,0 +1,221 @@
|
||||
use std::convert::TryFrom;
|
||||
|
||||
use bytes::Bytes;
|
||||
|
||||
/// A reason phrase in an HTTP/1 response.
|
||||
///
|
||||
/// # Clients
|
||||
///
|
||||
/// For clients, a `ReasonPhrase` will be present in the extensions of the `http::Response` returned
|
||||
/// for a request if the reason phrase is different from the canonical reason phrase for the
|
||||
/// response's status code. For example, if a server returns `HTTP/1.1 200 Awesome`, the
|
||||
/// `ReasonPhrase` will be present and contain `Awesome`, but if a server returns `HTTP/1.1 200 OK`,
|
||||
/// the response will not contain a `ReasonPhrase`.
|
||||
///
|
||||
/// ```no_run
|
||||
/// # #[cfg(all(feature = "tcp", feature = "client", feature = "http1"))]
|
||||
/// # async fn fake_fetch() -> hyper::Result<()> {
|
||||
/// use hyper::{Client, Uri};
|
||||
/// use hyper::ext::ReasonPhrase;
|
||||
///
|
||||
/// let res = Client::new().get(Uri::from_static("http://example.com/non_canonical_reason")).await?;
|
||||
///
|
||||
/// // Print out the non-canonical reason phrase, if it has one...
|
||||
/// if let Some(reason) = res.extensions().get::<ReasonPhrase>() {
|
||||
/// println!("non-canonical reason: {}", std::str::from_utf8(reason.as_bytes()).unwrap());
|
||||
/// }
|
||||
/// # Ok(())
|
||||
/// # }
|
||||
/// ```
|
||||
///
|
||||
/// # Servers
|
||||
///
|
||||
/// When a `ReasonPhrase` is present in the extensions of the `http::Response` written by a server,
|
||||
/// its contents will be written in place of the canonical reason phrase when responding via HTTP/1.
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct ReasonPhrase(Bytes);
|
||||
|
||||
impl ReasonPhrase {
|
||||
/// Gets the reason phrase as bytes.
|
||||
pub fn as_bytes(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
|
||||
/// Converts a static byte slice to a reason phrase.
|
||||
pub fn from_static(reason: &'static [u8]) -> Self {
|
||||
// TODO: this can be made const once MSRV is >= 1.57.0
|
||||
if find_invalid_byte(reason).is_some() {
|
||||
panic!("invalid byte in static reason phrase");
|
||||
}
|
||||
Self(Bytes::from_static(reason))
|
||||
}
|
||||
|
||||
/// Converts a `Bytes` directly into a `ReasonPhrase` without validating.
|
||||
///
|
||||
/// Use with care; invalid bytes in a reason phrase can cause serious security problems if
|
||||
/// emitted in a response.
|
||||
pub unsafe fn from_bytes_unchecked(reason: Bytes) -> Self {
|
||||
Self(reason)
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&[u8]> for ReasonPhrase {
|
||||
type Error = InvalidReasonPhrase;
|
||||
|
||||
fn try_from(reason: &[u8]) -> Result<Self, Self::Error> {
|
||||
if let Some(bad_byte) = find_invalid_byte(reason) {
|
||||
Err(InvalidReasonPhrase { bad_byte })
|
||||
} else {
|
||||
Ok(Self(Bytes::copy_from_slice(reason)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Vec<u8>> for ReasonPhrase {
|
||||
type Error = InvalidReasonPhrase;
|
||||
|
||||
fn try_from(reason: Vec<u8>) -> Result<Self, Self::Error> {
|
||||
if let Some(bad_byte) = find_invalid_byte(&reason) {
|
||||
Err(InvalidReasonPhrase { bad_byte })
|
||||
} else {
|
||||
Ok(Self(Bytes::from(reason)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<String> for ReasonPhrase {
|
||||
type Error = InvalidReasonPhrase;
|
||||
|
||||
fn try_from(reason: String) -> Result<Self, Self::Error> {
|
||||
if let Some(bad_byte) = find_invalid_byte(reason.as_bytes()) {
|
||||
Err(InvalidReasonPhrase { bad_byte })
|
||||
} else {
|
||||
Ok(Self(Bytes::from(reason)))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<Bytes> for ReasonPhrase {
|
||||
type Error = InvalidReasonPhrase;
|
||||
|
||||
fn try_from(reason: Bytes) -> Result<Self, Self::Error> {
|
||||
if let Some(bad_byte) = find_invalid_byte(&reason) {
|
||||
Err(InvalidReasonPhrase { bad_byte })
|
||||
} else {
|
||||
Ok(Self(reason))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Into<Bytes> for ReasonPhrase {
|
||||
fn into(self) -> Bytes {
|
||||
self.0
|
||||
}
|
||||
}
|
||||
|
||||
impl AsRef<[u8]> for ReasonPhrase {
|
||||
fn as_ref(&self) -> &[u8] {
|
||||
&self.0
|
||||
}
|
||||
}
|
||||
|
||||
/// Error indicating an invalid byte when constructing a `ReasonPhrase`.
|
||||
///
|
||||
/// See [the spec][spec] for details on allowed bytes.
|
||||
///
|
||||
/// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7
|
||||
#[derive(Debug)]
|
||||
pub struct InvalidReasonPhrase {
|
||||
bad_byte: u8,
|
||||
}
|
||||
|
||||
impl std::fmt::Display for InvalidReasonPhrase {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Invalid byte in reason phrase: {}", self.bad_byte)
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for InvalidReasonPhrase {}
|
||||
|
||||
const fn is_valid_byte(b: u8) -> bool {
|
||||
// See https://www.rfc-editor.org/rfc/rfc5234.html#appendix-B.1
|
||||
const fn is_vchar(b: u8) -> bool {
|
||||
0x21 <= b && b <= 0x7E
|
||||
}
|
||||
|
||||
// See https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#fields.values
|
||||
//
|
||||
// The 0xFF comparison is technically redundant, but it matches the text of the spec more
|
||||
// clearly and will be optimized away.
|
||||
#[allow(unused_comparisons)]
|
||||
const fn is_obs_text(b: u8) -> bool {
|
||||
0x80 <= b && b <= 0xFF
|
||||
}
|
||||
|
||||
// See https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7
|
||||
b == b'\t' || b == b' ' || is_vchar(b) || is_obs_text(b)
|
||||
}
|
||||
|
||||
const fn find_invalid_byte(bytes: &[u8]) -> Option<u8> {
|
||||
let mut i = 0;
|
||||
while i < bytes.len() {
|
||||
let b = bytes[i];
|
||||
if !is_valid_byte(b) {
|
||||
return Some(b);
|
||||
}
|
||||
i += 1;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn basic_valid() {
|
||||
const PHRASE: &'static [u8] = b"OK";
|
||||
assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
|
||||
assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn empty_valid() {
|
||||
const PHRASE: &'static [u8] = b"";
|
||||
assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
|
||||
assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn obs_text_valid() {
|
||||
const PHRASE: &'static [u8] = b"hyp\xe9r";
|
||||
assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
|
||||
assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
|
||||
}
|
||||
|
||||
const NEWLINE_PHRASE: &'static [u8] = b"hyp\ner";
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn newline_invalid_panic() {
|
||||
ReasonPhrase::from_static(NEWLINE_PHRASE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn newline_invalid_err() {
|
||||
assert!(ReasonPhrase::try_from(NEWLINE_PHRASE).is_err());
|
||||
}
|
||||
|
||||
const CR_PHRASE: &'static [u8] = b"hyp\rer";
|
||||
|
||||
#[test]
|
||||
#[should_panic]
|
||||
fn cr_invalid_panic() {
|
||||
ReasonPhrase::from_static(CR_PHRASE);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cr_invalid_err() {
|
||||
assert!(ReasonPhrase::try_from(CR_PHRASE).is_err());
|
||||
}
|
||||
}
|
||||
@@ -93,8 +93,7 @@ unsafe impl AsTaskType for hyper_clientconn {
|
||||
ffi_fn! {
|
||||
/// Creates a new set of HTTP clientconn options to be used in a handshake.
|
||||
fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options {
|
||||
let mut builder = conn::Builder::new();
|
||||
builder.http1_preserve_header_case(true);
|
||||
let builder = conn::Builder::new();
|
||||
|
||||
Box::into_raw(Box::new(hyper_clientconn_options {
|
||||
builder,
|
||||
@@ -103,6 +102,26 @@ ffi_fn! {
|
||||
} ?= std::ptr::null_mut()
|
||||
}
|
||||
|
||||
ffi_fn! {
|
||||
/// Set the whether or not header case is preserved.
|
||||
///
|
||||
/// Pass `0` to allow lowercase normalization (default), `1` to retain original case.
|
||||
fn hyper_clientconn_options_set_preserve_header_case(opts: *mut hyper_clientconn_options, enabled: c_int) {
|
||||
let opts = non_null! { &mut *opts ?= () };
|
||||
opts.builder.http1_preserve_header_case(enabled != 0);
|
||||
}
|
||||
}
|
||||
|
||||
ffi_fn! {
|
||||
/// Set the whether or not header order is preserved.
|
||||
///
|
||||
/// Pass `0` to allow reordering (default), `1` to retain original ordering.
|
||||
fn hyper_clientconn_options_set_preserve_header_order(opts: *mut hyper_clientconn_options, enabled: c_int) {
|
||||
let opts = non_null! { &mut *opts ?= () };
|
||||
opts.builder.http1_preserve_header_order(enabled != 0);
|
||||
}
|
||||
}
|
||||
|
||||
ffi_fn! {
|
||||
/// Free a `hyper_clientconn_options *`.
|
||||
fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) {
|
||||
@@ -160,3 +179,16 @@ ffi_fn! {
|
||||
hyper_code::HYPERE_OK
|
||||
}
|
||||
}
|
||||
|
||||
ffi_fn! {
|
||||
/// Set whether HTTP/1 connections will accept obsolete line folding for header values.
|
||||
/// Newline codepoints (\r and \n) will be transformed to spaces when parsing.
|
||||
///
|
||||
/// Pass `0` to disable, `1` to enable.
|
||||
///
|
||||
fn hyper_clientconn_options_http1_allow_multiline_headers(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code {
|
||||
let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG };
|
||||
opts.builder.http1_allow_obsolete_multiline_headers_in_responses(enabled != 0);
|
||||
hyper_code::HYPERE_OK
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,7 @@ use super::body::{hyper_body, hyper_buf};
|
||||
use super::error::hyper_code;
|
||||
use super::task::{hyper_task_return_type, AsTaskType};
|
||||
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
|
||||
use crate::ext::HeaderCaseMap;
|
||||
use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase};
|
||||
use crate::header::{HeaderName, HeaderValue};
|
||||
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
|
||||
|
||||
@@ -22,11 +22,9 @@ pub struct hyper_response(pub(super) Response<Body>);
|
||||
pub struct hyper_headers {
|
||||
pub(super) headers: HeaderMap,
|
||||
orig_casing: HeaderCaseMap,
|
||||
orig_order: OriginalHeaderOrder,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ReasonPhrase(pub(crate) Bytes);
|
||||
|
||||
pub(crate) struct RawHeaders(pub(crate) hyper_buf);
|
||||
|
||||
pub(crate) struct OnInformational {
|
||||
@@ -233,6 +231,7 @@ impl hyper_request {
|
||||
if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() {
|
||||
*self.0.headers_mut() = headers.headers;
|
||||
self.0.extensions_mut().insert(headers.orig_casing);
|
||||
self.0.extensions_mut().insert(headers.orig_order);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -348,9 +347,14 @@ impl hyper_response {
|
||||
.extensions_mut()
|
||||
.remove::<HeaderCaseMap>()
|
||||
.unwrap_or_else(HeaderCaseMap::default);
|
||||
let orig_order = resp
|
||||
.extensions_mut()
|
||||
.remove::<OriginalHeaderOrder>()
|
||||
.unwrap_or_else(OriginalHeaderOrder::default);
|
||||
resp.extensions_mut().insert(hyper_headers {
|
||||
headers,
|
||||
orig_casing,
|
||||
orig_order,
|
||||
});
|
||||
|
||||
hyper_response(resp)
|
||||
@@ -358,7 +362,7 @@ impl hyper_response {
|
||||
|
||||
fn reason_phrase(&self) -> &[u8] {
|
||||
if let Some(reason) = self.0.extensions().get::<ReasonPhrase>() {
|
||||
return &reason.0;
|
||||
return reason.as_bytes();
|
||||
}
|
||||
|
||||
if let Some(reason) = self.0.status().canonical_reason() {
|
||||
@@ -404,26 +408,54 @@ ffi_fn! {
|
||||
// and for each one, try to pair the originally cased name with the value.
|
||||
//
|
||||
// TODO: consider adding http::HeaderMap::entries() iterator
|
||||
for name in headers.headers.keys() {
|
||||
let mut names = headers.orig_casing.get_all(name);
|
||||
|
||||
for value in headers.headers.get_all(name) {
|
||||
let (name_ptr, name_len) = if let Some(orig_name) = names.next() {
|
||||
let mut ordered_iter = headers.orig_order.get_in_order().peekable();
|
||||
if ordered_iter.peek().is_some() {
|
||||
for (name, idx) in ordered_iter {
|
||||
let (name_ptr, name_len) = if let Some(orig_name) = headers.orig_casing.get_all(name).nth(*idx) {
|
||||
(orig_name.as_ref().as_ptr(), orig_name.as_ref().len())
|
||||
} else {
|
||||
(
|
||||
name.as_str().as_bytes().as_ptr(),
|
||||
name.as_str().as_bytes().len(),
|
||||
name.as_str().as_bytes().as_ptr(),
|
||||
name.as_str().as_bytes().len(),
|
||||
)
|
||||
};
|
||||
|
||||
let val_ptr = value.as_bytes().as_ptr();
|
||||
let val_len = value.as_bytes().len();
|
||||
let val_ptr;
|
||||
let val_len;
|
||||
if let Some(value) = headers.headers.get_all(name).iter().nth(*idx) {
|
||||
val_ptr = value.as_bytes().as_ptr();
|
||||
val_len = value.as_bytes().len();
|
||||
} else {
|
||||
// Stop iterating, something has gone wrong.
|
||||
return;
|
||||
}
|
||||
|
||||
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for name in headers.headers.keys() {
|
||||
let mut names = headers.orig_casing.get_all(name);
|
||||
|
||||
for value in headers.headers.get_all(name) {
|
||||
let (name_ptr, name_len) = if let Some(orig_name) = names.next() {
|
||||
(orig_name.as_ref().as_ptr(), orig_name.as_ref().len())
|
||||
} else {
|
||||
(
|
||||
name.as_str().as_bytes().as_ptr(),
|
||||
name.as_str().as_bytes().len(),
|
||||
)
|
||||
};
|
||||
|
||||
let val_ptr = value.as_bytes().as_ptr();
|
||||
let val_len = value.as_bytes().len();
|
||||
|
||||
if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -437,7 +469,8 @@ ffi_fn! {
|
||||
match unsafe { raw_name_value(name, name_len, value, value_len) } {
|
||||
Ok((name, value, orig_name)) => {
|
||||
headers.headers.insert(&name, value);
|
||||
headers.orig_casing.insert(name, orig_name);
|
||||
headers.orig_casing.insert(name.clone(), orig_name.clone());
|
||||
headers.orig_order.insert(name);
|
||||
hyper_code::HYPERE_OK
|
||||
}
|
||||
Err(code) => code,
|
||||
@@ -456,7 +489,8 @@ ffi_fn! {
|
||||
match unsafe { raw_name_value(name, name_len, value, value_len) } {
|
||||
Ok((name, value, orig_name)) => {
|
||||
headers.headers.append(&name, value);
|
||||
headers.orig_casing.append(name, orig_name);
|
||||
headers.orig_casing.append(&name, orig_name.clone());
|
||||
headers.orig_order.append(name);
|
||||
hyper_code::HYPERE_OK
|
||||
}
|
||||
Err(code) => code,
|
||||
@@ -469,6 +503,7 @@ impl Default for hyper_headers {
|
||||
Self {
|
||||
headers: Default::default(),
|
||||
orig_casing: HeaderCaseMap::default(),
|
||||
orig_order: OriginalHeaderOrder::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -555,4 +590,68 @@ mod tests {
|
||||
HYPER_ITER_CONTINUE
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "http1", feature = "ffi"))]
|
||||
#[test]
|
||||
fn test_headers_foreach_order_preserved() {
|
||||
let mut headers = hyper_headers::default();
|
||||
|
||||
let name1 = b"Set-CookiE";
|
||||
let value1 = b"a=b";
|
||||
hyper_headers_add(
|
||||
&mut headers,
|
||||
name1.as_ptr(),
|
||||
name1.len(),
|
||||
value1.as_ptr(),
|
||||
value1.len(),
|
||||
);
|
||||
|
||||
let name2 = b"Content-Encoding";
|
||||
let value2 = b"gzip";
|
||||
hyper_headers_add(
|
||||
&mut headers,
|
||||
name2.as_ptr(),
|
||||
name2.len(),
|
||||
value2.as_ptr(),
|
||||
value2.len(),
|
||||
);
|
||||
|
||||
let name3 = b"SET-COOKIE";
|
||||
let value3 = b"c=d";
|
||||
hyper_headers_add(
|
||||
&mut headers,
|
||||
name3.as_ptr(),
|
||||
name3.len(),
|
||||
value3.as_ptr(),
|
||||
value3.len(),
|
||||
);
|
||||
|
||||
let mut vec = Vec::<u8>::new();
|
||||
hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void);
|
||||
|
||||
println!("{}", std::str::from_utf8(&vec).unwrap());
|
||||
assert_eq!(
|
||||
vec,
|
||||
b"Set-CookiE: a=b\r\nContent-Encoding: gzip\r\nSET-COOKIE: c=d\r\n"
|
||||
);
|
||||
|
||||
extern "C" fn concat(
|
||||
vec: *mut c_void,
|
||||
name: *const u8,
|
||||
name_len: usize,
|
||||
value: *const u8,
|
||||
value_len: usize,
|
||||
) -> c_int {
|
||||
unsafe {
|
||||
let vec = &mut *(vec as *mut Vec<u8>);
|
||||
let name = std::slice::from_raw_parts(name, name_len);
|
||||
let value = std::slice::from_raw_parts(value, value_len);
|
||||
vec.extend(name);
|
||||
vec.extend(b": ");
|
||||
vec.extend(value);
|
||||
vec.extend(b"\r\n");
|
||||
}
|
||||
HYPER_ITER_CONTINUE
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -32,7 +32,7 @@ pub struct hyper_executor {
|
||||
/// The executor of all task futures.
|
||||
///
|
||||
/// There should never be contention on the mutex, as it is only locked
|
||||
/// to drive the futures. However, we cannot gaurantee proper usage from
|
||||
/// to drive the futures. However, we cannot guarantee proper usage from
|
||||
/// `hyper_executor_poll()`, which in C could potentially be called inside
|
||||
/// one of the stored futures. The mutex isn't re-entrant, so doing so
|
||||
/// would result in a deadlock, but that's better than data corruption.
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
#![deny(missing_debug_implementations)]
|
||||
#![cfg_attr(test, deny(rust_2018_idioms))]
|
||||
#![cfg_attr(all(test, feature = "full"), deny(unreachable_pub))]
|
||||
#![cfg_attr(test, deny(warnings))]
|
||||
#![cfg_attr(all(test, feature = "full"), deny(warnings))]
|
||||
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
||||
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||
|
||||
@@ -51,8 +51,6 @@
|
||||
//! - `server`: Enables the HTTP `server`.
|
||||
//! - `runtime`: Enables convenient integration with `tokio`, providing
|
||||
//! connectors and acceptors for TCP, and a default executor.
|
||||
//! - `tcp`: Enables convenient implementations over TCP (using tokio).
|
||||
//! - `stream`: Provides `futures::Stream` capabilities.
|
||||
//!
|
||||
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
|
||||
|
||||
@@ -95,15 +93,10 @@ cfg_feature! {
|
||||
#![feature = "client"]
|
||||
|
||||
pub mod client;
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[doc(no_inline)]
|
||||
pub use crate::client::Client;
|
||||
}
|
||||
|
||||
cfg_feature! {
|
||||
#![feature = "server"]
|
||||
|
||||
pub mod server;
|
||||
#[doc(no_inline)]
|
||||
pub use crate::server::Server;
|
||||
}
|
||||
|
||||
@@ -58,6 +58,8 @@ where
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
h1_header_read_timeout_running: false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
title_case_headers: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
@@ -111,6 +113,11 @@ where
|
||||
self.state.preserve_header_case = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
pub(crate) fn set_preserve_header_order(&mut self) {
|
||||
self.state.preserve_header_order = true;
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
pub(crate) fn set_h09_responses(&mut self) {
|
||||
self.state.h09_responses = true;
|
||||
@@ -148,19 +155,15 @@ where
|
||||
}
|
||||
|
||||
pub(crate) fn can_read_head(&self) -> bool {
|
||||
match self.state.reading {
|
||||
Reading::Init => {
|
||||
if T::should_read_first() {
|
||||
true
|
||||
} else {
|
||||
match self.state.writing {
|
||||
Writing::Init => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => false,
|
||||
if !matches!(self.state.reading, Reading::Init) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if T::should_read_first() {
|
||||
return true;
|
||||
}
|
||||
|
||||
!matches!(self.state.writing, Writing::Init)
|
||||
}
|
||||
|
||||
pub(crate) fn can_read_body(&self) -> bool {
|
||||
@@ -200,6 +203,8 @@ where
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running,
|
||||
preserve_header_case: self.state.preserve_header_case,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: self.state.preserve_header_order,
|
||||
h09_responses: self.state.h09_responses,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut self.state.on_informational,
|
||||
@@ -358,10 +363,10 @@ where
|
||||
}
|
||||
|
||||
fn is_mid_message(&self) -> bool {
|
||||
match (&self.state.reading, &self.state.writing) {
|
||||
(&Reading::Init, &Writing::Init) => false,
|
||||
_ => true,
|
||||
}
|
||||
!matches!(
|
||||
(&self.state.reading, &self.state.writing),
|
||||
(&Reading::Init, &Writing::Init)
|
||||
)
|
||||
}
|
||||
|
||||
// This will check to make sure the io object read is empty.
|
||||
@@ -484,11 +489,10 @@ where
|
||||
}
|
||||
|
||||
pub(crate) fn can_write_head(&self) -> bool {
|
||||
if !T::should_read_first() {
|
||||
if let Reading::Closed = self.state.reading {
|
||||
return false;
|
||||
}
|
||||
if !T::should_read_first() && matches!(self.state.reading, Reading::Closed) {
|
||||
return false;
|
||||
}
|
||||
|
||||
match self.state.writing {
|
||||
Writing::Init => self.io.can_headers_buf(),
|
||||
_ => false,
|
||||
@@ -582,7 +586,7 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
// Fix keep-alives when Connection: keep-alive header is not present
|
||||
// Fix keep-alive when Connection: keep-alive header is not present
|
||||
fn fix_keep_alive(&mut self, head: &mut MessageHead<T::Outgoing>) {
|
||||
let outgoing_is_keep_alive = head
|
||||
.headers
|
||||
@@ -632,15 +636,15 @@ where
|
||||
Writing::Body(ref mut encoder) => {
|
||||
self.io.buffer(encoder.encode(chunk));
|
||||
|
||||
if encoder.is_eof() {
|
||||
if encoder.is_last() {
|
||||
Writing::Closed
|
||||
} else {
|
||||
Writing::KeepAlive
|
||||
}
|
||||
} else {
|
||||
if !encoder.is_eof() {
|
||||
return;
|
||||
}
|
||||
|
||||
if encoder.is_last() {
|
||||
Writing::Closed
|
||||
} else {
|
||||
Writing::KeepAlive
|
||||
}
|
||||
}
|
||||
_ => unreachable!("write_body invalid state: {:?}", self.state.writing),
|
||||
};
|
||||
@@ -671,32 +675,31 @@ where
|
||||
pub(crate) fn end_body(&mut self) -> crate::Result<()> {
|
||||
debug_assert!(self.can_write_body());
|
||||
|
||||
let mut res = Ok(());
|
||||
let state = match self.state.writing {
|
||||
Writing::Body(ref mut encoder) => {
|
||||
// end of stream, that means we should try to eof
|
||||
match encoder.end() {
|
||||
Ok(end) => {
|
||||
if let Some(end) = end {
|
||||
self.io.buffer(end);
|
||||
}
|
||||
if encoder.is_last() || encoder.is_close_delimited() {
|
||||
Writing::Closed
|
||||
} else {
|
||||
Writing::KeepAlive
|
||||
}
|
||||
}
|
||||
Err(not_eof) => {
|
||||
res = Err(crate::Error::new_body_write_aborted().with(not_eof));
|
||||
Writing::Closed
|
||||
}
|
||||
}
|
||||
}
|
||||
let encoder = match self.state.writing {
|
||||
Writing::Body(ref mut enc) => enc,
|
||||
_ => return Ok(()),
|
||||
};
|
||||
|
||||
self.state.writing = state;
|
||||
res
|
||||
// end of stream, that means we should try to eof
|
||||
match encoder.end() {
|
||||
Ok(end) => {
|
||||
if let Some(end) = end {
|
||||
self.io.buffer(end);
|
||||
}
|
||||
|
||||
self.state.writing = if encoder.is_last() || encoder.is_close_delimited() {
|
||||
Writing::Closed
|
||||
} else {
|
||||
Writing::KeepAlive
|
||||
};
|
||||
|
||||
Ok(())
|
||||
}
|
||||
Err(not_eof) => {
|
||||
self.state.writing = Writing::Closed;
|
||||
Err(crate::Error::new_body_write_aborted().with(not_eof))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// When we get a parse error, depending on what side we are, we might be able
|
||||
@@ -749,10 +752,7 @@ where
|
||||
|
||||
// If still in Reading::Body, just give up
|
||||
match self.state.reading {
|
||||
Reading::Init | Reading::KeepAlive => {
|
||||
trace!("body drained");
|
||||
return;
|
||||
}
|
||||
Reading::Init | Reading::KeepAlive => trace!("body drained"),
|
||||
_ => self.close_read(),
|
||||
}
|
||||
}
|
||||
@@ -824,6 +824,8 @@ struct State {
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
h1_header_read_timeout_running: bool,
|
||||
preserve_header_case: bool,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: bool,
|
||||
title_case_headers: bool,
|
||||
h09_responses: bool,
|
||||
/// If set, called with each 1xx informational response received for
|
||||
@@ -1001,43 +1003,35 @@ impl State {
|
||||
|
||||
self.method = None;
|
||||
self.keep_alive.idle();
|
||||
if self.is_idle() {
|
||||
self.reading = Reading::Init;
|
||||
self.writing = Writing::Init;
|
||||
|
||||
// !T::should_read_first() means Client.
|
||||
//
|
||||
// If Client connection has just gone idle, the Dispatcher
|
||||
// should try the poll loop one more time, so as to poll the
|
||||
// pending requests stream.
|
||||
if !T::should_read_first() {
|
||||
self.notify_read = true;
|
||||
}
|
||||
} else {
|
||||
if !self.is_idle() {
|
||||
self.close();
|
||||
return;
|
||||
}
|
||||
|
||||
self.reading = Reading::Init;
|
||||
self.writing = Writing::Init;
|
||||
|
||||
// !T::should_read_first() means Client.
|
||||
//
|
||||
// If Client connection has just gone idle, the Dispatcher
|
||||
// should try the poll loop one more time, so as to poll the
|
||||
// pending requests stream.
|
||||
if !T::should_read_first() {
|
||||
self.notify_read = true;
|
||||
}
|
||||
}
|
||||
|
||||
fn is_idle(&self) -> bool {
|
||||
if let KA::Idle = self.keep_alive.status() {
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
matches!(self.keep_alive.status(), KA::Idle)
|
||||
}
|
||||
|
||||
fn is_read_closed(&self) -> bool {
|
||||
match self.reading {
|
||||
Reading::Closed => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.reading, Reading::Closed)
|
||||
}
|
||||
|
||||
fn is_write_closed(&self) -> bool {
|
||||
match self.writing {
|
||||
Writing::Closed => true,
|
||||
_ => false,
|
||||
}
|
||||
matches!(self.writing, Writing::Closed)
|
||||
}
|
||||
|
||||
fn prepare_upgrade(&mut self) -> crate::upgrade::OnUpgrade {
|
||||
|
||||
@@ -1,17 +1,17 @@
|
||||
use std::cmp;
|
||||
use std::fmt;
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use std::future::Future;
|
||||
use std::io::{self, IoSlice};
|
||||
use std::marker::Unpin;
|
||||
use std::mem::MaybeUninit;
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use std::future::Future;
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use tokio::time::Instant;
|
||||
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use tokio::time::Instant;
|
||||
use tracing::{debug, trace};
|
||||
|
||||
use super::{Http1Transaction, ParseContext, ParsedMessage};
|
||||
@@ -194,6 +194,8 @@ where
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
h1_header_read_timeout_running: parse_ctx.h1_header_read_timeout_running,
|
||||
preserve_header_case: parse_ctx.preserve_header_case,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: parse_ctx.preserve_header_order,
|
||||
h09_responses: parse_ctx.h09_responses,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: parse_ctx.on_informational,
|
||||
@@ -208,9 +210,13 @@ where
|
||||
{
|
||||
*parse_ctx.h1_header_read_timeout_running = false;
|
||||
|
||||
if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut {
|
||||
if let Some(h1_header_read_timeout_fut) =
|
||||
parse_ctx.h1_header_read_timeout_fut
|
||||
{
|
||||
// Reset the timer in order to avoid woken up when the timeout finishes
|
||||
h1_header_read_timeout_fut.as_mut().reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60));
|
||||
h1_header_read_timeout_fut
|
||||
.as_mut()
|
||||
.reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60));
|
||||
}
|
||||
}
|
||||
return Poll::Ready(Ok(msg));
|
||||
@@ -224,12 +230,14 @@ where
|
||||
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
if *parse_ctx.h1_header_read_timeout_running {
|
||||
if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut {
|
||||
if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() {
|
||||
if let Some(h1_header_read_timeout_fut) =
|
||||
parse_ctx.h1_header_read_timeout_fut
|
||||
{
|
||||
if Pin::new(h1_header_read_timeout_fut).poll(cx).is_ready() {
|
||||
*parse_ctx.h1_header_read_timeout_running = false;
|
||||
|
||||
tracing::warn!("read header from client timeout");
|
||||
return Poll::Ready(Err(crate::Error::new_header_timeout()))
|
||||
return Poll::Ready(Err(crate::Error::new_header_timeout()));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -727,10 +735,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -894,9 +907,7 @@ mod tests {
|
||||
async fn write_buf_flatten() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let mock = Mock::new()
|
||||
.write(b"hello world, it's hyper!")
|
||||
.build();
|
||||
let mock = Mock::new().write(b"hello world, it's hyper!").build();
|
||||
|
||||
let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock);
|
||||
buffered.write_buf.set_strategy(WriteStrategy::Flatten);
|
||||
|
||||
@@ -14,7 +14,7 @@ pub(crate) use self::conn::Conn;
|
||||
pub(crate) use self::decode::Decoder;
|
||||
pub(crate) use self::dispatch::Dispatcher;
|
||||
pub(crate) use self::encode::{EncodedBuf, Encoder};
|
||||
//TODO: move out of h1::io
|
||||
//TODO: move out of h1::io
|
||||
pub(crate) use self::io::MINIMUM_MAX_BUFFER_SIZE;
|
||||
|
||||
mod conn;
|
||||
@@ -24,7 +24,6 @@ mod encode;
|
||||
mod io;
|
||||
mod role;
|
||||
|
||||
|
||||
cfg_client! {
|
||||
pub(crate) type ClientTransaction = role::Client;
|
||||
}
|
||||
@@ -84,6 +83,8 @@ pub(crate) struct ParseContext<'a> {
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
h1_header_read_timeout_running: &'a mut bool,
|
||||
preserve_header_case: bool,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: bool,
|
||||
h09_responses: bool,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &'a mut Option<crate::ffi::OnInformational>,
|
||||
|
||||
@@ -1,15 +1,14 @@
|
||||
use std::fmt::{self, Write};
|
||||
use std::mem::MaybeUninit;
|
||||
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use tokio::time::Instant;
|
||||
#[cfg(any(test, feature = "server", feature = "ffi"))]
|
||||
use bytes::Bytes;
|
||||
use bytes::BytesMut;
|
||||
#[cfg(feature = "server")]
|
||||
use http::header::ValueIter;
|
||||
use http::header::{self, Entry, HeaderName, HeaderValue};
|
||||
use http::{HeaderMap, Method, StatusCode, Version};
|
||||
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||
use tokio::time::Instant;
|
||||
use tracing::{debug, error, trace, trace_span, warn};
|
||||
|
||||
use crate::body::DecodedLength;
|
||||
@@ -17,6 +16,8 @@ use crate::body::DecodedLength;
|
||||
use crate::common::date;
|
||||
use crate::error::Parse;
|
||||
use crate::ext::HeaderCaseMap;
|
||||
#[cfg(feature = "ffi")]
|
||||
use crate::ext::OriginalHeaderOrder;
|
||||
use crate::headers;
|
||||
use crate::proto::h1::{
|
||||
Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage,
|
||||
@@ -78,7 +79,7 @@ where
|
||||
if !*ctx.h1_header_read_timeout_running {
|
||||
if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout {
|
||||
let deadline = Instant::now() + h1_header_read_timeout;
|
||||
|
||||
*ctx.h1_header_read_timeout_running = true;
|
||||
match ctx.h1_header_read_timeout_fut {
|
||||
Some(h1_header_read_timeout_fut) => {
|
||||
debug!("resetting h1 header read timeout timer");
|
||||
@@ -214,6 +215,13 @@ impl Http1Transaction for Server {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
let mut header_order = if ctx.preserve_header_order {
|
||||
Some(OriginalHeaderOrder::default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new);
|
||||
|
||||
headers.reserve(headers_len);
|
||||
@@ -290,6 +298,11 @@ impl Http1Transaction for Server {
|
||||
header_case_map.append(&name, slice.slice(header.name.0..header.name.1));
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
if let Some(ref mut header_order) = header_order {
|
||||
header_order.append(&name);
|
||||
}
|
||||
|
||||
headers.append(name, value);
|
||||
}
|
||||
|
||||
@@ -304,6 +317,11 @@ impl Http1Transaction for Server {
|
||||
extensions.insert(header_case_map);
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
if let Some(header_order) = header_order {
|
||||
extensions.insert(header_order);
|
||||
}
|
||||
|
||||
*ctx.req_method = Some(subject.0.clone());
|
||||
|
||||
Ok(Some(ParsedMessage {
|
||||
@@ -358,7 +376,13 @@ impl Http1Transaction for Server {
|
||||
|
||||
let init_cap = 30 + msg.head.headers.len() * AVERAGE_HEADER_SIZE;
|
||||
dst.reserve(init_cap);
|
||||
if msg.head.version == Version::HTTP_11 && msg.head.subject == StatusCode::OK {
|
||||
|
||||
let custom_reason_phrase = msg.head.extensions.get::<crate::ext::ReasonPhrase>();
|
||||
|
||||
if msg.head.version == Version::HTTP_11
|
||||
&& msg.head.subject == StatusCode::OK
|
||||
&& custom_reason_phrase.is_none()
|
||||
{
|
||||
extend(dst, b"HTTP/1.1 200 OK\r\n");
|
||||
} else {
|
||||
match msg.head.version {
|
||||
@@ -373,15 +397,21 @@ impl Http1Transaction for Server {
|
||||
|
||||
extend(dst, msg.head.subject.as_str().as_bytes());
|
||||
extend(dst, b" ");
|
||||
// a reason MUST be written, as many parsers will expect it.
|
||||
extend(
|
||||
dst,
|
||||
msg.head
|
||||
.subject
|
||||
.canonical_reason()
|
||||
.unwrap_or("<none>")
|
||||
.as_bytes(),
|
||||
);
|
||||
|
||||
if let Some(reason) = custom_reason_phrase {
|
||||
extend(dst, reason.as_bytes());
|
||||
} else {
|
||||
// a reason MUST be written, as many parsers will expect it.
|
||||
extend(
|
||||
dst,
|
||||
msg.head
|
||||
.subject
|
||||
.canonical_reason()
|
||||
.unwrap_or("<none>")
|
||||
.as_bytes(),
|
||||
);
|
||||
}
|
||||
|
||||
extend(dst, b"\r\n");
|
||||
}
|
||||
|
||||
@@ -468,6 +498,10 @@ impl Server {
|
||||
}
|
||||
}
|
||||
|
||||
fn can_have_implicit_zero_content_length(method: &Option<Method>, status: StatusCode) -> bool {
|
||||
Server::can_have_content_length(method, status) && method != &Some(Method::HEAD)
|
||||
}
|
||||
|
||||
fn encode_headers_with_lower_case(
|
||||
msg: Encode<'_, StatusCode>,
|
||||
dst: &mut Vec<u8>,
|
||||
@@ -820,7 +854,10 @@ impl Server {
|
||||
}
|
||||
}
|
||||
None | Some(BodyLength::Known(0)) => {
|
||||
if Server::can_have_content_length(msg.req_method, msg.head.subject) {
|
||||
if Server::can_have_implicit_zero_content_length(
|
||||
msg.req_method,
|
||||
msg.head.subject,
|
||||
) {
|
||||
header_name_writer.write_full_header_line(
|
||||
dst,
|
||||
"content-length: 0\r\n",
|
||||
@@ -918,12 +955,9 @@ impl Http1Transaction for Client {
|
||||
trace!("Response.parse Complete({})", len);
|
||||
let status = StatusCode::from_u16(res.code.unwrap())?;
|
||||
|
||||
#[cfg(not(feature = "ffi"))]
|
||||
let reason = ();
|
||||
#[cfg(feature = "ffi")]
|
||||
let reason = {
|
||||
let reason = res.reason.unwrap();
|
||||
// Only save the reason phrase if it isnt the canonical reason
|
||||
// Only save the reason phrase if it isn't the canonical reason
|
||||
if Some(reason) != status.canonical_reason() {
|
||||
Some(Bytes::copy_from_slice(reason.as_bytes()))
|
||||
} else {
|
||||
@@ -944,12 +978,7 @@ impl Http1Transaction for Client {
|
||||
Err(httparse::Error::Version) if ctx.h09_responses => {
|
||||
trace!("Response.parse accepted HTTP/0.9 response");
|
||||
|
||||
#[cfg(not(feature = "ffi"))]
|
||||
let reason = ();
|
||||
#[cfg(feature = "ffi")]
|
||||
let reason = None;
|
||||
|
||||
(0, StatusCode::OK, reason, Version::HTTP_09, 0)
|
||||
(0, StatusCode::OK, None, Version::HTTP_09, 0)
|
||||
}
|
||||
Err(e) => return Err(e.into()),
|
||||
}
|
||||
@@ -957,15 +986,15 @@ impl Http1Transaction for Client {
|
||||
|
||||
let mut slice = buf.split_to(len);
|
||||
|
||||
if ctx.h1_parser_config.obsolete_multiline_headers_in_responses_are_allowed() {
|
||||
for header in &headers_indices[..headers_len] {
|
||||
if ctx
|
||||
.h1_parser_config
|
||||
.obsolete_multiline_headers_in_responses_are_allowed()
|
||||
{
|
||||
for header in &mut headers_indices[..headers_len] {
|
||||
// SAFETY: array is valid up to `headers_len`
|
||||
let header = unsafe { &*header.as_ptr() };
|
||||
for b in &mut slice[header.value.0..header.value.1] {
|
||||
if *b == b'\r' || *b == b'\n' {
|
||||
*b = b' ';
|
||||
}
|
||||
}
|
||||
let header = unsafe { &mut *header.as_mut_ptr() };
|
||||
Client::obs_fold_line(&mut slice, header);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@@ -981,6 +1010,13 @@ impl Http1Transaction for Client {
|
||||
None
|
||||
};
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
let mut header_order = if ctx.preserve_header_order {
|
||||
Some(OriginalHeaderOrder::default())
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
headers.reserve(headers_len);
|
||||
for header in &headers_indices[..headers_len] {
|
||||
// SAFETY: array is valid up to `headers_len`
|
||||
@@ -1003,6 +1039,11 @@ impl Http1Transaction for Client {
|
||||
header_case_map.append(&name, slice.slice(header.name.0..header.name.1));
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
if let Some(ref mut header_order) = header_order {
|
||||
header_order.append(&name);
|
||||
}
|
||||
|
||||
headers.append(name, value);
|
||||
}
|
||||
|
||||
@@ -1013,11 +1054,16 @@ impl Http1Transaction for Client {
|
||||
}
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
if let Some(reason) = reason {
|
||||
extensions.insert(crate::ffi::ReasonPhrase(reason));
|
||||
if let Some(header_order) = header_order {
|
||||
extensions.insert(header_order);
|
||||
}
|
||||
|
||||
if let Some(reason) = reason {
|
||||
// Safety: httparse ensures that only valid reason phrase bytes are present in this
|
||||
// field.
|
||||
let reason = unsafe { crate::ext::ReasonPhrase::from_bytes_unchecked(reason) };
|
||||
extensions.insert(reason);
|
||||
}
|
||||
#[cfg(not(feature = "ffi"))]
|
||||
drop(reason);
|
||||
|
||||
#[cfg(feature = "ffi")]
|
||||
if ctx.raw_headers {
|
||||
@@ -1295,6 +1341,65 @@ impl Client {
|
||||
|
||||
set_content_length(headers, len)
|
||||
}
|
||||
|
||||
fn obs_fold_line(all: &mut [u8], idx: &mut HeaderIndices) {
|
||||
// If the value has obs-folded text, then in-place shift the bytes out
|
||||
// of here.
|
||||
//
|
||||
// https://httpwg.org/specs/rfc9112.html#line.folding
|
||||
//
|
||||
// > A user agent that receives an obs-fold MUST replace each received
|
||||
// > obs-fold with one or more SP octets prior to interpreting the
|
||||
// > field value.
|
||||
//
|
||||
// This means strings like "\r\n\t foo" must replace the "\r\n\t " with
|
||||
// a single space.
|
||||
|
||||
let buf = &mut all[idx.value.0..idx.value.1];
|
||||
|
||||
// look for a newline, otherwise bail out
|
||||
let first_nl = match buf.iter().position(|b| *b == b'\n') {
|
||||
Some(i) => i,
|
||||
None => return,
|
||||
};
|
||||
|
||||
// not on standard slices because whatever, sigh
|
||||
fn trim_start(mut s: &[u8]) -> &[u8] {
|
||||
while let [first, rest @ ..] = s {
|
||||
if first.is_ascii_whitespace() {
|
||||
s = rest;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn trim_end(mut s: &[u8]) -> &[u8] {
|
||||
while let [rest @ .., last] = s {
|
||||
if last.is_ascii_whitespace() {
|
||||
s = rest;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
s
|
||||
}
|
||||
|
||||
fn trim(s: &[u8]) -> &[u8] {
|
||||
trim_start(trim_end(s))
|
||||
}
|
||||
|
||||
// TODO(perf): we could do the moves in-place, but this is so uncommon
|
||||
// that it shouldn't matter.
|
||||
let mut unfolded = trim_end(&buf[..first_nl]).to_vec();
|
||||
for line in buf[first_nl + 1..].split(|b| *b == b'\n') {
|
||||
unfolded.push(b' ');
|
||||
unfolded.extend_from_slice(trim(line));
|
||||
}
|
||||
buf[..unfolded.len()].copy_from_slice(&unfolded);
|
||||
idx.value.1 = idx.value.0 + unfolded.len();
|
||||
}
|
||||
}
|
||||
|
||||
fn set_content_length(headers: &mut HeaderMap, len: u64) -> Encoder {
|
||||
@@ -1474,10 +1579,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut method,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1504,10 +1614,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(crate::Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1529,10 +1644,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1552,10 +1672,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(crate::Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: true,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1577,10 +1702,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(crate::Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1606,10 +1736,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(crate::Method::GET),
|
||||
h1_parser_config,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1632,10 +1767,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(crate::Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1653,10 +1793,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: true,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1695,10 +1840,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1718,10 +1868,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1950,10 +2105,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1973,10 +2133,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(m),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -1996,10 +2161,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -2270,6 +2440,30 @@ mod tests {
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(feature = "client")]
|
||||
#[test]
|
||||
fn test_client_obs_fold_line() {
|
||||
fn unfold(src: &str) -> String {
|
||||
let mut buf = src.as_bytes().to_vec();
|
||||
let mut idx = HeaderIndices {
|
||||
name: (0, 0),
|
||||
value: (0, buf.len()),
|
||||
};
|
||||
Client::obs_fold_line(&mut buf, &mut idx);
|
||||
String::from_utf8(buf[idx.value.0 .. idx.value.1].to_vec()).unwrap()
|
||||
}
|
||||
|
||||
assert_eq!(
|
||||
unfold("a normal line"),
|
||||
"a normal line",
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
unfold("obs\r\n fold\r\n\t line"),
|
||||
"obs fold line",
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_client_request_encode_title_case() {
|
||||
use crate::proto::BodyLength;
|
||||
@@ -2496,10 +2690,15 @@ mod tests {
|
||||
cached_headers: &mut None,
|
||||
req_method: &mut Some(Method::GET),
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -2583,10 +2782,15 @@ mod tests {
|
||||
cached_headers: &mut headers,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
@@ -2626,10 +2830,15 @@ mod tests {
|
||||
cached_headers: &mut headers,
|
||||
req_method: &mut None,
|
||||
h1_parser_config: Default::default(),
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_fut: &mut None,
|
||||
#[cfg(feature = "runtime")]
|
||||
h1_header_read_timeout_running: &mut false,
|
||||
preserve_header_case: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
preserve_header_order: false,
|
||||
h09_responses: false,
|
||||
#[cfg(feature = "ffi")]
|
||||
on_informational: &mut None,
|
||||
|
||||
@@ -36,7 +36,7 @@ type ConnEof = oneshot::Receiver<Never>;
|
||||
// for performance.
|
||||
const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024 * 5; // 5mb
|
||||
const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb
|
||||
// const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
|
||||
const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
|
||||
const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -44,7 +44,7 @@ pub(crate) struct Config {
|
||||
pub(crate) adaptive_window: bool,
|
||||
pub(crate) initial_conn_window_size: u32,
|
||||
pub(crate) initial_stream_window_size: u32,
|
||||
pub(crate) max_frame_size: Option<u32>,
|
||||
pub(crate) max_frame_size: u32,
|
||||
#[cfg(feature = "runtime")]
|
||||
pub(crate) keep_alive_interval: Option<Duration>,
|
||||
#[cfg(feature = "runtime")]
|
||||
@@ -53,10 +53,6 @@ pub(crate) struct Config {
|
||||
pub(crate) keep_alive_while_idle: bool,
|
||||
pub(crate) max_concurrent_reset_streams: Option<usize>,
|
||||
pub(crate) max_send_buffer_size: usize,
|
||||
pub(crate) max_concurrent_streams: Option<u32>,
|
||||
pub(crate) max_header_list_size: Option<u32>,
|
||||
pub(crate) enable_push: Option<bool>,
|
||||
pub(crate) header_table_size: Option<u32>,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -65,7 +61,7 @@ impl Default for Config {
|
||||
adaptive_window: false,
|
||||
initial_conn_window_size: DEFAULT_CONN_WINDOW,
|
||||
initial_stream_window_size: DEFAULT_STREAM_WINDOW,
|
||||
max_frame_size: None,
|
||||
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
|
||||
#[cfg(feature = "runtime")]
|
||||
keep_alive_interval: None,
|
||||
#[cfg(feature = "runtime")]
|
||||
@@ -74,10 +70,6 @@ impl Default for Config {
|
||||
keep_alive_while_idle: false,
|
||||
max_concurrent_reset_streams: None,
|
||||
max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
|
||||
max_concurrent_streams: None,
|
||||
max_header_list_size: None,
|
||||
enable_push: None,
|
||||
header_table_size: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -87,26 +79,12 @@ fn new_builder(config: &Config) -> Builder {
|
||||
builder
|
||||
.initial_window_size(config.initial_stream_window_size)
|
||||
.initial_connection_window_size(config.initial_conn_window_size)
|
||||
.max_send_buffer_size(config.max_send_buffer_size);
|
||||
.max_frame_size(config.max_frame_size)
|
||||
.max_send_buffer_size(config.max_send_buffer_size)
|
||||
.enable_push(false);
|
||||
if let Some(max) = config.max_concurrent_reset_streams {
|
||||
builder.max_concurrent_reset_streams(max);
|
||||
}
|
||||
if let Some(max) = config.max_concurrent_streams {
|
||||
builder.max_concurrent_streams(max);
|
||||
}
|
||||
if let Some(max) = config.max_header_list_size {
|
||||
builder.max_header_list_size(max);
|
||||
}
|
||||
if let Some(opt) = config.enable_push {
|
||||
builder.enable_push(opt);
|
||||
}
|
||||
if let Some(max) = config.max_frame_size {
|
||||
builder.max_frame_size(max);
|
||||
}
|
||||
if let Some(max) = config.header_table_size {
|
||||
builder.header_table_size(max);
|
||||
}
|
||||
|
||||
builder
|
||||
}
|
||||
|
||||
|
||||
@@ -35,6 +35,8 @@ const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024; // 1mb
|
||||
const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb
|
||||
const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
|
||||
const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 400; // 400kb
|
||||
// 16 MB "sane default" taken from golang http2
|
||||
const DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: u32 = 16 << 20;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub(crate) struct Config {
|
||||
@@ -49,6 +51,7 @@ pub(crate) struct Config {
|
||||
#[cfg(feature = "runtime")]
|
||||
pub(crate) keep_alive_timeout: Duration,
|
||||
pub(crate) max_send_buffer_size: usize,
|
||||
pub(crate) max_header_list_size: u32,
|
||||
}
|
||||
|
||||
impl Default for Config {
|
||||
@@ -65,6 +68,7 @@ impl Default for Config {
|
||||
#[cfg(feature = "runtime")]
|
||||
keep_alive_timeout: Duration::from_secs(20),
|
||||
max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
|
||||
max_header_list_size: DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -116,6 +120,7 @@ where
|
||||
.initial_window_size(config.initial_stream_window_size)
|
||||
.initial_connection_window_size(config.initial_conn_window_size)
|
||||
.max_frame_size(config.max_frame_size)
|
||||
.max_header_list_size(config.max_header_list_size)
|
||||
.max_send_buffer_size(config.max_send_buffer_size);
|
||||
if let Some(max) = config.max_concurrent_streams {
|
||||
builder.max_concurrent_streams(max);
|
||||
@@ -138,7 +143,7 @@ where
|
||||
#[cfg(feature = "runtime")]
|
||||
keep_alive_timeout: config.keep_alive_timeout,
|
||||
// If keep-alive is enabled for servers, always enabled while
|
||||
// idle, so it can more aggresively close dead connections.
|
||||
// idle, so it can more aggressively close dead connections.
|
||||
#[cfg(feature = "runtime")]
|
||||
keep_alive_while_idle: true,
|
||||
};
|
||||
@@ -259,7 +264,7 @@ where
|
||||
let reason = err.h2_reason();
|
||||
if reason == Reason::NO_ERROR {
|
||||
// NO_ERROR is only used for graceful shutdowns...
|
||||
trace!("interpretting NO_ERROR user error as graceful_shutdown");
|
||||
trace!("interpreting NO_ERROR user error as graceful_shutdown");
|
||||
self.conn.graceful_shutdown();
|
||||
} else {
|
||||
trace!("abruptly shutting down with {:?}", reason);
|
||||
|
||||
@@ -1,111 +0,0 @@
|
||||
//! The `Accept` trait and supporting types.
|
||||
//!
|
||||
//! This module contains:
|
||||
//!
|
||||
//! - The [`Accept`](Accept) trait used to asynchronously accept incoming
|
||||
//! connections.
|
||||
//! - Utilities like `poll_fn` to ease creating a custom `Accept`.
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
use futures_core::Stream;
|
||||
#[cfg(feature = "stream")]
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
use crate::common::{
|
||||
task::{self, Poll},
|
||||
Pin,
|
||||
};
|
||||
|
||||
/// Asynchronously accept incoming connections.
|
||||
pub trait Accept {
|
||||
/// The connection type that can be accepted.
|
||||
type Conn;
|
||||
/// The error type that can occur when accepting a connection.
|
||||
type Error;
|
||||
|
||||
/// Poll to accept the next connection.
|
||||
fn poll_accept(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Conn, Self::Error>>>;
|
||||
}
|
||||
|
||||
/// Create an `Accept` with a polling function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// use std::task::Poll;
|
||||
/// use hyper::server::{accept, Server};
|
||||
///
|
||||
/// # let mock_conn = ();
|
||||
/// // If we created some mocked connection...
|
||||
/// let mut conn = Some(mock_conn);
|
||||
///
|
||||
/// // And accept just the mocked conn once...
|
||||
/// let once = accept::poll_fn(move |cx| {
|
||||
/// Poll::Ready(conn.take().map(Ok::<_, ()>))
|
||||
/// });
|
||||
///
|
||||
/// let builder = Server::builder(once);
|
||||
/// ```
|
||||
pub fn poll_fn<F, IO, E>(func: F) -> impl Accept<Conn = IO, Error = E>
|
||||
where
|
||||
F: FnMut(&mut task::Context<'_>) -> Poll<Option<Result<IO, E>>>,
|
||||
{
|
||||
struct PollFn<F>(F);
|
||||
|
||||
// The closure `F` is never pinned
|
||||
impl<F> Unpin for PollFn<F> {}
|
||||
|
||||
impl<F, IO, E> Accept for PollFn<F>
|
||||
where
|
||||
F: FnMut(&mut task::Context<'_>) -> Poll<Option<Result<IO, E>>>,
|
||||
{
|
||||
type Conn = IO;
|
||||
type Error = E;
|
||||
fn poll_accept(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||
(self.get_mut().0)(cx)
|
||||
}
|
||||
}
|
||||
|
||||
PollFn(func)
|
||||
}
|
||||
|
||||
/// Adapt a `Stream` of incoming connections into an `Accept`.
|
||||
///
|
||||
/// # Optional
|
||||
///
|
||||
/// This function requires enabling the `stream` feature in your
|
||||
/// `Cargo.toml`.
|
||||
#[cfg(feature = "stream")]
|
||||
pub fn from_stream<S, IO, E>(stream: S) -> impl Accept<Conn = IO, Error = E>
|
||||
where
|
||||
S: Stream<Item = Result<IO, E>>,
|
||||
{
|
||||
pin_project! {
|
||||
struct FromStream<S> {
|
||||
#[pin]
|
||||
stream: S,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, IO, E> Accept for FromStream<S>
|
||||
where
|
||||
S: Stream<Item = Result<IO, E>>,
|
||||
{
|
||||
type Conn = IO;
|
||||
type Error = E;
|
||||
fn poll_accept(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||
self.project().stream.poll_next(cx)
|
||||
}
|
||||
}
|
||||
|
||||
FromStream { stream }
|
||||
}
|
||||
@@ -5,9 +5,6 @@
|
||||
//! are not handled at this level. This module provides the building blocks to
|
||||
//! customize those things externally.
|
||||
//!
|
||||
//! If you don't have need to manage connections yourself, consider using the
|
||||
//! higher-level [Server](super) API.
|
||||
//!
|
||||
//! ## Example
|
||||
//! A simple example that uses the `Http` struct to talk HTTP over a Tokio TCP stream
|
||||
//! ```no_run
|
||||
@@ -48,8 +45,7 @@
|
||||
not(all(feature = "http1", feature = "http2"))
|
||||
))]
|
||||
use std::marker::PhantomData;
|
||||
#[cfg(feature = "tcp")]
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "runtime"))]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(feature = "http2")]
|
||||
@@ -70,34 +66,25 @@ cfg_feature! {
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tracing::trace;
|
||||
|
||||
use super::accept::Accept;
|
||||
use crate::body::{Body, HttpBody};
|
||||
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||
#[cfg(not(all(feature = "http1", feature = "http2")))]
|
||||
use crate::common::Never;
|
||||
use crate::common::exec::{ConnStreamExec, Exec, NewSvcExec};
|
||||
use crate::common::exec::{ConnStreamExec, Exec};
|
||||
use crate::proto;
|
||||
use crate::service::{HttpService, MakeServiceRef};
|
||||
use self::spawn_all::NewSvcTask;
|
||||
use crate::service::HttpService;
|
||||
|
||||
pub(super) use self::spawn_all::{NoopWatcher, Watcher};
|
||||
pub(super) use self::upgrades::UpgradeableConnection;
|
||||
}
|
||||
|
||||
#[cfg(feature = "tcp")]
|
||||
pub use super::tcp::{AddrIncoming, AddrStream};
|
||||
|
||||
/// A lower-level configuration of the HTTP protocol.
|
||||
///
|
||||
/// This structure is used to configure options for an HTTP server connection.
|
||||
///
|
||||
/// If you don't have need to manage connections yourself, consider using the
|
||||
/// higher-level [Server](super) API.
|
||||
#[derive(Clone, Debug)]
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
pub struct Http<E = Exec> {
|
||||
exec: E,
|
||||
pub(crate) exec: E,
|
||||
h1_half_close: bool,
|
||||
h1_keep_alive: bool,
|
||||
h1_title_case_headers: bool,
|
||||
@@ -127,51 +114,6 @@ enum ConnectionMode {
|
||||
Fallback,
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pin_project! {
|
||||
/// A stream mapping incoming IOs to new services.
|
||||
///
|
||||
/// Yields `Connecting`s that are futures that should be put on a reactor.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub(super) struct Serve<I, S, E = Exec> {
|
||||
#[pin]
|
||||
incoming: I,
|
||||
make_service: S,
|
||||
protocol: Http<E>,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pin_project! {
|
||||
/// A future building a new `Service` to a `Connection`.
|
||||
///
|
||||
/// Wraps the future returned from `MakeService` into one that returns
|
||||
/// a `Connection`.
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
pub struct Connecting<I, F, E = Exec> {
|
||||
#[pin]
|
||||
future: F,
|
||||
io: Option<I>,
|
||||
protocol: Http<E>,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pin_project! {
|
||||
#[must_use = "futures do nothing unless polled"]
|
||||
#[derive(Debug)]
|
||||
pub(super) struct SpawnAll<I, S, E> {
|
||||
// TODO: re-add `pub(super)` once rustdoc can handle this.
|
||||
//
|
||||
// See https://github.com/rust-lang/rust/issues/64705
|
||||
#[pin]
|
||||
pub(super) serve: Serve<I, S, E>,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pin_project! {
|
||||
/// A future binding a connection with a Service.
|
||||
@@ -375,7 +317,7 @@ impl<E> Http<E> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a timeout for reading client request headers. If a client does not
|
||||
/// Set a timeout for reading client request headers. If a client does not
|
||||
/// transmit the entire header within this time, the connection is closed.
|
||||
///
|
||||
/// Default is None.
|
||||
@@ -567,6 +509,16 @@ impl<E> Http<E> {
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the max size of received header frames.
|
||||
///
|
||||
/// Default is currently ~16MB, but may change.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_header_list_size(&mut self, max: u32) -> &mut Self {
|
||||
self.h2_builder.max_header_list_size = max;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum buffer size for the connection.
|
||||
///
|
||||
/// Default is ~400kb.
|
||||
@@ -719,23 +671,6 @@ impl<E> Http<E> {
|
||||
fallback: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn serve<I, IO, IE, S, Bd>(&self, incoming: I, make_service: S) -> Serve<I, S, E>
|
||||
where
|
||||
I: Accept<Conn = IO, Error = IE>,
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
IO: AsyncRead + AsyncWrite + Unpin,
|
||||
S: MakeServiceRef<IO, Body, ResBody = Bd>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
Bd: HttpBody,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, Bd>,
|
||||
{
|
||||
Serve {
|
||||
incoming,
|
||||
make_service,
|
||||
protocol: self.clone(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Connection =====
|
||||
@@ -987,141 +922,6 @@ impl Default for ConnectionMode {
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Serve =====
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
impl<I, S, E> Serve<I, S, E> {
|
||||
/// Get a reference to the incoming stream.
|
||||
#[inline]
|
||||
pub(super) fn incoming_ref(&self) -> &I {
|
||||
&self.incoming
|
||||
}
|
||||
|
||||
/*
|
||||
/// Get a mutable reference to the incoming stream.
|
||||
#[inline]
|
||||
pub fn incoming_mut(&mut self) -> &mut I {
|
||||
&mut self.incoming
|
||||
}
|
||||
*/
|
||||
|
||||
/// Spawn all incoming connections onto the executor in `Http`.
|
||||
pub(super) fn spawn_all(self) -> SpawnAll<I, S, E> {
|
||||
SpawnAll { serve: self }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
impl<I, IO, IE, S, B, E> Serve<I, S, E>
|
||||
where
|
||||
I: Accept<Conn = IO, Error = IE>,
|
||||
IO: AsyncRead + AsyncWrite + Unpin,
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
B: HttpBody,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
{
|
||||
fn poll_next_(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<crate::Result<Connecting<IO, S::Future, E>>>> {
|
||||
let me = self.project();
|
||||
match ready!(me.make_service.poll_ready_ref(cx)) {
|
||||
Ok(()) => (),
|
||||
Err(e) => {
|
||||
trace!("make_service closed");
|
||||
return Poll::Ready(Some(Err(crate::Error::new_user_make_service(e))));
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(item) = ready!(me.incoming.poll_accept(cx)) {
|
||||
let io = item.map_err(crate::Error::new_accept)?;
|
||||
let new_fut = me.make_service.make_service_ref(&io);
|
||||
Poll::Ready(Some(Ok(Connecting {
|
||||
future: new_fut,
|
||||
io: Some(io),
|
||||
protocol: me.protocol.clone(),
|
||||
})))
|
||||
} else {
|
||||
Poll::Ready(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Connecting =====
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
impl<I, F, S, FE, E, B> Future for Connecting<I, F, E>
|
||||
where
|
||||
I: AsyncRead + AsyncWrite + Unpin,
|
||||
F: Future<Output = Result<S, FE>>,
|
||||
S: HttpService<Body, ResBody = B>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
{
|
||||
type Output = Result<Connection<I, S, E>, FE>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut me = self.project();
|
||||
let service = ready!(me.future.poll(cx))?;
|
||||
let io = Option::take(&mut me.io).expect("polled after complete");
|
||||
Poll::Ready(Ok(me.protocol.serve_connection(io, service)))
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl SpawnAll =====
|
||||
|
||||
#[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))]
|
||||
impl<S, E> SpawnAll<AddrIncoming, S, E> {
|
||||
pub(super) fn local_addr(&self) -> SocketAddr {
|
||||
self.serve.incoming.local_addr()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
impl<I, S, E> SpawnAll<I, S, E> {
|
||||
pub(super) fn incoming_ref(&self) -> &I {
|
||||
self.serve.incoming_ref()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
impl<I, IO, IE, S, B, E> SpawnAll<I, S, E>
|
||||
where
|
||||
I: Accept<Conn = IO, Error = IE>,
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
B: HttpBody,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
{
|
||||
pub(super) fn poll_watch<W>(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
watcher: &W,
|
||||
) -> Poll<crate::Result<()>>
|
||||
where
|
||||
E: NewSvcExec<IO, S::Future, S::Service, E, W>,
|
||||
W: Watcher<IO, S::Service, E>,
|
||||
{
|
||||
let mut me = self.project();
|
||||
loop {
|
||||
if let Some(connecting) = ready!(me.serve.as_mut().poll_next_(cx)?) {
|
||||
let fut = NewSvcTask::new(connecting, watcher.clone());
|
||||
me.serve
|
||||
.as_mut()
|
||||
.project()
|
||||
.protocol
|
||||
.exec
|
||||
.execute_new_svc(fut);
|
||||
} else {
|
||||
return Poll::Ready(Ok(()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl ProtoServer =====
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
@@ -1151,150 +951,6 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pub(crate) mod spawn_all {
|
||||
use std::error::Error as StdError;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tracing::debug;
|
||||
|
||||
use super::{Connecting, UpgradeableConnection};
|
||||
use crate::body::{Body, HttpBody};
|
||||
use crate::common::exec::ConnStreamExec;
|
||||
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||
use crate::service::HttpService;
|
||||
use pin_project_lite::pin_project;
|
||||
|
||||
// Used by `SpawnAll` to optionally watch a `Connection` future.
|
||||
//
|
||||
// The regular `hyper::Server` just uses a `NoopWatcher`, which does
|
||||
// not need to watch anything, and so returns the `Connection` untouched.
|
||||
//
|
||||
// The `Server::with_graceful_shutdown` needs to keep track of all active
|
||||
// connections, and signal that they start to shutdown when prompted, so
|
||||
// it has a `GracefulWatcher` implementation to do that.
|
||||
pub trait Watcher<I, S: HttpService<Body>, E>: Clone {
|
||||
type Future: Future<Output = crate::Result<()>>;
|
||||
|
||||
fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future;
|
||||
}
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Copy, Clone)]
|
||||
pub struct NoopWatcher;
|
||||
|
||||
impl<I, S, E> Watcher<I, S, E> for NoopWatcher
|
||||
where
|
||||
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: HttpService<Body>,
|
||||
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||
S::ResBody: 'static,
|
||||
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
type Future = UpgradeableConnection<I, S, E>;
|
||||
|
||||
fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future {
|
||||
conn
|
||||
}
|
||||
}
|
||||
|
||||
// This is a `Future<Item=(), Error=()>` spawned to an `Executor` inside
|
||||
// the `SpawnAll`. By being a nameable type, we can be generic over the
|
||||
// user's `Service::Future`, and thus an `Executor` can execute it.
|
||||
//
|
||||
// Doing this allows for the server to conditionally require `Send` futures,
|
||||
// depending on the `Executor` configured.
|
||||
//
|
||||
// Users cannot import this type, nor the associated `NewSvcExec`. Instead,
|
||||
// a blanket implementation for `Executor<impl Future>` is sufficient.
|
||||
|
||||
pin_project! {
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct NewSvcTask<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> {
|
||||
#[pin]
|
||||
state: State<I, N, S, E, W>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
pub(super) enum State<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> {
|
||||
Connecting {
|
||||
#[pin]
|
||||
connecting: Connecting<I, N, E>,
|
||||
watcher: W,
|
||||
},
|
||||
Connected {
|
||||
#[pin]
|
||||
future: W::Future,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> NewSvcTask<I, N, S, E, W> {
|
||||
pub(super) fn new(connecting: Connecting<I, N, E>, watcher: W) -> Self {
|
||||
NewSvcTask {
|
||||
state: State::Connecting {
|
||||
connecting,
|
||||
watcher,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, N, S, NE, B, E, W> Future for NewSvcTask<I, N, S, E, W>
|
||||
where
|
||||
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
N: Future<Output = Result<S, NE>>,
|
||||
NE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S: HttpService<Body, ResBody = B>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: ConnStreamExec<S::Future, B>,
|
||||
W: Watcher<I, S, E>,
|
||||
{
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
// If it weren't for needing to name this type so the `Send` bounds
|
||||
// could be projected to the `Serve` executor, this could just be
|
||||
// an `async fn`, and much safer. Woe is me.
|
||||
|
||||
let mut me = self.project();
|
||||
loop {
|
||||
let next = {
|
||||
match me.state.as_mut().project() {
|
||||
StateProj::Connecting {
|
||||
connecting,
|
||||
watcher,
|
||||
} => {
|
||||
let res = ready!(connecting.poll(cx));
|
||||
let conn = match res {
|
||||
Ok(conn) => conn,
|
||||
Err(err) => {
|
||||
let err = crate::Error::new_user_make_service(err);
|
||||
debug!("connecting error: {}", err);
|
||||
return Poll::Ready(());
|
||||
}
|
||||
};
|
||||
let future = watcher.watch(conn.with_upgrades());
|
||||
State::Connected { future }
|
||||
}
|
||||
StateProj::Connected { future } => {
|
||||
return future.poll(cx).map(|res| {
|
||||
if let Err(err) = res {
|
||||
debug!("connection error: {}", err);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
me.state.set(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
mod upgrades {
|
||||
use super::*;
|
||||
|
||||
@@ -1,164 +1,10 @@
|
||||
//! HTTP Server
|
||||
//!
|
||||
//! A `Server` is created to listen on a port, parse HTTP requests, and hand
|
||||
//! them off to a `Service`.
|
||||
//! A "server" is usually created by listening on a port for new connections,
|
||||
//! parse HTTP requests, and hand them off to a `Service`.
|
||||
//!
|
||||
//! There are two levels of APIs provide for constructing HTTP servers:
|
||||
//!
|
||||
//! - The higher-level [`Server`](Server) type.
|
||||
//! - The lower-level [`conn`](conn) module.
|
||||
//!
|
||||
//! # Server
|
||||
//!
|
||||
//! The [`Server`](Server) is main way to start listening for HTTP requests.
|
||||
//! It wraps a listener with a [`MakeService`](crate::service), and then should
|
||||
//! be executed to start serving requests.
|
||||
//!
|
||||
//! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default.
|
||||
//!
|
||||
//! ## Examples
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::convert::Infallible;
|
||||
//! use std::net::SocketAddr;
|
||||
//! use hyper::{Body, Request, Response, Server};
|
||||
//! use hyper::service::{make_service_fn, service_fn};
|
||||
//!
|
||||
//! async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
//! Ok(Response::new(Body::from("Hello World")))
|
||||
//! }
|
||||
//!
|
||||
//! # #[cfg(feature = "runtime")]
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! // Construct our SocketAddr to listen on...
|
||||
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
//!
|
||||
//! // And a MakeService to handle each connection...
|
||||
//! let make_service = make_service_fn(|_conn| async {
|
||||
//! Ok::<_, Infallible>(service_fn(handle))
|
||||
//! });
|
||||
//!
|
||||
//! // Then bind and serve...
|
||||
//! let server = Server::bind(&addr).serve(make_service);
|
||||
//!
|
||||
//! // And run forever...
|
||||
//! if let Err(e) = server.await {
|
||||
//! eprintln!("server error: {}", e);
|
||||
//! }
|
||||
//! }
|
||||
//! # #[cfg(not(feature = "runtime"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! If you don't need the connection and your service implements `Clone` you can use
|
||||
//! [`tower::make::Shared`] instead of `make_service_fn` which is a bit simpler:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! # use std::convert::Infallible;
|
||||
//! # use std::net::SocketAddr;
|
||||
//! # use hyper::{Body, Request, Response, Server};
|
||||
//! # use hyper::service::{make_service_fn, service_fn};
|
||||
//! # use tower::make::Shared;
|
||||
//! # async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||
//! # Ok(Response::new(Body::from("Hello World")))
|
||||
//! # }
|
||||
//! # #[cfg(feature = "runtime")]
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! // Construct our SocketAddr to listen on...
|
||||
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
//!
|
||||
//! // Shared is a MakeService that produces services by cloning an inner service...
|
||||
//! let make_service = Shared::new(service_fn(handle));
|
||||
//!
|
||||
//! // Then bind and serve...
|
||||
//! let server = Server::bind(&addr).serve(make_service);
|
||||
//!
|
||||
//! // And run forever...
|
||||
//! if let Err(e) = server.await {
|
||||
//! eprintln!("server error: {}", e);
|
||||
//! }
|
||||
//! }
|
||||
//! # #[cfg(not(feature = "runtime"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! Passing data to your request handler can be done like so:
|
||||
//!
|
||||
//! ```no_run
|
||||
//! use std::convert::Infallible;
|
||||
//! use std::net::SocketAddr;
|
||||
//! use hyper::{Body, Request, Response, Server};
|
||||
//! use hyper::service::{make_service_fn, service_fn};
|
||||
//! use hyper::server::conn::AddrStream;
|
||||
//!
|
||||
//! #[derive(Clone)]
|
||||
//! struct AppContext {
|
||||
//! // Whatever data your application needs can go here
|
||||
//! }
|
||||
//!
|
||||
//! async fn handle(
|
||||
//! context: AppContext,
|
||||
//! addr: SocketAddr,
|
||||
//! req: Request<Body>
|
||||
//! ) -> Result<Response<Body>, Infallible> {
|
||||
//! Ok(Response::new(Body::from("Hello World")))
|
||||
//! }
|
||||
//!
|
||||
//! # #[cfg(feature = "runtime")]
|
||||
//! #[tokio::main]
|
||||
//! async fn main() {
|
||||
//! let context = AppContext {
|
||||
//! // ...
|
||||
//! };
|
||||
//!
|
||||
//! // A `MakeService` that produces a `Service` to handle each connection.
|
||||
//! let make_service = make_service_fn(move |conn: &AddrStream| {
|
||||
//! // We have to clone the context to share it with each invocation of
|
||||
//! // `make_service`. If your data doesn't implement `Clone` consider using
|
||||
//! // an `std::sync::Arc`.
|
||||
//! let context = context.clone();
|
||||
//!
|
||||
//! // You can grab the address of the incoming connection like so.
|
||||
//! let addr = conn.remote_addr();
|
||||
//!
|
||||
//! // Create a `Service` for responding to the request.
|
||||
//! let service = service_fn(move |req| {
|
||||
//! handle(context.clone(), addr, req)
|
||||
//! });
|
||||
//!
|
||||
//! // Return the service to hyper.
|
||||
//! async move { Ok::<_, Infallible>(service) }
|
||||
//! });
|
||||
//!
|
||||
//! // Run the server like above...
|
||||
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||
//!
|
||||
//! let server = Server::bind(&addr).serve(make_service);
|
||||
//!
|
||||
//! if let Err(e) = server.await {
|
||||
//! eprintln!("server error: {}", e);
|
||||
//! }
|
||||
//! }
|
||||
//! # #[cfg(not(feature = "runtime"))]
|
||||
//! # fn main() {}
|
||||
//! ```
|
||||
//!
|
||||
//! [`tower::make::Shared`]: https://docs.rs/tower/latest/tower/make/struct.Shared.html
|
||||
|
||||
pub mod accept;
|
||||
//! How exactly you choose to listen for connections is not something hyper
|
||||
//! concerns itself with. After you have a connection, you can handle HTTP over
|
||||
//! it with the types in the [`conn`](conn) module.
|
||||
pub mod conn;
|
||||
mod server;
|
||||
#[cfg(feature = "tcp")]
|
||||
mod tcp;
|
||||
|
||||
pub use self::server::Server;
|
||||
|
||||
cfg_feature! {
|
||||
#![any(feature = "http1", feature = "http2")]
|
||||
|
||||
pub use self::server::Builder;
|
||||
|
||||
mod shutdown;
|
||||
}
|
||||
|
||||
@@ -1,560 +0,0 @@
|
||||
use std::fmt;
|
||||
#[cfg(feature = "tcp")]
|
||||
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
||||
#[cfg(any(feature = "tcp", feature = "http1"))]
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))]
|
||||
use super::tcp::AddrIncoming;
|
||||
use crate::common::exec::Exec;
|
||||
|
||||
cfg_feature! {
|
||||
#![any(feature = "http1", feature = "http2")]
|
||||
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::accept::Accept;
|
||||
use crate::body::{Body, HttpBody};
|
||||
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||
use crate::common::exec::{ConnStreamExec, NewSvcExec};
|
||||
// Renamed `Http` as `Http_` for now so that people upgrading don't see an
|
||||
// error that `hyper::server::Http` is private...
|
||||
use super::conn::{Http as Http_, NoopWatcher, SpawnAll};
|
||||
use super::shutdown::{Graceful, GracefulWatcher};
|
||||
use crate::service::{HttpService, MakeServiceRef};
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
pin_project! {
|
||||
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
|
||||
///
|
||||
/// `Server` is a `Future` mapping a bound listener with a set of service
|
||||
/// handlers. It is built using the [`Builder`](Builder), and the future
|
||||
/// completes when the server has been shutdown. It should be run by an
|
||||
/// `Executor`.
|
||||
pub struct Server<I, S, E = Exec> {
|
||||
#[pin]
|
||||
spawn_all: SpawnAll<I, S, E>,
|
||||
}
|
||||
}
|
||||
|
||||
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
|
||||
///
|
||||
/// Needs at least one of the `http1` and `http2` features to be activated to actually be useful.
|
||||
#[cfg(not(any(feature = "http1", feature = "http2")))]
|
||||
pub struct Server<I, S, E = Exec> {
|
||||
_marker: std::marker::PhantomData<(I, S, E)>,
|
||||
}
|
||||
|
||||
/// A builder for a [`Server`](Server).
|
||||
#[derive(Debug)]
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
pub struct Builder<I, E = Exec> {
|
||||
incoming: I,
|
||||
protocol: Http_<E>,
|
||||
}
|
||||
|
||||
// ===== impl Server =====
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
impl<I> Server<I, ()> {
|
||||
/// Starts a [`Builder`](Builder) with the provided incoming stream.
|
||||
pub fn builder(incoming: I) -> Builder<I> {
|
||||
Builder {
|
||||
incoming,
|
||||
protocol: Http_::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_feature! {
|
||||
#![all(feature = "tcp", any(feature = "http1", feature = "http2"))]
|
||||
|
||||
impl Server<AddrIncoming, ()> {
|
||||
/// Binds to the provided address, and returns a [`Builder`](Builder).
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// This method will panic if binding to the address fails. For a method
|
||||
/// to bind to an address and return a `Result`, see `Server::try_bind`.
|
||||
pub fn bind(addr: &SocketAddr) -> Builder<AddrIncoming> {
|
||||
let incoming = AddrIncoming::new(addr).unwrap_or_else(|e| {
|
||||
panic!("error binding to {}: {}", addr, e);
|
||||
});
|
||||
Server::builder(incoming)
|
||||
}
|
||||
|
||||
/// Tries to bind to the provided address, and returns a [`Builder`](Builder).
|
||||
pub fn try_bind(addr: &SocketAddr) -> crate::Result<Builder<AddrIncoming>> {
|
||||
AddrIncoming::new(addr).map(Server::builder)
|
||||
}
|
||||
|
||||
/// Create a new instance from a `std::net::TcpListener` instance.
|
||||
pub fn from_tcp(listener: StdTcpListener) -> Result<Builder<AddrIncoming>, crate::Error> {
|
||||
AddrIncoming::from_std(listener).map(Server::builder)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cfg_feature! {
|
||||
#![all(feature = "tcp", any(feature = "http1", feature = "http2"))]
|
||||
|
||||
impl<S, E> Server<AddrIncoming, S, E> {
|
||||
/// Returns the local address that this server is bound to.
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.spawn_all.local_addr()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
impl<I, IO, IE, S, E, B> Server<I, S, E>
|
||||
where
|
||||
I: Accept<Conn = IO, Error = IE>,
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
E: NewSvcExec<IO, S::Future, S::Service, E, GracefulWatcher>,
|
||||
{
|
||||
/// Prepares a server to handle graceful shutdown when the provided future
|
||||
/// completes.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # fn main() {}
|
||||
/// # #[cfg(feature = "tcp")]
|
||||
/// # async fn run() {
|
||||
/// # use hyper::{Body, Response, Server, Error};
|
||||
/// # use hyper::service::{make_service_fn, service_fn};
|
||||
/// # let make_service = make_service_fn(|_| async {
|
||||
/// # Ok::<_, Error>(service_fn(|_req| async {
|
||||
/// # Ok::<_, Error>(Response::new(Body::from("Hello World")))
|
||||
/// # }))
|
||||
/// # });
|
||||
/// // Make a server from the previous examples...
|
||||
/// let server = Server::bind(&([127, 0, 0, 1], 3000).into())
|
||||
/// .serve(make_service);
|
||||
///
|
||||
/// // Prepare some signal for when the server should start shutting down...
|
||||
/// let (tx, rx) = tokio::sync::oneshot::channel::<()>();
|
||||
/// let graceful = server
|
||||
/// .with_graceful_shutdown(async {
|
||||
/// rx.await.ok();
|
||||
/// });
|
||||
///
|
||||
/// // Await the `server` receiving the signal...
|
||||
/// if let Err(e) = graceful.await {
|
||||
/// eprintln!("server error: {}", e);
|
||||
/// }
|
||||
///
|
||||
/// // And later, trigger the signal by calling `tx.send(())`.
|
||||
/// let _ = tx.send(());
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn with_graceful_shutdown<F>(self, signal: F) -> Graceful<I, S, F, E>
|
||||
where
|
||||
F: Future<Output = ()>,
|
||||
{
|
||||
Graceful::new(self.spawn_all, signal)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
impl<I, IO, IE, S, B, E> Future for Server<I, S, E>
|
||||
where
|
||||
I: Accept<Conn = IO, Error = IE>,
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
E: NewSvcExec<IO, S::Future, S::Service, E, NoopWatcher>,
|
||||
{
|
||||
type Output = crate::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
self.project().spawn_all.poll_watch(cx, &NoopWatcher)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I: fmt::Debug, S: fmt::Debug> fmt::Debug for Server<I, S> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
let mut st = f.debug_struct("Server");
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
st.field("listener", &self.spawn_all.incoming_ref());
|
||||
st.finish()
|
||||
}
|
||||
}
|
||||
|
||||
// ===== impl Builder =====
|
||||
|
||||
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||
impl<I, E> Builder<I, E> {
|
||||
/// Start a new builder, wrapping an incoming stream and low-level options.
|
||||
///
|
||||
/// For a more convenient constructor, see [`Server::bind`](Server::bind).
|
||||
pub fn new(incoming: I, protocol: Http_<E>) -> Self {
|
||||
Builder { incoming, protocol }
|
||||
}
|
||||
|
||||
/// Sets whether to use keep-alive for HTTP/1 connections.
|
||||
///
|
||||
/// Default is `true`.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_keepalive(mut self, val: bool) -> Self {
|
||||
self.protocol.http1_keep_alive(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections should support half-closures.
|
||||
///
|
||||
/// Clients can chose to shutdown their write-side while waiting
|
||||
/// for the server to respond. Setting this to `true` will
|
||||
/// prevent closing the connection immediately if `read`
|
||||
/// detects an EOF in the middle of a request.
|
||||
///
|
||||
/// Default is `false`.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_half_close(mut self, val: bool) -> Self {
|
||||
self.protocol.http1_half_close(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum buffer size.
|
||||
///
|
||||
/// Default is ~ 400kb.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_max_buf_size(mut self, val: usize) -> Self {
|
||||
self.protocol.max_buf_size(val);
|
||||
self
|
||||
}
|
||||
|
||||
// Sets whether to bunch up HTTP/1 writes until the read buffer is empty.
|
||||
//
|
||||
// This isn't really desirable in most cases, only really being useful in
|
||||
// silly pipeline benchmarks.
|
||||
#[doc(hidden)]
|
||||
#[cfg(feature = "http1")]
|
||||
pub fn http1_pipeline_flush(mut self, val: bool) -> Self {
|
||||
self.protocol.pipeline_flush(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||
/// or always flatten into a single buffer.
|
||||
///
|
||||
/// Note that setting this to false may mean more copies of body data,
|
||||
/// but may also improve performance when an IO transport doesn't
|
||||
/// support vectored writes well, such as most TLS implementations.
|
||||
///
|
||||
/// Setting this to true will force hyper to use queued strategy
|
||||
/// which may eliminate unnecessary cloning on some TLS backends
|
||||
///
|
||||
/// Default is `auto`. In this mode hyper will try to guess which
|
||||
/// mode to use
|
||||
#[cfg(feature = "http1")]
|
||||
pub fn http1_writev(mut self, enabled: bool) -> Self {
|
||||
self.protocol.http1_writev(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether HTTP/1 connections will write header names as title case at
|
||||
/// the socket level.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_title_case_headers(mut self, val: bool) -> Self {
|
||||
self.protocol.http1_title_case_headers(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to support preserving original header cases.
|
||||
///
|
||||
/// Currently, this will record the original cases received, and store them
|
||||
/// in a private extension on the `Request`. It will also look for and use
|
||||
/// such an extension in any provided `Response`.
|
||||
///
|
||||
/// Since the relevant extension is still private, there is no way to
|
||||
/// interact with the original cases. The only effect this can have now is
|
||||
/// to forward the cases in a proxy-like fashion.
|
||||
///
|
||||
/// Note that this setting does not affect HTTP/2.
|
||||
///
|
||||
/// Default is false.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_preserve_header_case(mut self, val: bool) -> Self {
|
||||
self.protocol.http1_preserve_header_case(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set a timeout for reading client request headers. If a client does not
|
||||
/// transmit the entire header within this time, the connection is closed.
|
||||
///
|
||||
/// Default is None.
|
||||
#[cfg(all(feature = "http1", feature = "runtime"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(all(feature = "http1", feature = "runtime"))))]
|
||||
pub fn http1_header_read_timeout(mut self, read_timeout: Duration) -> Self {
|
||||
self.protocol.http1_header_read_timeout(read_timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether HTTP/1 is required.
|
||||
///
|
||||
/// Default is `false`.
|
||||
#[cfg(feature = "http1")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||
pub fn http1_only(mut self, val: bool) -> Self {
|
||||
self.protocol.http1_only(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether HTTP/2 is required.
|
||||
///
|
||||
/// Default is `false`.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_only(mut self, val: bool) -> Self {
|
||||
self.protocol.http2_only(val);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
|
||||
/// stream-level flow control.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
///
|
||||
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_initial_stream_window_size(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
self.protocol.http2_initial_stream_window_size(sz.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the max connection-level flow control for HTTP2
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_initial_connection_window_size(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
self.protocol
|
||||
.http2_initial_connection_window_size(sz.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets whether to use an adaptive flow control.
|
||||
///
|
||||
/// Enabling this will override the limits set in
|
||||
/// `http2_initial_stream_window_size` and
|
||||
/// `http2_initial_connection_window_size`.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_adaptive_window(mut self, enabled: bool) -> Self {
|
||||
self.protocol.http2_adaptive_window(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the maximum frame size to use for HTTP2.
|
||||
///
|
||||
/// Passing `None` will do nothing.
|
||||
///
|
||||
/// If not set, hyper will use a default.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||
self.protocol.http2_max_frame_size(sz);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2
|
||||
/// connections.
|
||||
///
|
||||
/// Default is no limit (`std::u32::MAX`). Passing `None` will do nothing.
|
||||
///
|
||||
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_concurrent_streams(mut self, max: impl Into<Option<u32>>) -> Self {
|
||||
self.protocol.http2_max_concurrent_streams(max.into());
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets an interval for HTTP2 Ping frames should be sent to keep a
|
||||
/// connection alive.
|
||||
///
|
||||
/// Pass `None` to disable HTTP2 keep-alive.
|
||||
///
|
||||
/// Default is currently disabled.
|
||||
///
|
||||
/// # Cargo Feature
|
||||
///
|
||||
/// Requires the `runtime` cargo feature to be enabled.
|
||||
#[cfg(all(feature = "runtime", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_keep_alive_interval(mut self, interval: impl Into<Option<Duration>>) -> Self {
|
||||
self.protocol.http2_keep_alive_interval(interval);
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
|
||||
///
|
||||
/// If the ping is not acknowledged within the timeout, the connection will
|
||||
/// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
|
||||
///
|
||||
/// Default is 20 seconds.
|
||||
///
|
||||
/// # Cargo Feature
|
||||
///
|
||||
/// Requires the `runtime` cargo feature to be enabled.
|
||||
#[cfg(all(feature = "runtime", feature = "http2"))]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> Self {
|
||||
self.protocol.http2_keep_alive_timeout(timeout);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the maximum write buffer size for each HTTP/2 stream.
|
||||
///
|
||||
/// Default is currently ~400KB, but may change.
|
||||
///
|
||||
/// # Panics
|
||||
///
|
||||
/// The value must be no larger than `u32::MAX`.
|
||||
#[cfg(feature = "http2")]
|
||||
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||
pub fn http2_max_send_buf_size(mut self, max: usize) -> Self {
|
||||
self.protocol.http2_max_send_buf_size(max);
|
||||
self
|
||||
}
|
||||
|
||||
/// Enables the [extended CONNECT protocol].
|
||||
///
|
||||
/// [extended CONNECT protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
||||
#[cfg(feature = "http2")]
|
||||
pub fn http2_enable_connect_protocol(mut self) -> Self {
|
||||
self.protocol.http2_enable_connect_protocol();
|
||||
self
|
||||
}
|
||||
|
||||
/// Sets the `Executor` to deal with connection tasks.
|
||||
///
|
||||
/// Default is `tokio::spawn`.
|
||||
pub fn executor<E2>(self, executor: E2) -> Builder<I, E2> {
|
||||
Builder {
|
||||
incoming: self.incoming,
|
||||
protocol: self.protocol.with_executor(executor),
|
||||
}
|
||||
}
|
||||
|
||||
/// Consume this `Builder`, creating a [`Server`](Server).
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "tcp")]
|
||||
/// # async fn run() {
|
||||
/// use hyper::{Body, Error, Response, Server};
|
||||
/// use hyper::service::{make_service_fn, service_fn};
|
||||
///
|
||||
/// // Construct our SocketAddr to listen on...
|
||||
/// let addr = ([127, 0, 0, 1], 3000).into();
|
||||
///
|
||||
/// // And a MakeService to handle each connection...
|
||||
/// let make_svc = make_service_fn(|_| async {
|
||||
/// Ok::<_, Error>(service_fn(|_req| async {
|
||||
/// Ok::<_, Error>(Response::new(Body::from("Hello World")))
|
||||
/// }))
|
||||
/// });
|
||||
///
|
||||
/// // Then bind and serve...
|
||||
/// let server = Server::bind(&addr)
|
||||
/// .serve(make_svc);
|
||||
///
|
||||
/// // Run forever-ish...
|
||||
/// if let Err(err) = server.await {
|
||||
/// eprintln!("server error: {}", err);
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
pub fn serve<S, B>(self, new_service: S) -> Server<I, S, E>
|
||||
where
|
||||
I: Accept,
|
||||
I::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
I::Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: MakeServiceRef<I::Conn, Body, ResBody = B>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: NewSvcExec<I::Conn, S::Future, S::Service, E, NoopWatcher>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
{
|
||||
let serve = self.protocol.serve(self.incoming, new_service);
|
||||
let spawn_all = serve.spawn_all();
|
||||
Server { spawn_all }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))]
|
||||
impl<E> Builder<AddrIncoming, E> {
|
||||
/// Set whether TCP keepalive messages are enabled on accepted connections.
|
||||
///
|
||||
/// If `None` is specified, keepalive is disabled, otherwise the duration
|
||||
/// specified will be the time to remain idle before sending TCP keepalive
|
||||
/// probes.
|
||||
pub fn tcp_keepalive(mut self, keepalive: Option<Duration>) -> Self {
|
||||
self.incoming.set_keepalive(keepalive);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the value of `TCP_NODELAY` option for accepted connections.
|
||||
pub fn tcp_nodelay(mut self, enabled: bool) -> Self {
|
||||
self.incoming.set_nodelay(enabled);
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to sleep on accept errors.
|
||||
///
|
||||
/// A possible scenario is that the process has hit the max open files
|
||||
/// allowed, and so trying to accept a new connection will fail with
|
||||
/// EMFILE. In some cases, it's preferable to just wait for some time, if
|
||||
/// the application will likely close some files (or connections), and try
|
||||
/// to accept the connection again. If this option is true, the error will
|
||||
/// be logged at the error level, since it is still a big deal, and then
|
||||
/// the listener will sleep for 1 second.
|
||||
///
|
||||
/// In other cases, hitting the max open files should be treat similarly
|
||||
/// to being out-of-memory, and simply error (and shutdown). Setting this
|
||||
/// option to false will allow that.
|
||||
///
|
||||
/// For more details see [`AddrIncoming::set_sleep_on_errors`]
|
||||
pub fn tcp_sleep_on_accept_errors(mut self, val: bool) -> Self {
|
||||
self.incoming.set_sleep_on_errors(val);
|
||||
self
|
||||
}
|
||||
}
|
||||
@@ -1,127 +0,0 @@
|
||||
use std::error::Error as StdError;
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
use tracing::debug;
|
||||
|
||||
use super::accept::Accept;
|
||||
use super::conn::{SpawnAll, UpgradeableConnection, Watcher};
|
||||
use crate::body::{Body, HttpBody};
|
||||
use crate::common::drain::{self, Draining, Signal, Watch, Watching};
|
||||
use crate::common::exec::{ConnStreamExec, NewSvcExec};
|
||||
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||
use crate::service::{HttpService, MakeServiceRef};
|
||||
|
||||
pin_project! {
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Graceful<I, S, F, E> {
|
||||
#[pin]
|
||||
state: State<I, S, F, E>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
pub(super) enum State<I, S, F, E> {
|
||||
Running {
|
||||
drain: Option<(Signal, Watch)>,
|
||||
#[pin]
|
||||
spawn_all: SpawnAll<I, S, E>,
|
||||
#[pin]
|
||||
signal: F,
|
||||
},
|
||||
Draining { draining: Draining },
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, S, F, E> Graceful<I, S, F, E> {
|
||||
pub(super) fn new(spawn_all: SpawnAll<I, S, E>, signal: F) -> Self {
|
||||
let drain = Some(drain::channel());
|
||||
Graceful {
|
||||
state: State::Running {
|
||||
drain,
|
||||
spawn_all,
|
||||
signal,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<I, IO, IE, S, B, F, E> Future for Graceful<I, S, F, E>
|
||||
where
|
||||
I: Accept<Conn = IO, Error = IE>,
|
||||
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
B: HttpBody + 'static,
|
||||
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
F: Future<Output = ()>,
|
||||
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||
E: NewSvcExec<IO, S::Future, S::Service, E, GracefulWatcher>,
|
||||
{
|
||||
type Output = crate::Result<()>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut me = self.project();
|
||||
loop {
|
||||
let next = {
|
||||
match me.state.as_mut().project() {
|
||||
StateProj::Running {
|
||||
drain,
|
||||
spawn_all,
|
||||
signal,
|
||||
} => match signal.poll(cx) {
|
||||
Poll::Ready(()) => {
|
||||
debug!("signal received, starting graceful shutdown");
|
||||
let sig = drain.take().expect("drain channel").0;
|
||||
State::Draining {
|
||||
draining: sig.drain(),
|
||||
}
|
||||
}
|
||||
Poll::Pending => {
|
||||
let watch = drain.as_ref().expect("drain channel").1.clone();
|
||||
return spawn_all.poll_watch(cx, &GracefulWatcher(watch));
|
||||
}
|
||||
},
|
||||
StateProj::Draining { ref mut draining } => {
|
||||
return Pin::new(draining).poll(cx).map(Ok);
|
||||
}
|
||||
}
|
||||
};
|
||||
me.state.set(next);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
#[derive(Clone)]
|
||||
pub struct GracefulWatcher(Watch);
|
||||
|
||||
impl<I, S, E> Watcher<I, S, E> for GracefulWatcher
|
||||
where
|
||||
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||
S: HttpService<Body>,
|
||||
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||
S::ResBody: 'static,
|
||||
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
type Future =
|
||||
Watching<UpgradeableConnection<I, S, E>, fn(Pin<&mut UpgradeableConnection<I, S, E>>)>;
|
||||
|
||||
fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future {
|
||||
self.0.clone().watch(conn, on_drain)
|
||||
}
|
||||
}
|
||||
|
||||
fn on_drain<I, S, E>(conn: Pin<&mut UpgradeableConnection<I, S, E>>)
|
||||
where
|
||||
S: HttpService<Body>,
|
||||
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
I: AsyncRead + AsyncWrite + Unpin,
|
||||
S::ResBody: HttpBody + 'static,
|
||||
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||
{
|
||||
conn.graceful_shutdown()
|
||||
}
|
||||
@@ -1,302 +0,0 @@
|
||||
use std::fmt;
|
||||
use std::io;
|
||||
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
||||
use std::time::Duration;
|
||||
|
||||
use tokio::net::TcpListener;
|
||||
use tokio::time::Sleep;
|
||||
use tracing::{debug, error, trace};
|
||||
|
||||
use crate::common::{task, Future, Pin, Poll};
|
||||
|
||||
#[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411
|
||||
pub use self::addr_stream::AddrStream;
|
||||
use super::accept::Accept;
|
||||
|
||||
/// A stream of connections from binding to an address.
|
||||
#[must_use = "streams do nothing unless polled"]
|
||||
pub struct AddrIncoming {
|
||||
addr: SocketAddr,
|
||||
listener: TcpListener,
|
||||
sleep_on_errors: bool,
|
||||
tcp_keepalive_timeout: Option<Duration>,
|
||||
tcp_nodelay: bool,
|
||||
timeout: Option<Pin<Box<Sleep>>>,
|
||||
}
|
||||
|
||||
impl AddrIncoming {
|
||||
pub(super) fn new(addr: &SocketAddr) -> crate::Result<Self> {
|
||||
let std_listener = StdTcpListener::bind(addr).map_err(crate::Error::new_listen)?;
|
||||
|
||||
AddrIncoming::from_std(std_listener)
|
||||
}
|
||||
|
||||
pub(super) fn from_std(std_listener: StdTcpListener) -> crate::Result<Self> {
|
||||
// TcpListener::from_std doesn't set O_NONBLOCK
|
||||
std_listener
|
||||
.set_nonblocking(true)
|
||||
.map_err(crate::Error::new_listen)?;
|
||||
let listener = TcpListener::from_std(std_listener).map_err(crate::Error::new_listen)?;
|
||||
AddrIncoming::from_listener(listener)
|
||||
}
|
||||
|
||||
/// Creates a new `AddrIncoming` binding to provided socket address.
|
||||
pub fn bind(addr: &SocketAddr) -> crate::Result<Self> {
|
||||
AddrIncoming::new(addr)
|
||||
}
|
||||
|
||||
/// Creates a new `AddrIncoming` from an existing `tokio::net::TcpListener`.
|
||||
pub fn from_listener(listener: TcpListener) -> crate::Result<Self> {
|
||||
let addr = listener.local_addr().map_err(crate::Error::new_listen)?;
|
||||
Ok(AddrIncoming {
|
||||
listener,
|
||||
addr,
|
||||
sleep_on_errors: true,
|
||||
tcp_keepalive_timeout: None,
|
||||
tcp_nodelay: false,
|
||||
timeout: None,
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the local address bound to this listener.
|
||||
pub fn local_addr(&self) -> SocketAddr {
|
||||
self.addr
|
||||
}
|
||||
|
||||
/// Set whether TCP keepalive messages are enabled on accepted connections.
|
||||
///
|
||||
/// If `None` is specified, keepalive is disabled, otherwise the duration
|
||||
/// specified will be the time to remain idle before sending TCP keepalive
|
||||
/// probes.
|
||||
pub fn set_keepalive(&mut self, keepalive: Option<Duration>) -> &mut Self {
|
||||
self.tcp_keepalive_timeout = keepalive;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set the value of `TCP_NODELAY` option for accepted connections.
|
||||
pub fn set_nodelay(&mut self, enabled: bool) -> &mut Self {
|
||||
self.tcp_nodelay = enabled;
|
||||
self
|
||||
}
|
||||
|
||||
/// Set whether to sleep on accept errors.
|
||||
///
|
||||
/// A possible scenario is that the process has hit the max open files
|
||||
/// allowed, and so trying to accept a new connection will fail with
|
||||
/// `EMFILE`. In some cases, it's preferable to just wait for some time, if
|
||||
/// the application will likely close some files (or connections), and try
|
||||
/// to accept the connection again. If this option is `true`, the error
|
||||
/// will be logged at the `error` level, since it is still a big deal,
|
||||
/// and then the listener will sleep for 1 second.
|
||||
///
|
||||
/// In other cases, hitting the max open files should be treat similarly
|
||||
/// to being out-of-memory, and simply error (and shutdown). Setting
|
||||
/// this option to `false` will allow that.
|
||||
///
|
||||
/// Default is `true`.
|
||||
pub fn set_sleep_on_errors(&mut self, val: bool) {
|
||||
self.sleep_on_errors = val;
|
||||
}
|
||||
|
||||
fn poll_next_(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<AddrStream>> {
|
||||
// Check if a previous timeout is active that was set by IO errors.
|
||||
if let Some(ref mut to) = self.timeout {
|
||||
ready!(Pin::new(to).poll(cx));
|
||||
}
|
||||
self.timeout = None;
|
||||
|
||||
loop {
|
||||
match ready!(self.listener.poll_accept(cx)) {
|
||||
Ok((socket, addr)) => {
|
||||
if let Some(dur) = self.tcp_keepalive_timeout {
|
||||
let socket = socket2::SockRef::from(&socket);
|
||||
let conf = socket2::TcpKeepalive::new().with_time(dur);
|
||||
if let Err(e) = socket.set_tcp_keepalive(&conf) {
|
||||
trace!("error trying to set TCP keepalive: {}", e);
|
||||
}
|
||||
}
|
||||
if let Err(e) = socket.set_nodelay(self.tcp_nodelay) {
|
||||
trace!("error trying to set TCP nodelay: {}", e);
|
||||
}
|
||||
return Poll::Ready(Ok(AddrStream::new(socket, addr)));
|
||||
}
|
||||
Err(e) => {
|
||||
// Connection errors can be ignored directly, continue by
|
||||
// accepting the next request.
|
||||
if is_connection_error(&e) {
|
||||
debug!("accepted connection already errored: {}", e);
|
||||
continue;
|
||||
}
|
||||
|
||||
if self.sleep_on_errors {
|
||||
error!("accept error: {}", e);
|
||||
|
||||
// Sleep 1s.
|
||||
let mut timeout = Box::pin(tokio::time::sleep(Duration::from_secs(1)));
|
||||
|
||||
match timeout.as_mut().poll(cx) {
|
||||
Poll::Ready(()) => {
|
||||
// Wow, it's been a second already? Ok then...
|
||||
continue;
|
||||
}
|
||||
Poll::Pending => {
|
||||
self.timeout = Some(timeout);
|
||||
return Poll::Pending;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
return Poll::Ready(Err(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Accept for AddrIncoming {
|
||||
type Conn = AddrStream;
|
||||
type Error = io::Error;
|
||||
|
||||
fn poll_accept(
|
||||
mut self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||
let result = ready!(self.poll_next_(cx));
|
||||
Poll::Ready(Some(result))
|
||||
}
|
||||
}
|
||||
|
||||
/// This function defines errors that are per-connection. Which basically
|
||||
/// means that if we get this error from `accept()` system call it means
|
||||
/// next connection might be ready to be accepted.
|
||||
///
|
||||
/// All other errors will incur a timeout before next `accept()` is performed.
|
||||
/// The timeout is useful to handle resource exhaustion errors like ENFILE
|
||||
/// and EMFILE. Otherwise, could enter into tight loop.
|
||||
fn is_connection_error(e: &io::Error) -> bool {
|
||||
matches!(e.kind(), io::ErrorKind::ConnectionRefused
|
||||
| io::ErrorKind::ConnectionAborted
|
||||
| io::ErrorKind::ConnectionReset)
|
||||
}
|
||||
|
||||
impl fmt::Debug for AddrIncoming {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("AddrIncoming")
|
||||
.field("addr", &self.addr)
|
||||
.field("sleep_on_errors", &self.sleep_on_errors)
|
||||
.field("tcp_keepalive_timeout", &self.tcp_keepalive_timeout)
|
||||
.field("tcp_nodelay", &self.tcp_nodelay)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
mod addr_stream {
|
||||
use std::io;
|
||||
use std::net::SocketAddr;
|
||||
#[cfg(unix)]
|
||||
use std::os::unix::io::{AsRawFd, RawFd};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::net::TcpStream;
|
||||
|
||||
use crate::common::{task, Pin, Poll};
|
||||
|
||||
pin_project_lite::pin_project! {
|
||||
/// A transport returned yieled by `AddrIncoming`.
|
||||
#[derive(Debug)]
|
||||
pub struct AddrStream {
|
||||
#[pin]
|
||||
inner: TcpStream,
|
||||
pub(super) remote_addr: SocketAddr,
|
||||
}
|
||||
}
|
||||
|
||||
impl AddrStream {
|
||||
pub(super) fn new(tcp: TcpStream, addr: SocketAddr) -> AddrStream {
|
||||
AddrStream {
|
||||
inner: tcp,
|
||||
remote_addr: addr,
|
||||
}
|
||||
}
|
||||
|
||||
/// Returns the remote (peer) address of this connection.
|
||||
#[inline]
|
||||
pub fn remote_addr(&self) -> SocketAddr {
|
||||
self.remote_addr
|
||||
}
|
||||
|
||||
/// Consumes the AddrStream and returns the underlying IO object
|
||||
#[inline]
|
||||
pub fn into_inner(self) -> TcpStream {
|
||||
self.inner
|
||||
}
|
||||
|
||||
/// Attempt to receive data on the socket, without removing that data
|
||||
/// from the queue, registering the current task for wakeup if data is
|
||||
/// not yet available.
|
||||
pub fn poll_peek(
|
||||
&mut self,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut tokio::io::ReadBuf<'_>,
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.inner.poll_peek(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncRead for AddrStream {
|
||||
#[inline]
|
||||
fn poll_read(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &mut ReadBuf<'_>,
|
||||
) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_read(cx, buf)
|
||||
}
|
||||
}
|
||||
|
||||
impl AsyncWrite for AddrStream {
|
||||
#[inline]
|
||||
fn poll_write(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
buf: &[u8],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.project().inner.poll_write(cx, buf)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_write_vectored(
|
||||
self: Pin<&mut Self>,
|
||||
cx: &mut task::Context<'_>,
|
||||
bufs: &[io::IoSlice<'_>],
|
||||
) -> Poll<io::Result<usize>> {
|
||||
self.project().inner.poll_write_vectored(cx, bufs)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||
// TCP flush is a noop
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||
self.project().inner.poll_shutdown(cx)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
fn is_write_vectored(&self) -> bool {
|
||||
// Note that since `self.inner` is a `TcpStream`, this could
|
||||
// *probably* be hard-coded to return `true`...but it seems more
|
||||
// correct to ask it anyway (maybe we're on some platform without
|
||||
// scatter-gather IO?)
|
||||
self.inner.is_write_vectored()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(unix)]
|
||||
impl AsRawFd for AddrStream {
|
||||
fn as_raw_fd(&self) -> RawFd {
|
||||
self.inner.as_raw_fd()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,187 +0,0 @@
|
||||
use std::error::Error as StdError;
|
||||
use std::fmt;
|
||||
|
||||
use tokio::io::{AsyncRead, AsyncWrite};
|
||||
|
||||
use super::{HttpService, Service};
|
||||
use crate::body::HttpBody;
|
||||
use crate::common::{task, Future, Poll};
|
||||
|
||||
// The same "trait alias" as tower::MakeConnection, but inlined to reduce
|
||||
// dependencies.
|
||||
pub trait MakeConnection<Target>: self::sealed::Sealed<(Target,)> {
|
||||
type Connection: AsyncRead + AsyncWrite;
|
||||
type Error;
|
||||
type Future: Future<Output = Result<Self::Connection, Self::Error>>;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||
fn make_connection(&mut self, target: Target) -> Self::Future;
|
||||
}
|
||||
|
||||
impl<S, Target> self::sealed::Sealed<(Target,)> for S where S: Service<Target> {}
|
||||
|
||||
impl<S, Target> MakeConnection<Target> for S
|
||||
where
|
||||
S: Service<Target>,
|
||||
S::Response: AsyncRead + AsyncWrite,
|
||||
{
|
||||
type Connection = S::Response;
|
||||
type Error = S::Error;
|
||||
type Future = S::Future;
|
||||
|
||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Service::poll_ready(self, cx)
|
||||
}
|
||||
|
||||
fn make_connection(&mut self, target: Target) -> Self::Future {
|
||||
Service::call(self, target)
|
||||
}
|
||||
}
|
||||
|
||||
// Just a sort-of "trait alias" of `MakeService`, not to be implemented
|
||||
// by anyone, only used as bounds.
|
||||
pub trait MakeServiceRef<Target, ReqBody>: self::sealed::Sealed<(Target, ReqBody)> {
|
||||
type ResBody: HttpBody;
|
||||
type Error: Into<Box<dyn StdError + Send + Sync>>;
|
||||
type Service: HttpService<ReqBody, ResBody = Self::ResBody, Error = Self::Error>;
|
||||
type MakeError: Into<Box<dyn StdError + Send + Sync>>;
|
||||
type Future: Future<Output = Result<Self::Service, Self::MakeError>>;
|
||||
|
||||
// Acting like a #[non_exhaustive] for associated types of this trait.
|
||||
//
|
||||
// Basically, no one outside of hyper should be able to set this type
|
||||
// or declare bounds on it, so it should prevent people from creating
|
||||
// trait objects or otherwise writing code that requires using *all*
|
||||
// of the associated types.
|
||||
//
|
||||
// Why? So we can add new associated types to this alias in the future,
|
||||
// if necessary.
|
||||
type __DontNameMe: self::sealed::CantImpl;
|
||||
|
||||
fn poll_ready_ref(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::MakeError>>;
|
||||
|
||||
fn make_service_ref(&mut self, target: &Target) -> Self::Future;
|
||||
}
|
||||
|
||||
impl<T, Target, E, ME, S, F, IB, OB> MakeServiceRef<Target, IB> for T
|
||||
where
|
||||
T: for<'a> Service<&'a Target, Error = ME, Response = S, Future = F>,
|
||||
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||
ME: Into<Box<dyn StdError + Send + Sync>>,
|
||||
S: HttpService<IB, ResBody = OB, Error = E>,
|
||||
F: Future<Output = Result<S, ME>>,
|
||||
IB: HttpBody,
|
||||
OB: HttpBody,
|
||||
{
|
||||
type Error = E;
|
||||
type Service = S;
|
||||
type ResBody = OB;
|
||||
type MakeError = ME;
|
||||
type Future = F;
|
||||
|
||||
type __DontNameMe = self::sealed::CantName;
|
||||
|
||||
fn poll_ready_ref(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::MakeError>> {
|
||||
self.poll_ready(cx)
|
||||
}
|
||||
|
||||
fn make_service_ref(&mut self, target: &Target) -> Self::Future {
|
||||
self.call(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T, Target, S, B1, B2> self::sealed::Sealed<(Target, B1)> for T
|
||||
where
|
||||
T: for<'a> Service<&'a Target, Response = S>,
|
||||
S: HttpService<B1, ResBody = B2>,
|
||||
B1: HttpBody,
|
||||
B2: HttpBody,
|
||||
{
|
||||
}
|
||||
|
||||
/// Create a `MakeService` from a function.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```
|
||||
/// # #[cfg(feature = "runtime")]
|
||||
/// # async fn run() {
|
||||
/// use std::convert::Infallible;
|
||||
/// use hyper::{Body, Request, Response, Server};
|
||||
/// use hyper::server::conn::AddrStream;
|
||||
/// use hyper::service::{make_service_fn, service_fn};
|
||||
///
|
||||
/// let addr = ([127, 0, 0, 1], 3000).into();
|
||||
///
|
||||
/// let make_svc = make_service_fn(|socket: &AddrStream| {
|
||||
/// let remote_addr = socket.remote_addr();
|
||||
/// async move {
|
||||
/// Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
|
||||
/// Ok::<_, Infallible>(
|
||||
/// Response::new(Body::from(format!("Hello, {}!", remote_addr)))
|
||||
/// )
|
||||
/// }))
|
||||
/// }
|
||||
/// });
|
||||
///
|
||||
/// // Then bind and serve...
|
||||
/// let server = Server::bind(&addr)
|
||||
/// .serve(make_svc);
|
||||
///
|
||||
/// // Finally, spawn `server` onto an Executor...
|
||||
/// if let Err(e) = server.await {
|
||||
/// eprintln!("server error: {}", e);
|
||||
/// }
|
||||
/// # }
|
||||
/// # fn main() {}
|
||||
/// ```
|
||||
pub fn make_service_fn<F, Target, Ret>(f: F) -> MakeServiceFn<F>
|
||||
where
|
||||
F: FnMut(&Target) -> Ret,
|
||||
Ret: Future,
|
||||
{
|
||||
MakeServiceFn { f }
|
||||
}
|
||||
|
||||
/// `MakeService` returned from [`make_service_fn`]
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct MakeServiceFn<F> {
|
||||
f: F,
|
||||
}
|
||||
|
||||
impl<'t, F, Ret, Target, Svc, MkErr> Service<&'t Target> for MakeServiceFn<F>
|
||||
where
|
||||
F: FnMut(&Target) -> Ret,
|
||||
Ret: Future<Output = Result<Svc, MkErr>>,
|
||||
MkErr: Into<Box<dyn StdError + Send + Sync>>,
|
||||
{
|
||||
type Error = MkErr;
|
||||
type Response = Svc;
|
||||
type Future = Ret;
|
||||
|
||||
fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||
Poll::Ready(Ok(()))
|
||||
}
|
||||
|
||||
fn call(&mut self, target: &'t Target) -> Self::Future {
|
||||
(self.f)(target)
|
||||
}
|
||||
}
|
||||
|
||||
impl<F> fmt::Debug for MakeServiceFn<F> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
f.debug_struct("MakeServiceFn").finish()
|
||||
}
|
||||
}
|
||||
|
||||
mod sealed {
|
||||
pub trait Sealed<X> {}
|
||||
|
||||
#[allow(unreachable_pub)] // This is intentional.
|
||||
pub trait CantImpl {}
|
||||
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub enum CantName {}
|
||||
|
||||
impl CantImpl for CantName {}
|
||||
}
|
||||
@@ -10,10 +10,6 @@
|
||||
//!
|
||||
//! - `HttpService`: This is blanketly implemented for all types that
|
||||
//! implement `Service<http::Request<B1>, Response = http::Response<B2>>`.
|
||||
//! - `MakeService`: When a `Service` returns a new `Service` as its "response",
|
||||
//! we consider it a `MakeService`. Again, blanketly implemented in those cases.
|
||||
//! - `MakeConnection`: A `Service` that returns a "connection", a type that
|
||||
//! implements `AsyncRead` and `AsyncWrite`.
|
||||
//!
|
||||
//! # HttpService
|
||||
//!
|
||||
@@ -24,32 +20,13 @@
|
||||
//! The helper [`service_fn`](service_fn) should be sufficient for most cases, but
|
||||
//! if you need to implement `Service` for a type manually, you can follow the example
|
||||
//! in `service_struct_impl.rs`.
|
||||
//!
|
||||
//! # MakeService
|
||||
//!
|
||||
//! Since a `Service` is bound to a single connection, a [`Server`](crate::Server)
|
||||
//! needs a way to make them as it accepts connections. This is what a
|
||||
//! `MakeService` does.
|
||||
//!
|
||||
//! Resources that need to be shared by all `Service`s can be put into a
|
||||
//! `MakeService`, and then passed to individual `Service`s when `call`
|
||||
//! is called.
|
||||
|
||||
pub use tower_service::Service;
|
||||
|
||||
mod http;
|
||||
mod make;
|
||||
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))]
|
||||
mod oneshot;
|
||||
mod util;
|
||||
|
||||
pub(super) use self::http::HttpService;
|
||||
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))]
|
||||
pub(super) use self::make::MakeConnection;
|
||||
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "server"))]
|
||||
pub(super) use self::make::MakeServiceRef;
|
||||
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))]
|
||||
pub(super) use self::oneshot::{oneshot, Oneshot};
|
||||
pub(super) use self::http::HttpService;
|
||||
|
||||
pub use self::make::make_service_fn;
|
||||
pub use self::util::service_fn;
|
||||
|
||||
@@ -1,73 +0,0 @@
|
||||
// TODO: Eventually to be replaced with tower_util::Oneshot.
|
||||
|
||||
use pin_project_lite::pin_project;
|
||||
use tower_service::Service;
|
||||
|
||||
use crate::common::{task, Future, Pin, Poll};
|
||||
|
||||
pub(crate) fn oneshot<S, Req>(svc: S, req: Req) -> Oneshot<S, Req>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
Oneshot {
|
||||
state: State::NotReady { svc, req },
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
// A `Future` consuming a `Service` and request, waiting until the `Service`
|
||||
// is ready, and then calling `Service::call` with the request, and
|
||||
// waiting for that `Future`.
|
||||
#[allow(missing_debug_implementations)]
|
||||
pub struct Oneshot<S: Service<Req>, Req> {
|
||||
#[pin]
|
||||
state: State<S, Req>,
|
||||
}
|
||||
}
|
||||
|
||||
pin_project! {
|
||||
#[project = StateProj]
|
||||
#[project_replace = StateProjOwn]
|
||||
enum State<S: Service<Req>, Req> {
|
||||
NotReady {
|
||||
svc: S,
|
||||
req: Req,
|
||||
},
|
||||
Called {
|
||||
#[pin]
|
||||
fut: S::Future,
|
||||
},
|
||||
Tmp,
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, Req> Future for Oneshot<S, Req>
|
||||
where
|
||||
S: Service<Req>,
|
||||
{
|
||||
type Output = Result<S::Response, S::Error>;
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut me = self.project();
|
||||
|
||||
loop {
|
||||
match me.state.as_mut().project() {
|
||||
StateProj::NotReady { ref mut svc, .. } => {
|
||||
ready!(svc.poll_ready(cx))?;
|
||||
// fallthrough out of the match's borrow
|
||||
}
|
||||
StateProj::Called { fut } => {
|
||||
return fut.poll(cx);
|
||||
}
|
||||
StateProj::Tmp => unreachable!(),
|
||||
}
|
||||
|
||||
match me.state.as_mut().project_replace(State::Tmp) {
|
||||
StateProjOwn::NotReady { mut svc, req } => {
|
||||
me.state.set(State::Called { fut: svc.call(req) });
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
1380
tests/client.rs
1380
tests/client.rs
File diff suppressed because it is too large
Load Diff
340
tests/server.rs
340
tests/server.rs
@@ -1,6 +1,7 @@
|
||||
#![deny(warnings)]
|
||||
#![deny(rust_2018_idioms)]
|
||||
|
||||
use std::convert::TryInto;
|
||||
use std::future::Future;
|
||||
use std::io::{self, Read, Write};
|
||||
use std::net::TcpListener as StdTcpListener;
|
||||
@@ -16,20 +17,18 @@ use std::time::Duration;
|
||||
use bytes::Bytes;
|
||||
use futures_channel::oneshot;
|
||||
use futures_util::future::{self, Either, FutureExt, TryFutureExt};
|
||||
#[cfg(feature = "stream")]
|
||||
use futures_util::stream::StreamExt as _;
|
||||
use h2::client::SendRequest;
|
||||
use h2::{RecvStream, SendStream};
|
||||
use http::header::{HeaderName, HeaderValue};
|
||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
||||
use tokio::net::{TcpListener, TcpStream as TkTcpStream};
|
||||
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
||||
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use tokio::net::{TcpListener as TkTcpListener, TcpListener, TcpStream as TkTcpStream};
|
||||
|
||||
use hyper::body::HttpBody as _;
|
||||
use hyper::client::Client;
|
||||
use hyper::body::HttpBody;
|
||||
use hyper::server::conn::Http;
|
||||
use hyper::server::Server;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Request, Response, StatusCode, Version};
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Method, Request, Response, StatusCode, Uri, Version};
|
||||
|
||||
mod support;
|
||||
|
||||
@@ -109,8 +108,7 @@ mod response_body_lengths {
|
||||
b
|
||||
}
|
||||
Bd::Unknown(b) => {
|
||||
let (mut tx, body) = hyper::Body::channel();
|
||||
tx.try_send_data(b.into()).expect("try_send_data");
|
||||
let body = futures_util::stream::once(async move { Ok(b.into()) });
|
||||
reply.body_stream(body);
|
||||
b
|
||||
}
|
||||
@@ -321,15 +319,11 @@ mod response_body_lengths {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http2_auto_response_with_known_length() {
|
||||
use http_body::Body;
|
||||
|
||||
let server = serve();
|
||||
let addr_str = format!("http://{}", server.addr());
|
||||
server.reply().body("Hello, World!");
|
||||
|
||||
let client = Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>();
|
||||
let client = TestClient::new().http2_only();
|
||||
let uri = addr_str
|
||||
.parse::<hyper::Uri>()
|
||||
.expect("server addr should parse");
|
||||
@@ -341,8 +335,6 @@ mod response_body_lengths {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http2_auto_response_with_conflicting_lengths() {
|
||||
use http_body::Body;
|
||||
|
||||
let server = serve();
|
||||
let addr_str = format!("http://{}", server.addr());
|
||||
server
|
||||
@@ -350,9 +342,7 @@ mod response_body_lengths {
|
||||
.header("content-length", "10")
|
||||
.body("Hello, World!");
|
||||
|
||||
let client = Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>();
|
||||
let client = TestClient::new().http2_only();
|
||||
let uri = addr_str
|
||||
.parse::<hyper::Uri>()
|
||||
.expect("server addr should parse");
|
||||
@@ -364,15 +354,11 @@ mod response_body_lengths {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http2_implicit_empty_size_hint() {
|
||||
use http_body::Body;
|
||||
|
||||
let server = serve();
|
||||
let addr_str = format!("http://{}", server.addr());
|
||||
server.reply();
|
||||
|
||||
let client = Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>();
|
||||
let client = TestClient::new().http2_only();
|
||||
let uri = addr_str
|
||||
.parse::<hyper::Uri>()
|
||||
.expect("server addr should parse");
|
||||
@@ -383,6 +369,33 @@ mod response_body_lengths {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_response_custom_reason_phrase() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let server = serve();
|
||||
server.reply().reason_phrase("Cool");
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(
|
||||
b"\
|
||||
GET / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
|
||||
let mut lines = response.lines();
|
||||
assert_eq!(lines.next(), Some("HTTP/1.1 200 Cool"));
|
||||
|
||||
let mut lines = lines.skip_while(|line| !line.is_empty());
|
||||
assert_eq!(lines.next(), Some(""));
|
||||
assert_eq!(lines.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_chunked_response_with_ka() {
|
||||
let foo_bar = b"foo bar baz";
|
||||
@@ -1454,8 +1467,6 @@ async fn header_read_timeout_slow_writes_multiple_requests() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn upgrades() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -1513,8 +1524,6 @@ async fn upgrades() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -1649,15 +1658,19 @@ async fn upgrades_ignored() {
|
||||
future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty()))
|
||||
});
|
||||
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
Http::new()
|
||||
.serve_connection(socket, svc)
|
||||
.with_upgrades()
|
||||
.await
|
||||
.expect("server task");
|
||||
loop {
|
||||
let (socket, _) = listener.accept().await.unwrap();
|
||||
tokio::task::spawn(async move {
|
||||
Http::new()
|
||||
.serve_connection(socket, svc)
|
||||
.with_upgrades()
|
||||
.await
|
||||
.expect("server task");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let client = hyper::Client::new();
|
||||
let client = TestClient::new();
|
||||
let url = format!("http://{}/", addr);
|
||||
|
||||
let make_req = || {
|
||||
@@ -1679,8 +1692,6 @@ async fn upgrades_ignored() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn http_connect_new() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -1745,8 +1756,6 @@ async fn http_connect_new() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn h2_connect() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -1816,7 +1825,7 @@ async fn h2_connect() {
|
||||
#[tokio::test]
|
||||
async fn h2_connect_multiplex() {
|
||||
use futures_util::stream::FuturesUnordered;
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
use futures_util::StreamExt;
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
@@ -1927,8 +1936,6 @@ async fn h2_connect_multiplex() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn h2_connect_large_body() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -2004,8 +2011,6 @@ async fn h2_connect_large_body() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn h2_connect_empty_frames() {
|
||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||
let addr = listener.local_addr().unwrap();
|
||||
@@ -2164,17 +2169,17 @@ async fn max_buf_size() {
|
||||
.expect_err("should TooLarge error");
|
||||
}
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
#[test]
|
||||
fn streaming_body() {
|
||||
use futures_util::StreamExt;
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
// disable keep-alive so we can use read_to_end
|
||||
let server = serve_opts().keep_alive(false).serve();
|
||||
|
||||
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _;
|
||||
let b = futures_util::stream::iter(S.iter()).map(|&s| Ok::<_, hyper::Error>(s));
|
||||
let b = hyper::Body::wrap_stream(b);
|
||||
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 100] as _;
|
||||
let b =
|
||||
futures_util::stream::iter(S.iter()).map(|&s| Ok::<_, BoxError>(Bytes::copy_from_slice(s)));
|
||||
server.reply().body_stream(b);
|
||||
|
||||
let mut tcp = connect(server.addr());
|
||||
@@ -2198,8 +2203,8 @@ fn http1_response_with_http2_version() {
|
||||
|
||||
server.reply().version(hyper::Version::HTTP_2);
|
||||
|
||||
let client = TestClient::new();
|
||||
rt.block_on({
|
||||
let client = Client::new();
|
||||
let uri = addr_str.parse().expect("server addr should parse");
|
||||
client.get(uri)
|
||||
})
|
||||
@@ -2213,10 +2218,8 @@ fn try_h2() {
|
||||
|
||||
let rt = support::runtime();
|
||||
|
||||
let client = TestClient::new().http2_only();
|
||||
rt.block_on({
|
||||
let client = Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>();
|
||||
let uri = addr_str.parse().expect("server addr should parse");
|
||||
|
||||
client.get(uri).map_ok(|_| ()).map_err(|_e| ())
|
||||
@@ -2233,10 +2236,8 @@ fn http1_only() {
|
||||
|
||||
let rt = support::runtime();
|
||||
|
||||
let client = TestClient::new().http2_only();
|
||||
rt.block_on({
|
||||
let client = Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>();
|
||||
let uri = addr_str.parse().expect("server addr should parse");
|
||||
client.get(uri)
|
||||
})
|
||||
@@ -2256,9 +2257,8 @@ async fn http2_service_error_sends_reset_reason() {
|
||||
|
||||
let uri = addr_str.parse().expect("server addr should parse");
|
||||
dbg!("start");
|
||||
let err = dbg!(Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>()
|
||||
let err = dbg!(TestClient::new()
|
||||
.http2_only()
|
||||
.get(uri)
|
||||
.await
|
||||
.expect_err("client.get"));
|
||||
@@ -2272,32 +2272,28 @@ async fn http2_service_error_sends_reset_reason() {
|
||||
assert_eq!(h2_err.reason(), Some(h2::Reason::INADEQUATE_SECURITY));
|
||||
}
|
||||
|
||||
#[cfg(feature = "stream")]
|
||||
#[test]
|
||||
fn http2_body_user_error_sends_reset_reason() {
|
||||
use std::error::Error;
|
||||
let server = serve();
|
||||
let addr_str = format!("http://{}", server.addr());
|
||||
|
||||
let b = futures_util::stream::once(future::err::<String, _>(h2::Error::from(
|
||||
let b = futures_util::stream::once(future::err::<Bytes, BoxError>(Box::new(h2::Error::from(
|
||||
h2::Reason::INADEQUATE_SECURITY,
|
||||
)));
|
||||
let b = hyper::Body::wrap_stream(b);
|
||||
|
||||
))));
|
||||
server.reply().body_stream(b);
|
||||
|
||||
let rt = support::runtime();
|
||||
|
||||
let err: hyper::Error = rt
|
||||
.block_on(async move {
|
||||
let client = Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>();
|
||||
let client = TestClient::new().http2_only();
|
||||
|
||||
let uri = addr_str.parse().expect("server addr should parse");
|
||||
|
||||
let mut res = client.get(uri).await?;
|
||||
|
||||
while let Some(chunk) = res.body_mut().next().await {
|
||||
while let Some(chunk) = res.body_mut().data().await {
|
||||
chunk?;
|
||||
}
|
||||
Ok(())
|
||||
@@ -2339,22 +2335,33 @@ async fn http2_service_poll_ready_error_sends_goaway() {
|
||||
|
||||
let _ = pretty_env_logger::try_init();
|
||||
|
||||
let server = hyper::Server::bind(&([127, 0, 0, 1], 0).into())
|
||||
.http2_only(true)
|
||||
.serve(make_service_fn(|_| async move {
|
||||
Ok::<_, BoxError>(Http2ReadyErrorSvc)
|
||||
}));
|
||||
let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let addr_str = format!("http://{}", server.local_addr());
|
||||
let addr_str = format!("http://{}", listener.local_addr().unwrap());
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
server.await.expect("server");
|
||||
loop {
|
||||
tokio::select! {
|
||||
res = listener.accept() => {
|
||||
let (stream, _) = res.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
let mut http = Http::new();
|
||||
http.http2_only(true);
|
||||
|
||||
let service = Http2ReadyErrorSvc;
|
||||
http.serve_connection(stream, service).await.unwrap();
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let uri = addr_str.parse().expect("server addr should parse");
|
||||
let err = dbg!(Client::builder()
|
||||
.http2_only(true)
|
||||
.build_http::<hyper::Body>()
|
||||
let err = dbg!(TestClient::new()
|
||||
.http2_only()
|
||||
.get(uri)
|
||||
.await
|
||||
.expect_err("client.get should fail"));
|
||||
@@ -2421,6 +2428,26 @@ fn skips_content_length_and_body_for_304_responses() {
|
||||
assert_eq!(lines.next(), None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn no_implicit_zero_content_length_for_head_responses() {
|
||||
let server = serve();
|
||||
server.reply().status(hyper::StatusCode::OK).body([]);
|
||||
let mut req = connect(server.addr());
|
||||
req.write_all(
|
||||
b"\
|
||||
HEAD / HTTP/1.1\r\n\
|
||||
Host: example.domain\r\n\
|
||||
Connection: close\r\n\
|
||||
\r\n\
|
||||
",
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mut response = String::new();
|
||||
req.read_to_string(&mut response).unwrap();
|
||||
assert!(!response.contains("content-length:"));
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn http2_keep_alive_detects_unresponsive_client() {
|
||||
let _ = pretty_env_logger::try_init();
|
||||
@@ -2639,7 +2666,7 @@ impl Serve {
|
||||
}
|
||||
|
||||
type BoxError = Box<dyn std::error::Error + Send + Sync>;
|
||||
type BoxFuture = Pin<Box<dyn Future<Output = Result<Response<Body>, BoxError>> + Send>>;
|
||||
type BoxFuture = Pin<Box<dyn Future<Output = Result<Response<ReplyBody>, BoxError>> + Send>>;
|
||||
|
||||
struct ReplyBuilder<'a> {
|
||||
tx: &'a Mutex<spmc::Sender<Reply>>,
|
||||
@@ -2651,6 +2678,17 @@ impl<'a> ReplyBuilder<'a> {
|
||||
self
|
||||
}
|
||||
|
||||
fn reason_phrase(self, reason: &str) -> Self {
|
||||
self.tx
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(Reply::ReasonPhrase(
|
||||
reason.as_bytes().try_into().expect("reason phrase"),
|
||||
))
|
||||
.unwrap();
|
||||
self
|
||||
}
|
||||
|
||||
fn version(self, version: hyper::Version) -> Self {
|
||||
self.tx
|
||||
.lock()
|
||||
@@ -2672,14 +2710,16 @@ impl<'a> ReplyBuilder<'a> {
|
||||
}
|
||||
|
||||
fn body<T: AsRef<[u8]>>(self, body: T) {
|
||||
self.tx
|
||||
.lock()
|
||||
.unwrap()
|
||||
.send(Reply::Body(body.as_ref().to_vec().into()))
|
||||
.unwrap();
|
||||
let chunk = Bytes::copy_from_slice(body.as_ref());
|
||||
let body = BodyExt::boxed(http_body_util::Full::new(chunk).map_err(|e| match e {}));
|
||||
self.tx.lock().unwrap().send(Reply::Body(body)).unwrap();
|
||||
}
|
||||
|
||||
fn body_stream(self, body: Body) {
|
||||
fn body_stream<S>(self, stream: S)
|
||||
where
|
||||
S: futures_util::Stream<Item = Result<Bytes, BoxError>> + Send + Sync + 'static,
|
||||
{
|
||||
let body = BodyExt::boxed(StreamBody::new(stream));
|
||||
self.tx.lock().unwrap().send(Reply::Body(body)).unwrap();
|
||||
}
|
||||
|
||||
@@ -2721,12 +2761,15 @@ struct TestService {
|
||||
reply: spmc::Receiver<Reply>,
|
||||
}
|
||||
|
||||
type ReplyBody = BoxBody<Bytes, BoxError>;
|
||||
|
||||
#[derive(Debug)]
|
||||
enum Reply {
|
||||
Status(hyper::StatusCode),
|
||||
ReasonPhrase(hyper::ext::ReasonPhrase),
|
||||
Version(hyper::Version),
|
||||
Header(HeaderName, HeaderValue),
|
||||
Body(hyper::Body),
|
||||
Body(ReplyBody),
|
||||
Error(BoxError),
|
||||
End,
|
||||
}
|
||||
@@ -2739,7 +2782,7 @@ enum Msg {
|
||||
}
|
||||
|
||||
impl tower_service::Service<Request<Body>> for TestService {
|
||||
type Response = Response<Body>;
|
||||
type Response = Response<ReplyBody>;
|
||||
type Error = BoxError;
|
||||
type Future = BoxFuture;
|
||||
|
||||
@@ -2772,13 +2815,18 @@ impl tower_service::Service<Request<Body>> for TestService {
|
||||
}
|
||||
|
||||
impl TestService {
|
||||
fn build_reply(replies: spmc::Receiver<Reply>) -> Result<Response<Body>, BoxError> {
|
||||
let mut res = Response::new(Body::empty());
|
||||
fn build_reply(replies: spmc::Receiver<Reply>) -> Result<Response<ReplyBody>, BoxError> {
|
||||
let empty =
|
||||
BodyExt::boxed(http_body_util::Empty::new().map_err(|e| -> BoxError { match e {} }));
|
||||
let mut res = Response::new(empty);
|
||||
while let Ok(reply) = replies.try_recv() {
|
||||
match reply {
|
||||
Reply::Status(s) => {
|
||||
*res.status_mut() = s;
|
||||
}
|
||||
Reply::ReasonPhrase(reason) => {
|
||||
res.extensions_mut().insert(reason);
|
||||
}
|
||||
Reply::Version(v) => {
|
||||
*res.version_mut() = v;
|
||||
}
|
||||
@@ -2817,7 +2865,7 @@ impl tower_service::Service<Request<Body>> for HelloWorld {
|
||||
|
||||
fn unreachable_service() -> impl tower_service::Service<
|
||||
http::Request<hyper::Body>,
|
||||
Response = http::Response<hyper::Body>,
|
||||
Response = http::Response<ReplyBody>,
|
||||
Error = BoxError,
|
||||
Future = BoxFuture,
|
||||
> {
|
||||
@@ -2883,9 +2931,9 @@ impl ServeOptions {
|
||||
let (addr_tx, addr_rx) = mpsc::channel();
|
||||
let (msg_tx, msg_rx) = mpsc::channel();
|
||||
let (reply_tx, reply_rx) = spmc::channel();
|
||||
let (shutdown_tx, shutdown_rx) = oneshot::channel();
|
||||
let (shutdown_tx, mut shutdown_rx) = oneshot::channel();
|
||||
|
||||
let addr = ([127, 0, 0, 1], 0).into();
|
||||
let addr: SocketAddr = ([127, 0, 0, 1], 0).into();
|
||||
|
||||
let thread_name = format!(
|
||||
"test-server-{}",
|
||||
@@ -2896,36 +2944,46 @@ impl ServeOptions {
|
||||
let thread = thread::Builder::new()
|
||||
.name(thread_name)
|
||||
.spawn(move || {
|
||||
support::runtime()
|
||||
.block_on(async move {
|
||||
let service = make_service_fn(|_| {
|
||||
let msg_tx = msg_tx.clone();
|
||||
let reply_rx = reply_rx.clone();
|
||||
future::ok::<_, BoxError>(TestService {
|
||||
tx: msg_tx,
|
||||
reply: reply_rx,
|
||||
})
|
||||
});
|
||||
support::runtime().block_on(async move {
|
||||
let listener = TkTcpListener::bind(addr).await.unwrap();
|
||||
|
||||
let builder = Server::bind(&addr);
|
||||
addr_tx
|
||||
.send(listener.local_addr().unwrap())
|
||||
.expect("server addr tx");
|
||||
|
||||
#[cfg(feature = "http1")]
|
||||
let builder = builder
|
||||
.http1_only(_options.http1_only)
|
||||
.http1_keepalive(_options.keep_alive)
|
||||
.http1_pipeline_flush(_options.pipeline);
|
||||
loop {
|
||||
let msg_tx = msg_tx.clone();
|
||||
let reply_rx = reply_rx.clone();
|
||||
|
||||
let server = builder.serve(service);
|
||||
tokio::select! {
|
||||
res = listener.accept() => {
|
||||
let (stream, _) = res.unwrap();
|
||||
|
||||
addr_tx.send(server.local_addr()).expect("server addr tx");
|
||||
tokio::task::spawn(async move {
|
||||
let mut http = Http::new();
|
||||
|
||||
server
|
||||
.with_graceful_shutdown(async {
|
||||
let _ = shutdown_rx.await;
|
||||
})
|
||||
.await
|
||||
})
|
||||
.expect("serve()");
|
||||
#[cfg(feature = "http1")]
|
||||
let http = http
|
||||
.http1_only(_options.http1_only)
|
||||
.http1_keep_alive(_options.keep_alive)
|
||||
.pipeline_flush(_options.pipeline);
|
||||
|
||||
let msg_tx = msg_tx.clone();
|
||||
let reply_rx = reply_rx.clone();
|
||||
let service = TestService {
|
||||
tx: msg_tx,
|
||||
reply: reply_rx,
|
||||
};
|
||||
|
||||
http.serve_connection(stream, service).await.unwrap();
|
||||
});
|
||||
}
|
||||
_ = &mut shutdown_rx => {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
.expect("thread spawn");
|
||||
|
||||
@@ -3054,3 +3112,49 @@ impl Drop for Dropped {
|
||||
self.0.store(true, Ordering::SeqCst);
|
||||
}
|
||||
}
|
||||
|
||||
struct TestClient {
|
||||
http2_only: bool,
|
||||
}
|
||||
|
||||
impl TestClient {
|
||||
fn new() -> Self {
|
||||
Self { http2_only: false }
|
||||
}
|
||||
|
||||
fn http2_only(mut self) -> Self {
|
||||
self.http2_only = true;
|
||||
self
|
||||
}
|
||||
|
||||
async fn get(&self, uri: Uri) -> Result<Response<Body>, hyper::Error> {
|
||||
self.request(
|
||||
Request::builder()
|
||||
.uri(uri)
|
||||
.method(Method::GET)
|
||||
.body(Body::empty())
|
||||
.unwrap(),
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn request(&self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
||||
let host = req.uri().host().expect("uri has no host");
|
||||
let port = req.uri().port_u16().expect("uri has no port");
|
||||
|
||||
let mut builder = hyper::client::conn::Builder::new();
|
||||
builder.http2_only(self.http2_only);
|
||||
|
||||
let stream = TkTcpStream::connect(format!("{}:{}", host, port))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let (mut sender, conn) = builder.handshake(stream).await.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
conn.await.unwrap();
|
||||
});
|
||||
|
||||
sender.send_request(req).await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,9 +6,12 @@ use std::sync::{
|
||||
Arc, Mutex,
|
||||
};
|
||||
|
||||
use hyper::client::HttpConnector;
|
||||
use hyper::service::{make_service_fn, service_fn};
|
||||
use hyper::{Body, Client, Request, Response, Server, Version};
|
||||
use hyper::client::conn::Builder;
|
||||
use hyper::server::conn::Http;
|
||||
use tokio::net::{TcpListener, TcpStream};
|
||||
|
||||
use hyper::service::service_fn;
|
||||
use hyper::{Body, Request, Response, Version};
|
||||
|
||||
pub use futures_util::{
|
||||
future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _,
|
||||
@@ -326,16 +329,20 @@ async fn async_test(cfg: __TestConfig) {
|
||||
Version::HTTP_11
|
||||
};
|
||||
|
||||
let connector = HttpConnector::new();
|
||||
let client = Client::builder()
|
||||
.http2_only(cfg.client_version == 2)
|
||||
.build::<_, Body>(connector);
|
||||
let http2_only = cfg.server_version == 2;
|
||||
|
||||
let serve_handles = Arc::new(Mutex::new(cfg.server_msgs));
|
||||
|
||||
let listener = TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut addr = listener.local_addr().unwrap();
|
||||
|
||||
let expected_connections = cfg.connections;
|
||||
let mut cnt = 0;
|
||||
let new_service = make_service_fn(move |_| {
|
||||
tokio::task::spawn(async move {
|
||||
let mut cnt = 0;
|
||||
|
||||
cnt += 1;
|
||||
assert!(
|
||||
cnt <= expected_connections,
|
||||
@@ -344,98 +351,108 @@ async fn async_test(cfg: __TestConfig) {
|
||||
cnt
|
||||
);
|
||||
|
||||
// Move a clone into the service_fn
|
||||
let serve_handles = serve_handles.clone();
|
||||
future::ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| {
|
||||
let (sreq, sres) = serve_handles.lock().unwrap().remove(0);
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await.expect("server error");
|
||||
|
||||
assert_eq!(req.uri().path(), sreq.uri, "client path");
|
||||
assert_eq!(req.method(), &sreq.method, "client method");
|
||||
assert_eq!(req.version(), version, "client version");
|
||||
for func in &sreq.headers {
|
||||
func(&req.headers());
|
||||
}
|
||||
let sbody = sreq.body;
|
||||
hyper::body::to_bytes(req).map_ok(move |body| {
|
||||
assert_eq!(body.as_ref(), sbody.as_slice(), "client body");
|
||||
// Move a clone into the service_fn
|
||||
let serve_handles = serve_handles.clone();
|
||||
let service = service_fn(move |req: Request<Body>| {
|
||||
let (sreq, sres) = serve_handles.lock().unwrap().remove(0);
|
||||
|
||||
let mut res = Response::builder()
|
||||
.status(sres.status)
|
||||
.body(Body::from(sres.body))
|
||||
.expect("Response::build");
|
||||
*res.headers_mut() = sres.headers;
|
||||
res
|
||||
})
|
||||
}))
|
||||
assert_eq!(req.uri().path(), sreq.uri, "client path");
|
||||
assert_eq!(req.method(), &sreq.method, "client method");
|
||||
assert_eq!(req.version(), version, "client version");
|
||||
for func in &sreq.headers {
|
||||
func(&req.headers());
|
||||
}
|
||||
let sbody = sreq.body;
|
||||
hyper::body::to_bytes(req).map_ok(move |body| {
|
||||
assert_eq!(body.as_ref(), sbody.as_slice(), "client body");
|
||||
|
||||
let mut res = Response::builder()
|
||||
.status(sres.status)
|
||||
.body(Body::from(sres.body))
|
||||
.expect("Response::build");
|
||||
*res.headers_mut() = sres.headers;
|
||||
res
|
||||
})
|
||||
});
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
Http::new()
|
||||
.http2_only(http2_only)
|
||||
.serve_connection(stream, service)
|
||||
.await
|
||||
.expect("server error");
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
let server = hyper::Server::bind(&SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.http2_only(cfg.server_version == 2)
|
||||
.serve(new_service);
|
||||
|
||||
let mut addr = server.local_addr();
|
||||
|
||||
tokio::task::spawn(server.map(|result| {
|
||||
result.expect("server error");
|
||||
}));
|
||||
|
||||
if cfg.proxy {
|
||||
let (proxy_addr, proxy) = naive_proxy(ProxyConfig {
|
||||
connections: cfg.connections,
|
||||
dst: addr,
|
||||
version: cfg.server_version,
|
||||
});
|
||||
})
|
||||
.await;
|
||||
tokio::task::spawn(proxy);
|
||||
addr = proxy_addr;
|
||||
}
|
||||
|
||||
let make_request = Arc::new(
|
||||
move |client: &Client<HttpConnector>, creq: __CReq, cres: __CRes| {
|
||||
let uri = format!("http://{}{}", addr, creq.uri);
|
||||
let mut req = Request::builder()
|
||||
.method(creq.method)
|
||||
.uri(uri)
|
||||
//.headers(creq.headers)
|
||||
.body(creq.body.into())
|
||||
.expect("Request::build");
|
||||
*req.headers_mut() = creq.headers;
|
||||
let cstatus = cres.status;
|
||||
let cheaders = cres.headers;
|
||||
let cbody = cres.body;
|
||||
let make_request = Arc::new(move |creq: __CReq, cres: __CRes| {
|
||||
let uri = format!("http://{}{}", addr, creq.uri);
|
||||
let mut req = Request::builder()
|
||||
.method(creq.method)
|
||||
.uri(uri)
|
||||
//.headers(creq.headers)
|
||||
.body(creq.body.into())
|
||||
.expect("Request::build");
|
||||
*req.headers_mut() = creq.headers;
|
||||
let cstatus = cres.status;
|
||||
let cheaders = cres.headers;
|
||||
let cbody = cres.body;
|
||||
|
||||
client
|
||||
.request(req)
|
||||
.and_then(move |res| {
|
||||
assert_eq!(res.status(), cstatus, "server status");
|
||||
assert_eq!(res.version(), version, "server version");
|
||||
for func in &cheaders {
|
||||
func(&res.headers());
|
||||
}
|
||||
hyper::body::to_bytes(res)
|
||||
})
|
||||
.map_ok(move |body| {
|
||||
assert_eq!(body.as_ref(), cbody.as_slice(), "server body");
|
||||
})
|
||||
.map(|res| res.expect("client error"))
|
||||
},
|
||||
);
|
||||
async move {
|
||||
let stream = TcpStream::connect(addr).await.unwrap();
|
||||
|
||||
let (mut sender, conn) = hyper::client::conn::Builder::new()
|
||||
.http2_only(http2_only)
|
||||
.handshake::<TcpStream, Body>(stream)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
panic!("{:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let res = sender.send_request(req).await.unwrap();
|
||||
|
||||
assert_eq!(res.status(), cstatus, "server status");
|
||||
assert_eq!(res.version(), version, "server version");
|
||||
for func in &cheaders {
|
||||
func(&res.headers());
|
||||
}
|
||||
|
||||
let body = hyper::body::to_bytes(res).await.unwrap();
|
||||
|
||||
assert_eq!(body.as_ref(), cbody.as_slice(), "server body");
|
||||
}
|
||||
});
|
||||
|
||||
let client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = if cfg.parallel {
|
||||
let mut client_futures = vec![];
|
||||
for (creq, cres) in cfg.client_msgs {
|
||||
client_futures.push(make_request(&client, creq, cres));
|
||||
client_futures.push(make_request(creq, cres));
|
||||
}
|
||||
drop(client);
|
||||
Box::pin(future::join_all(client_futures).map(|_| ()))
|
||||
} else {
|
||||
let mut client_futures: Pin<Box<dyn Future<Output = Client<HttpConnector>> + Send>> =
|
||||
Box::pin(future::ready(client));
|
||||
let mut client_futures: Pin<Box<dyn Future<Output = ()> + Send>> =
|
||||
Box::pin(future::ready(()));
|
||||
for (creq, cres) in cfg.client_msgs {
|
||||
let mk_request = make_request.clone();
|
||||
client_futures = Box::pin(client_futures.then(move |client| {
|
||||
let fut = mk_request(&client, creq, cres);
|
||||
fut.map(move |()| client)
|
||||
}));
|
||||
client_futures = Box::pin(client_futures.then(move |_| mk_request(creq, cres)));
|
||||
}
|
||||
Box::pin(client_futures.map(|_| ()))
|
||||
};
|
||||
@@ -449,27 +466,75 @@ struct ProxyConfig {
|
||||
version: usize,
|
||||
}
|
||||
|
||||
fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) {
|
||||
let client = Client::builder()
|
||||
.http2_only(cfg.version == 2)
|
||||
.build_http::<Body>();
|
||||
|
||||
async fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) {
|
||||
let dst_addr = cfg.dst;
|
||||
let max_connections = cfg.connections;
|
||||
let counter = AtomicUsize::new(0);
|
||||
let http2_only = cfg.version == 2;
|
||||
|
||||
let srv = Server::bind(&([127, 0, 0, 1], 0).into()).serve(make_service_fn(move |_| {
|
||||
let prev = counter.fetch_add(1, Ordering::Relaxed);
|
||||
assert!(max_connections > prev, "proxy max connections");
|
||||
let client = client.clone();
|
||||
future::ok::<_, hyper::Error>(service_fn(move |mut req| {
|
||||
let uri = format!("http://{}{}", dst_addr, req.uri().path())
|
||||
.parse()
|
||||
.expect("proxy new uri parse");
|
||||
*req.uri_mut() = uri;
|
||||
client.request(req)
|
||||
}))
|
||||
}));
|
||||
let proxy_addr = srv.local_addr();
|
||||
(proxy_addr, srv.map(|res| res.expect("proxy error")))
|
||||
let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let proxy_addr = listener.local_addr().unwrap();
|
||||
|
||||
let fut = async move {
|
||||
tokio::task::spawn(async move {
|
||||
let prev = counter.fetch_add(1, Ordering::Relaxed);
|
||||
assert!(max_connections > prev, "proxy max connections");
|
||||
|
||||
loop {
|
||||
let (stream, _) = listener.accept().await.unwrap();
|
||||
|
||||
let service = service_fn(move |mut req| {
|
||||
async move {
|
||||
let uri = format!("http://{}{}", dst_addr, req.uri().path())
|
||||
.parse()
|
||||
.expect("proxy new uri parse");
|
||||
*req.uri_mut() = uri;
|
||||
|
||||
// Make the client request
|
||||
let uri = req.uri().host().expect("uri has no host");
|
||||
let port = req.uri().port_u16().expect("uri has no port");
|
||||
|
||||
let stream = TcpStream::connect(format!("{}:{}", uri, port))
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let mut builder = Builder::new();
|
||||
builder.http2_only(http2_only);
|
||||
let (mut sender, conn) = builder.handshake(stream).await.unwrap();
|
||||
|
||||
tokio::task::spawn(async move {
|
||||
if let Err(err) = conn.await {
|
||||
panic!("{:?}", err);
|
||||
}
|
||||
});
|
||||
|
||||
let resp = sender.send_request(req).await?;
|
||||
|
||||
let (mut parts, body) = resp.into_parts();
|
||||
|
||||
// Remove the Connection header for HTTP/1.1 proxy connections.
|
||||
if !http2_only {
|
||||
parts.headers.remove("Connection");
|
||||
}
|
||||
|
||||
let mut builder = Response::builder().status(parts.status);
|
||||
*builder.headers_mut().unwrap() = parts.headers;
|
||||
|
||||
Result::<Response<Body>, hyper::Error>::Ok(builder.body(body).unwrap())
|
||||
}
|
||||
});
|
||||
|
||||
Http::new()
|
||||
.http2_only(http2_only)
|
||||
.serve_connection(stream, service)
|
||||
.await
|
||||
.unwrap();
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
(proxy_addr, fut)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user