feat(client,server): remove tcp feature and code (#2929)
This removes the `tcp` feature from hyper's `Cargo.toml`, and the code it enabled: - `HttpConnector` - `GaiResolver` - `AddrStream` And parts of `Client` and `Server` that used those types. Alternatives will be available in the `hyper-util` crate. Closes #2856 Co-authored-by: MrGunflame <mrgunflame@protonmail.com>
This commit is contained in:
@@ -94,12 +94,6 @@ server = []
|
|||||||
|
|
||||||
# Tokio support
|
# Tokio support
|
||||||
runtime = [
|
runtime = [
|
||||||
"tcp",
|
|
||||||
"tokio/rt",
|
|
||||||
"tokio/time",
|
|
||||||
]
|
|
||||||
tcp = [
|
|
||||||
"socket2",
|
|
||||||
"tokio/net",
|
"tokio/net",
|
||||||
"tokio/rt",
|
"tokio/rt",
|
||||||
"tokio/time",
|
"tokio/time",
|
||||||
|
|||||||
@@ -3,35 +3,38 @@
|
|||||||
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use http::Uri;
|
// TODO: Reimplement http_connector bench using hyper::client::conn
|
||||||
use hyper::client::connect::HttpConnector;
|
// (instead of removed HttpConnector).
|
||||||
use hyper::service::Service;
|
|
||||||
use std::net::SocketAddr;
|
|
||||||
use tokio::net::TcpListener;
|
|
||||||
|
|
||||||
#[bench]
|
// use http::Uri;
|
||||||
fn http_connector(b: &mut test::Bencher) {
|
// use hyper::client::connect::HttpConnector;
|
||||||
let _ = pretty_env_logger::try_init();
|
// use hyper::service::Service;
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
// use std::net::SocketAddr;
|
||||||
.enable_all()
|
// use tokio::net::TcpListener;
|
||||||
.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();
|
|
||||||
|
|
||||||
rt.spawn(async move {
|
// #[bench]
|
||||||
loop {
|
// fn http_connector(b: &mut test::Bencher) {
|
||||||
let _ = listener.accept().await;
|
// 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.spawn(async move {
|
||||||
rt.block_on(async {
|
// loop {
|
||||||
connector.call(dst.clone()).await.expect("connect");
|
// let _ = listener.accept().await;
|
||||||
});
|
// }
|
||||||
});
|
// });
|
||||||
}
|
|
||||||
|
// b.iter(|| {
|
||||||
|
// rt.block_on(async {
|
||||||
|
// connector.call(dst.clone()).await.expect("connect");
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|||||||
@@ -3,380 +3,383 @@
|
|||||||
|
|
||||||
extern crate test;
|
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 futures_util::future::join_all;
|
||||||
use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server};
|
|
||||||
|
|
||||||
// HTTP1
|
// use hyper::client::HttpConnector;
|
||||||
|
// use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server};
|
||||||
|
|
||||||
#[bench]
|
// // HTTP1
|
||||||
fn http1_consecutive_x1_empty(b: &mut test::Bencher) {
|
|
||||||
opts().bench(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
// fn http1_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||||
opts()
|
// opts().bench(b)
|
||||||
.method(Method::POST)
|
// }
|
||||||
.request_body(&[b's'; 10])
|
|
||||||
.bench(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) {
|
// fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 100];
|
// opts()
|
||||||
opts()
|
// .method(Method::POST)
|
||||||
.method(Method::POST)
|
// .request_body(&[b's'; 10])
|
||||||
.request_body(body)
|
// .bench(b)
|
||||||
.response_body(body)
|
// }
|
||||||
.bench(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) {
|
// fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 10];
|
// let body = &[b'x'; 1024 * 100];
|
||||||
opts()
|
// opts()
|
||||||
.method(Method::POST)
|
// .method(Method::POST)
|
||||||
.request_body(body)
|
// .request_body(body)
|
||||||
.response_body(body)
|
// .response_body(body)
|
||||||
.bench(b)
|
// .bench(b)
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_parallel_x10_empty(b: &mut test::Bencher) {
|
// fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) {
|
||||||
opts().parallel(10).bench(b)
|
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
}
|
// opts()
|
||||||
|
// .method(Method::POST)
|
||||||
|
// .request_body(body)
|
||||||
|
// .response_body(body)
|
||||||
|
// .bench(b)
|
||||||
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
// fn http1_parallel_x10_empty(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 10];
|
// opts().parallel(10).bench(b)
|
||||||
opts()
|
// }
|
||||||
.parallel(10)
|
|
||||||
.method(Method::POST)
|
|
||||||
.request_body(body)
|
|
||||||
.bench(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
// fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 10];
|
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
opts()
|
// opts()
|
||||||
.parallel(10)
|
// .parallel(10)
|
||||||
.method(Method::POST)
|
// .method(Method::POST)
|
||||||
.request_chunks(body, 100)
|
// .request_body(body)
|
||||||
.bench(b)
|
// .bench(b)
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
// fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 1];
|
// let body = &[b'x'; 1024 * 10];
|
||||||
opts().parallel(10).response_body(body).bench(b)
|
// opts()
|
||||||
}
|
// .parallel(10)
|
||||||
|
// .method(Method::POST)
|
||||||
|
// .request_chunks(body, 100)
|
||||||
|
// .bench(b)
|
||||||
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
// fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 10];
|
// let body = &[b'x'; 1024 * 1024 * 1];
|
||||||
opts().parallel(10).response_body(body).bench(b)
|
// 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]
|
// const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1;
|
||||||
fn http2_consecutive_x1_empty(b: &mut test::Bencher) {
|
|
||||||
opts().http2().bench(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
// fn http2_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||||
opts()
|
// opts().http2().bench(b)
|
||||||
.http2()
|
// }
|
||||||
.method(Method::POST)
|
|
||||||
.request_body(&[b's'; 10])
|
|
||||||
.bench(b)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) {
|
// fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 100];
|
// opts()
|
||||||
opts()
|
// .http2()
|
||||||
.http2()
|
// .method(Method::POST)
|
||||||
.method(Method::POST)
|
// .request_body(&[b's'; 10])
|
||||||
.request_body(body)
|
// .bench(b)
|
||||||
.bench(b)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_parallel_x10_empty(b: &mut test::Bencher) {
|
// fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) {
|
||||||
opts().http2().parallel(10).bench(b)
|
// let body = &[b'x'; 1024 * 100];
|
||||||
}
|
// opts()
|
||||||
|
// .http2()
|
||||||
|
// .method(Method::POST)
|
||||||
|
// .request_body(body)
|
||||||
|
// .bench(b)
|
||||||
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
// fn http2_parallel_x10_empty(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 10];
|
// opts().http2().parallel(10).bench(b)
|
||||||
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]
|
// #[bench]
|
||||||
fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
// fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 10];
|
// let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
opts()
|
// opts()
|
||||||
.http2()
|
// .http2()
|
||||||
.parallel(10)
|
// .parallel(10)
|
||||||
.method(Method::POST)
|
// .method(Method::POST)
|
||||||
.request_chunks(body, 100)
|
// .request_body(body)
|
||||||
.bench(b)
|
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
}
|
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
|
// .bench(b)
|
||||||
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) {
|
// fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 10];
|
// let body = &[b'x'; 1024 * 10];
|
||||||
opts()
|
// opts()
|
||||||
.http2()
|
// .http2()
|
||||||
.parallel(10)
|
// .parallel(10)
|
||||||
.method(Method::POST)
|
// .method(Method::POST)
|
||||||
.request_chunks(body, 100)
|
// .request_chunks(body, 100)
|
||||||
.http2_adaptive_window()
|
// .bench(b)
|
||||||
.bench(b)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) {
|
// fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 10];
|
// let body = &[b'x'; 1024 * 10];
|
||||||
opts()
|
// opts()
|
||||||
.http2()
|
// .http2()
|
||||||
.parallel(10)
|
// .parallel(10)
|
||||||
.method(Method::POST)
|
// .method(Method::POST)
|
||||||
.request_chunks(body, 100)
|
// .request_chunks(body, 100)
|
||||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
// .http2_adaptive_window()
|
||||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
// .bench(b)
|
||||||
.bench(b)
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
// fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 1];
|
// let body = &[b'x'; 1024 * 10];
|
||||||
opts()
|
// opts()
|
||||||
.http2()
|
// .http2()
|
||||||
.parallel(10)
|
// .parallel(10)
|
||||||
.response_body(body)
|
// .method(Method::POST)
|
||||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
// .request_chunks(body, 100)
|
||||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
.bench(b)
|
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
}
|
// .bench(b)
|
||||||
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
// fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||||
let body = &[b'x'; 1024 * 1024 * 10];
|
// let body = &[b'x'; 1024 * 1024 * 1];
|
||||||
opts()
|
// opts()
|
||||||
.http2()
|
// .http2()
|
||||||
.parallel(10)
|
// .parallel(10)
|
||||||
.response_body(body)
|
// .response_body(body)
|
||||||
.http2_stream_window(HTTP2_MAX_WINDOW)
|
// .http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
.http2_conn_window(HTTP2_MAX_WINDOW)
|
// .http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
.bench(b)
|
// .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 {
|
// // ==== Benchmark Options =====
|
||||||
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],
|
|
||||||
}
|
|
||||||
|
|
||||||
fn opts() -> Opts {
|
// struct Opts {
|
||||||
Opts {
|
// http2: bool,
|
||||||
http2: false,
|
// http2_stream_window: Option<u32>,
|
||||||
http2_stream_window: None,
|
// http2_conn_window: Option<u32>,
|
||||||
http2_conn_window: None,
|
// http2_adaptive_window: bool,
|
||||||
http2_adaptive_window: false,
|
// parallel_cnt: u32,
|
||||||
parallel_cnt: 1,
|
// request_method: Method,
|
||||||
request_method: Method::GET,
|
// request_body: Option<&'static [u8]>,
|
||||||
request_body: None,
|
// request_chunks: usize,
|
||||||
request_chunks: 0,
|
// response_body: &'static [u8],
|
||||||
response_body: b"",
|
// }
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Opts {
|
// fn opts() -> Opts {
|
||||||
fn http2(mut self) -> Self {
|
// Opts {
|
||||||
self.http2 = true;
|
// http2: false,
|
||||||
self
|
// 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 {
|
// impl Opts {
|
||||||
assert!(!self.http2_adaptive_window);
|
// fn http2(mut self) -> Self {
|
||||||
self.http2_stream_window = sz.into();
|
// self.http2 = true;
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
// fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||||
assert!(!self.http2_adaptive_window);
|
// assert!(!self.http2_adaptive_window);
|
||||||
self.http2_conn_window = sz.into();
|
// self.http2_stream_window = sz.into();
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn http2_adaptive_window(mut self) -> Self {
|
// fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||||
assert!(self.http2_stream_window.is_none());
|
// assert!(!self.http2_adaptive_window);
|
||||||
assert!(self.http2_conn_window.is_none());
|
// self.http2_conn_window = sz.into();
|
||||||
self.http2_adaptive_window = true;
|
// self
|
||||||
self
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
fn method(mut self, m: Method) -> Self {
|
// fn http2_adaptive_window(mut self) -> Self {
|
||||||
self.request_method = m;
|
// assert!(self.http2_stream_window.is_none());
|
||||||
self
|
// assert!(self.http2_conn_window.is_none());
|
||||||
}
|
// self.http2_adaptive_window = true;
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
fn request_body(mut self, body: &'static [u8]) -> Self {
|
// fn method(mut self, m: Method) -> Self {
|
||||||
self.request_body = Some(body);
|
// self.request_method = m;
|
||||||
self
|
// self
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self {
|
// fn request_body(mut self, body: &'static [u8]) -> Self {
|
||||||
assert!(cnt > 0);
|
// self.request_body = Some(body);
|
||||||
self.request_body = Some(chunk);
|
// self
|
||||||
self.request_chunks = cnt;
|
// }
|
||||||
self
|
|
||||||
}
|
|
||||||
|
|
||||||
fn response_body(mut self, body: &'static [u8]) -> Self {
|
// fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self {
|
||||||
self.response_body = body;
|
// assert!(cnt > 0);
|
||||||
self
|
// self.request_body = Some(chunk);
|
||||||
}
|
// self.request_chunks = cnt;
|
||||||
|
// self
|
||||||
|
// }
|
||||||
|
|
||||||
fn parallel(mut self, cnt: u32) -> Self {
|
// fn response_body(mut self, body: &'static [u8]) -> Self {
|
||||||
assert!(cnt > 0, "parallel count must be larger than 0");
|
// self.response_body = body;
|
||||||
self.parallel_cnt = cnt;
|
// self
|
||||||
self
|
// }
|
||||||
}
|
|
||||||
|
|
||||||
fn bench(self, b: &mut test::Bencher) {
|
// fn parallel(mut self, cnt: u32) -> Self {
|
||||||
use std::sync::Arc;
|
// assert!(cnt > 0, "parallel count must be larger than 0");
|
||||||
let _ = pretty_env_logger::try_init();
|
// self.parallel_cnt = cnt;
|
||||||
// Create a runtime of current thread.
|
// self
|
||||||
let rt = Arc::new(
|
// }
|
||||||
tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.expect("rt build"),
|
|
||||||
);
|
|
||||||
let exec = rt.clone();
|
|
||||||
|
|
||||||
let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64;
|
// fn bench(self, b: &mut test::Bencher) {
|
||||||
let req_len = if self.request_chunks > 0 {
|
// use std::sync::Arc;
|
||||||
req_len * self.request_chunks as u64
|
// let _ = pretty_env_logger::try_init();
|
||||||
} else {
|
// // Create a runtime of current thread.
|
||||||
req_len
|
// let rt = Arc::new(
|
||||||
};
|
// tokio::runtime::Builder::new_current_thread()
|
||||||
let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64;
|
// .enable_all()
|
||||||
b.bytes = bytes_per_iter;
|
// .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 addr = spawn_server(&rt, &self);
|
||||||
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 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 url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap();
|
||||||
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 send_request = |req: Request<Body>| {
|
// let make_request = || {
|
||||||
let fut = client.request(req);
|
// let chunk_cnt = self.request_chunks;
|
||||||
async {
|
// let body = if chunk_cnt > 0 {
|
||||||
let res = fut.await.expect("client wait");
|
// let (mut tx, body) = Body::channel();
|
||||||
let mut body = res.into_body();
|
// let chunk = self
|
||||||
while let Some(_chunk) = body.data().await {}
|
// .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 {
|
// let send_request = |req: Request<Body>| {
|
||||||
b.iter(|| {
|
// let fut = client.request(req);
|
||||||
let req = make_request();
|
// async {
|
||||||
rt.block_on(send_request(req));
|
// let res = fut.await.expect("client wait");
|
||||||
});
|
// let mut body = res.into_body();
|
||||||
} else {
|
// while let Some(_chunk) = body.data().await {}
|
||||||
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));
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr {
|
// if self.parallel_cnt == 1 {
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
// b.iter(|| {
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
// 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;
|
// fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr {
|
||||||
let srv = rt.block_on(async move {
|
// use hyper::service::{make_service_fn, service_fn};
|
||||||
Server::bind(&addr)
|
// let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
.http2_only(opts.http2)
|
|
||||||
.http2_initial_stream_window_size(opts.http2_stream_window)
|
// let body = opts.response_body;
|
||||||
.http2_initial_connection_window_size(opts.http2_conn_window)
|
// let srv = rt.block_on(async move {
|
||||||
.http2_adaptive_window(opts.http2_adaptive_window)
|
// Server::bind(&addr)
|
||||||
.serve(make_service_fn(move |_| async move {
|
// .http2_only(opts.http2)
|
||||||
Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move {
|
// .http2_initial_stream_window_size(opts.http2_stream_window)
|
||||||
let mut req_body = req.into_body();
|
// .http2_initial_connection_window_size(opts.http2_conn_window)
|
||||||
while let Some(_chunk) = req_body.data().await {}
|
// .http2_adaptive_window(opts.http2_adaptive_window)
|
||||||
Ok::<_, hyper::Error>(Response::new(Body::from(body)))
|
// .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 {}
|
||||||
let addr = srv.local_addr();
|
// Ok::<_, hyper::Error>(Response::new(Body::from(body)))
|
||||||
rt.spawn(async {
|
// }))
|
||||||
if let Err(err) = srv.await {
|
// }))
|
||||||
panic!("server error: {}", err);
|
// });
|
||||||
}
|
// let addr = srv.local_addr();
|
||||||
});
|
// rt.spawn(async {
|
||||||
addr
|
// if let Err(err) = srv.await {
|
||||||
}
|
// panic!("server error: {}", err);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// addr
|
||||||
|
// }
|
||||||
|
|||||||
@@ -3,84 +3,87 @@
|
|||||||
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
// TODO: Reimplement hello_world_16 bench using hyper::server::conn
|
||||||
use std::net::TcpStream;
|
// (instead of Server).
|
||||||
use std::sync::mpsc;
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use tokio::sync::oneshot;
|
// use std::io::{Read, Write};
|
||||||
|
// use std::net::TcpStream;
|
||||||
|
// use std::sync::mpsc;
|
||||||
|
// use std::time::Duration;
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
// use tokio::sync::oneshot;
|
||||||
use hyper::{Body, Response, Server};
|
|
||||||
|
|
||||||
const PIPELINED_REQUESTS: usize = 16;
|
// use hyper::service::{make_service_fn, service_fn};
|
||||||
|
// use hyper::{Body, Response, Server};
|
||||||
|
|
||||||
#[bench]
|
// const PIPELINED_REQUESTS: usize = 16;
|
||||||
fn hello_world_16(b: &mut test::Bencher) {
|
|
||||||
let _ = pretty_env_logger::try_init();
|
|
||||||
let (_until_tx, until_rx) = oneshot::channel::<()>();
|
|
||||||
|
|
||||||
let addr = {
|
// #[bench]
|
||||||
let (addr_tx, addr_rx) = mpsc::channel();
|
// fn hello_world_16(b: &mut test::Bencher) {
|
||||||
std::thread::spawn(move || {
|
// let _ = pretty_env_logger::try_init();
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
// let (_until_tx, until_rx) = oneshot::channel::<()>();
|
||||||
|
|
||||||
let make_svc = make_service_fn(|_| async {
|
// let addr = {
|
||||||
Ok::<_, hyper::Error>(service_fn(|_| async {
|
// let (addr_tx, addr_rx) = mpsc::channel();
|
||||||
Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!")))
|
// std::thread::spawn(move || {
|
||||||
}))
|
// let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
});
|
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
// let make_svc = make_service_fn(|_| async {
|
||||||
.enable_all()
|
// Ok::<_, hyper::Error>(service_fn(|_| async {
|
||||||
.build()
|
// Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!")))
|
||||||
.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 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)
|
||||||
|
// });
|
||||||
|
|
||||||
let graceful = srv.with_graceful_shutdown(async {
|
// addr_tx.send(srv.local_addr()).unwrap();
|
||||||
until_rx.await.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
rt.block_on(async {
|
// let graceful = srv.with_graceful_shutdown(async {
|
||||||
if let Err(e) = graceful.await {
|
// until_rx.await.ok();
|
||||||
panic!("server error: {}", e);
|
// });
|
||||||
}
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
addr_rx.recv().unwrap()
|
// rt.block_on(async {
|
||||||
};
|
// if let Err(e) = graceful.await {
|
||||||
|
// panic!("server error: {}", e);
|
||||||
|
// }
|
||||||
|
// });
|
||||||
|
// });
|
||||||
|
|
||||||
let mut pipelined_reqs = Vec::new();
|
// addr_rx.recv().unwrap()
|
||||||
for _ in 0..PIPELINED_REQUESTS {
|
// };
|
||||||
pipelined_reqs.extend_from_slice(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
let total_bytes = {
|
// let mut pipelined_reqs = Vec::new();
|
||||||
let mut tcp = TcpStream::connect(addr).unwrap();
|
// for _ in 0..PIPELINED_REQUESTS {
|
||||||
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
// pipelined_reqs.extend_from_slice(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
.unwrap();
|
// }
|
||||||
let mut buf = Vec::new();
|
|
||||||
tcp.read_to_end(&mut buf).unwrap()
|
|
||||||
} * PIPELINED_REQUESTS;
|
|
||||||
|
|
||||||
let mut tcp = TcpStream::connect(addr).unwrap();
|
// let total_bytes = {
|
||||||
tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
|
// let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
let mut buf = [0u8; 8192];
|
// tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
||||||
|
// .unwrap();
|
||||||
|
// let mut buf = Vec::new();
|
||||||
|
// tcp.read_to_end(&mut buf).unwrap()
|
||||||
|
// } * PIPELINED_REQUESTS;
|
||||||
|
|
||||||
b.bytes = (pipelined_reqs.len() + total_bytes) as u64;
|
// let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
b.iter(|| {
|
// tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
|
||||||
tcp.write_all(&pipelined_reqs).unwrap();
|
// let mut buf = [0u8; 8192];
|
||||||
let mut sum = 0;
|
|
||||||
while sum < total_bytes {
|
// b.bytes = (pipelined_reqs.len() + total_bytes) as u64;
|
||||||
sum += tcp.read(&mut buf).unwrap();
|
// b.iter(|| {
|
||||||
}
|
// tcp.write_all(&pipelined_reqs).unwrap();
|
||||||
assert_eq!(sum, total_bytes);
|
// let mut sum = 0;
|
||||||
});
|
// while sum < total_bytes {
|
||||||
}
|
// sum += tcp.read(&mut buf).unwrap();
|
||||||
|
// }
|
||||||
|
// assert_eq!(sum, total_bytes);
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|||||||
@@ -3,130 +3,133 @@
|
|||||||
|
|
||||||
extern crate test;
|
extern crate test;
|
||||||
|
|
||||||
|
// TODO: Reimplement bench_server using hyper::server::conn (instead
|
||||||
|
// of removed Server).
|
||||||
|
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::{TcpListener, TcpStream};
|
use std::net::{TcpListener, TcpStream};
|
||||||
use std::sync::mpsc;
|
use std::sync::mpsc;
|
||||||
use std::time::Duration;
|
// use std::time::Duration;
|
||||||
|
|
||||||
use futures_util::{stream, StreamExt};
|
// use futures_util::{stream, StreamExt};
|
||||||
use http_body_util::StreamBody;
|
// use http_body_util::StreamBody;
|
||||||
use tokio::sync::oneshot;
|
// use tokio::sync::oneshot;
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
// use hyper::service::{make_service_fn, service_fn};
|
||||||
use hyper::{Response, Server};
|
// use hyper::{Response, Server};
|
||||||
|
|
||||||
macro_rules! bench_server {
|
// macro_rules! bench_server {
|
||||||
($b:ident, $header:expr, $body:expr) => {{
|
// ($b:ident, $header:expr, $body:expr) => {{
|
||||||
let _ = pretty_env_logger::try_init();
|
// let _ = pretty_env_logger::try_init();
|
||||||
let (_until_tx, until_rx) = oneshot::channel::<()>();
|
// let (_until_tx, until_rx) = oneshot::channel::<()>();
|
||||||
let addr = {
|
// let addr = {
|
||||||
let (addr_tx, addr_rx) = mpsc::channel();
|
// let (addr_tx, addr_rx) = mpsc::channel();
|
||||||
std::thread::spawn(move || {
|
// std::thread::spawn(move || {
|
||||||
let addr = "127.0.0.1:0".parse().unwrap();
|
// let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
let make_svc = make_service_fn(|_| async {
|
// let make_svc = make_service_fn(|_| async {
|
||||||
Ok::<_, hyper::Error>(service_fn(|_| async {
|
// Ok::<_, hyper::Error>(service_fn(|_| async {
|
||||||
Ok::<_, hyper::Error>(
|
// Ok::<_, hyper::Error>(
|
||||||
Response::builder()
|
// Response::builder()
|
||||||
.header($header.0, $header.1)
|
// .header($header.0, $header.1)
|
||||||
.header("content-type", "text/plain")
|
// .header("content-type", "text/plain")
|
||||||
.body($body())
|
// .body($body())
|
||||||
.unwrap(),
|
// .unwrap(),
|
||||||
)
|
// )
|
||||||
}))
|
// }))
|
||||||
});
|
// });
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
// let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
// .enable_all()
|
||||||
.build()
|
// .build()
|
||||||
.expect("rt build");
|
// .expect("rt build");
|
||||||
|
|
||||||
let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) });
|
// let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) });
|
||||||
|
|
||||||
addr_tx.send(srv.local_addr()).unwrap();
|
// addr_tx.send(srv.local_addr()).unwrap();
|
||||||
|
|
||||||
let graceful = srv.with_graceful_shutdown(async {
|
// let graceful = srv.with_graceful_shutdown(async {
|
||||||
until_rx.await.ok();
|
// until_rx.await.ok();
|
||||||
});
|
// });
|
||||||
rt.block_on(async move {
|
// rt.block_on(async move {
|
||||||
if let Err(e) = graceful.await {
|
// if let Err(e) = graceful.await {
|
||||||
panic!("server error: {}", e);
|
// panic!("server error: {}", e);
|
||||||
}
|
// }
|
||||||
});
|
// });
|
||||||
});
|
// });
|
||||||
|
|
||||||
addr_rx.recv().unwrap()
|
// addr_rx.recv().unwrap()
|
||||||
};
|
// };
|
||||||
|
|
||||||
let total_bytes = {
|
// let total_bytes = {
|
||||||
let mut tcp = TcpStream::connect(addr).unwrap();
|
// let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
// tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
let mut buf = Vec::new();
|
// let mut buf = Vec::new();
|
||||||
tcp.read_to_end(&mut buf).unwrap()
|
// tcp.read_to_end(&mut buf).unwrap()
|
||||||
};
|
// };
|
||||||
|
|
||||||
let mut tcp = TcpStream::connect(addr).unwrap();
|
// let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
|
// tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
|
||||||
let mut buf = [0u8; 8192];
|
// let mut buf = [0u8; 8192];
|
||||||
|
|
||||||
$b.bytes = 35 + total_bytes as u64;
|
// $b.bytes = 35 + total_bytes as u64;
|
||||||
$b.iter(|| {
|
// $b.iter(|| {
|
||||||
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
|
// tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
|
||||||
.unwrap();
|
// .unwrap();
|
||||||
let mut sum = 0;
|
// let mut sum = 0;
|
||||||
while sum < total_bytes {
|
// while sum < total_bytes {
|
||||||
sum += tcp.read(&mut buf).unwrap();
|
// sum += tcp.read(&mut buf).unwrap();
|
||||||
}
|
// }
|
||||||
assert_eq!(sum, total_bytes);
|
// assert_eq!(sum, total_bytes);
|
||||||
});
|
// });
|
||||||
}};
|
// }};
|
||||||
}
|
// }
|
||||||
|
|
||||||
fn body(b: &'static [u8]) -> hyper::Body {
|
// fn body(b: &'static [u8]) -> hyper::Body {
|
||||||
b.into()
|
// b.into()
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn throughput_fixedsize_small_payload(b: &mut test::Bencher) {
|
// fn throughput_fixedsize_small_payload(b: &mut test::Bencher) {
|
||||||
bench_server!(b, ("content-length", "13"), || body(b"Hello, World!"))
|
// bench_server!(b, ("content-length", "13"), || body(b"Hello, World!"))
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn throughput_fixedsize_large_payload(b: &mut test::Bencher) {
|
// fn throughput_fixedsize_large_payload(b: &mut test::Bencher) {
|
||||||
bench_server!(b, ("content-length", "1000000"), || body(
|
// bench_server!(b, ("content-length", "1000000"), || body(
|
||||||
&[b'x'; 1_000_000]
|
// &[b'x'; 1_000_000]
|
||||||
))
|
// ))
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) {
|
// fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) {
|
||||||
bench_server!(b, ("content-length", "1000000"), || {
|
// bench_server!(b, ("content-length", "1000000"), || {
|
||||||
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
// static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
||||||
StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
// StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn throughput_chunked_small_payload(b: &mut test::Bencher) {
|
// fn throughput_chunked_small_payload(b: &mut test::Bencher) {
|
||||||
bench_server!(b, ("transfer-encoding", "chunked"), || body(
|
// bench_server!(b, ("transfer-encoding", "chunked"), || body(
|
||||||
b"Hello, World!"
|
// b"Hello, World!"
|
||||||
))
|
// ))
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn throughput_chunked_large_payload(b: &mut test::Bencher) {
|
// fn throughput_chunked_large_payload(b: &mut test::Bencher) {
|
||||||
bench_server!(b, ("transfer-encoding", "chunked"), || body(
|
// bench_server!(b, ("transfer-encoding", "chunked"), || body(
|
||||||
&[b'x'; 1_000_000]
|
// &[b'x'; 1_000_000]
|
||||||
))
|
// ))
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
// #[bench]
|
||||||
fn throughput_chunked_many_chunks(b: &mut test::Bencher) {
|
// fn throughput_chunked_many_chunks(b: &mut test::Bencher) {
|
||||||
bench_server!(b, ("transfer-encoding", "chunked"), || {
|
// bench_server!(b, ("transfer-encoding", "chunked"), || {
|
||||||
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
// static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
||||||
StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
// StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
||||||
})
|
// })
|
||||||
}
|
// }
|
||||||
|
|
||||||
#[bench]
|
#[bench]
|
||||||
fn raw_tcp_throughput_small_payload(b: &mut test::Bencher) {
|
fn raw_tcp_throughput_small_payload(b: &mut test::Bencher) {
|
||||||
|
|||||||
@@ -2,8 +2,9 @@
|
|||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
use std::env;
|
use std::env;
|
||||||
|
|
||||||
use hyper::{body::HttpBody as _, Client};
|
use hyper::{body::HttpBody as _, Body, Request};
|
||||||
use tokio::io::{self, AsyncWriteExt as _};
|
use tokio::io::{self, AsyncWriteExt as _};
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
// A simple type alias so as to DRY.
|
// A simple type alias so as to DRY.
|
||||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
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<()> {
|
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!("Response: {}", res.status());
|
||||||
println!("Headers: {:#?}\n", res.headers());
|
println!("Headers: {:#?}\n", res.headers());
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
use hyper::body::Buf;
|
use hyper::Body;
|
||||||
use hyper::Client;
|
use hyper::{body::Buf, Request};
|
||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
// A simple type alias so as to DRY.
|
// A simple type alias so as to DRY.
|
||||||
type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>;
|
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>> {
|
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...
|
// 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
|
// asynchronously aggregate the chunks of the body
|
||||||
let body = hyper::body::aggregate(res).await?;
|
let body = hyper::body::aggregate(res).await?;
|
||||||
|
|||||||
@@ -1,7 +1,11 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use std::net::SocketAddr;
|
||||||
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;
|
||||||
|
|
||||||
/// This is our service handler. It receives a Request, routes on its
|
/// This is our service handler. It receives a Request, routes on its
|
||||||
/// path, and returns a Future of a Response.
|
/// path, and returns a Future of a Response.
|
||||||
@@ -51,15 +55,17 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> {
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
let addr = ([127, 0, 0, 1], 3000).into();
|
let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
|
||||||
let service = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(echo)) });
|
|
||||||
|
|
||||||
let server = Server::bind(&addr).serve(service);
|
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
println!("Listening on http://{}", addr);
|
println!("Listening on http://{}", addr);
|
||||||
|
loop {
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
|
||||||
server.await?;
|
tokio::task::spawn(async move {
|
||||||
|
if let Err(err) = Http::new().serve_connection(stream, service_fn(echo)).await {
|
||||||
Ok(())
|
println!("Error serving connection: {:?}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,51 +1,63 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::{server::conn::Http, service::service_fn};
|
||||||
use hyper::{Client, Error, Server};
|
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
pretty_env_logger::init();
|
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 out_addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||||
|
|
||||||
let client_main = Client::new();
|
|
||||||
|
|
||||||
let out_addr_clone = out_addr.clone();
|
let out_addr_clone = out_addr.clone();
|
||||||
|
|
||||||
// The closure inside `make_service_fn` is run for each connection,
|
let listener = TcpListener::bind(in_addr).await?;
|
||||||
// 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);
|
|
||||||
|
|
||||||
println!("Listening on http://{}", in_addr);
|
println!("Listening on http://{}", in_addr);
|
||||||
println!("Proxying on http://{}", out_addr);
|
println!("Proxying on http://{}", out_addr);
|
||||||
|
|
||||||
if let Err(e) = server.await {
|
loop {
|
||||||
eprintln!("server error: {}", e);
|
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)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::server::conn::Http;
|
||||||
use hyper::{Body, Request, Response, Server};
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Request, Response};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
|
async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
Ok(Response::new(Body::from("Hello World!")))
|
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>> {
|
pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
// For every connection, we must make a `Service` to handle all
|
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||||
// 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 listener = TcpListener::bind(addr).await?;
|
||||||
println!("Listening on http://{}", addr);
|
println!("Listening on http://{}", addr);
|
||||||
|
loop {
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
|
||||||
server.await?;
|
tokio::task::spawn(async move {
|
||||||
|
if let Err(err) = Http::new()
|
||||||
Ok(())
|
.serve_connection(stream, service_fn(hello))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Error serving connection: {:?}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,15 +1,14 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
use std::convert::Infallible;
|
|
||||||
use std::net::SocketAddr;
|
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::upgrade::Upgraded;
|
||||||
use hyper::{Body, Client, Method, Request, Response, Server};
|
use hyper::{Body, Method, Request, Response};
|
||||||
|
|
||||||
use tokio::net::TcpStream;
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
type HttpClient = Client<hyper::client::HttpConnector>;
|
|
||||||
|
|
||||||
// To try this example:
|
// To try this example:
|
||||||
// 1. cargo run --example http_proxy
|
// 1. cargo run --example http_proxy
|
||||||
@@ -19,32 +18,29 @@ type HttpClient = Client<hyper::client::HttpConnector>;
|
|||||||
// 3. send requests
|
// 3. send requests
|
||||||
// $ curl -i https://www.some_domain.com/
|
// $ curl -i https://www.some_domain.com/
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let addr = SocketAddr::from(([127, 0, 0, 1], 8100));
|
let addr = SocketAddr::from(([127, 0, 0, 1], 8100));
|
||||||
|
|
||||||
let client = Client::builder()
|
let listener = TcpListener::bind(addr).await?;
|
||||||
.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);
|
|
||||||
|
|
||||||
println!("Listening on http://{}", addr);
|
println!("Listening on http://{}", addr);
|
||||||
|
|
||||||
if let Err(e) = server.await {
|
loop {
|
||||||
eprintln!("server error: {}", e);
|
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);
|
println!("req: {:?}", req);
|
||||||
|
|
||||||
if Method::CONNECT == req.method() {
|
if Method::CONNECT == req.method() {
|
||||||
@@ -82,7 +78,24 @@ async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>,
|
|||||||
Ok(resp)
|
Ok(resp)
|
||||||
}
|
}
|
||||||
} else {
|
} 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)]
|
#![deny(warnings)]
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use futures_util::future::join;
|
use futures_util::future::join;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::server::conn::Http;
|
||||||
use hyper::{Body, Request, Response, Server};
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Request, Response};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
static INDEX1: &[u8] = b"The 1st service!";
|
static INDEX1: &[u8] = b"The 1st service!";
|
||||||
static INDEX2: &[u8] = b"The 2nd 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>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let addr1 = ([127, 0, 0, 1], 1337).into();
|
let addr1: SocketAddr = ([127, 0, 0, 1], 1337).into();
|
||||||
let addr2 = ([127, 0, 0, 1], 1338).into();
|
let addr2: SocketAddr = ([127, 0, 0, 1], 1338).into();
|
||||||
|
|
||||||
let srv1 = Server::bind(&addr1).serve(make_service_fn(|_| async {
|
let srv1 = async move {
|
||||||
Ok::<_, hyper::Error>(service_fn(index1))
|
let listener = TcpListener::bind(addr1).await.unwrap();
|
||||||
}));
|
loop {
|
||||||
|
let (stream, _) = listener.accept().await.unwrap();
|
||||||
|
|
||||||
let srv2 = Server::bind(&addr2).serve(make_service_fn(|_| async {
|
tokio::task::spawn(async move {
|
||||||
Ok::<_, hyper::Error>(service_fn(index2))
|
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);
|
println!("Listening on http://{} and http://{}", addr1, addr2);
|
||||||
|
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// #![deny(warnings)] // FIXME: https://github.com/rust-lang/rust/issues/62411
|
// #![deny(warnings)] // FIXME: https://github.com/rust-lang/rust/issues/62411
|
||||||
#![warn(rust_2018_idioms)]
|
#![warn(rust_2018_idioms)]
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::server::conn::Http;
|
||||||
use hyper::{Body, Method, Request, Response, Server, StatusCode};
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Method, Request, Response, StatusCode};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use url::form_urlencoded;
|
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>";
|
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>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let addr = ([127, 0, 0, 1], 1337).into();
|
let addr: SocketAddr = ([127, 0, 0, 1], 1337).into();
|
||||||
|
|
||||||
let server = Server::bind(&addr).serve(make_service_fn(|_| async {
|
|
||||||
Ok::<_, hyper::Error>(service_fn(param_example))
|
|
||||||
}));
|
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
println!("Listening on http://{}", addr);
|
println!("Listening on http://{}", addr);
|
||||||
|
loop {
|
||||||
|
let (stream, _) = listener.accept().await?;
|
||||||
|
|
||||||
server.await?;
|
tokio::task::spawn(async move {
|
||||||
|
if let Err(err) = Http::new()
|
||||||
Ok(())
|
.serve_connection(stream, service_fn(param_example))
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
println!("Error serving connection: {:?}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,26 +1,36 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use std::net::SocketAddr;
|
||||||
use hyper::{Body, Method, Request, Response, Result, Server, StatusCode};
|
|
||||||
|
use hyper::server::conn::Http;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Method, Request, Response, Result, StatusCode};
|
||||||
|
|
||||||
static INDEX: &str = "examples/send_file_index.html";
|
static INDEX: &str = "examples/send_file_index.html";
|
||||||
static NOTFOUND: &[u8] = b"Not Found";
|
static NOTFOUND: &[u8] = b"Not Found";
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let addr = "127.0.0.1:1337".parse().unwrap();
|
let addr: SocketAddr = "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 listener = TcpListener::bind(addr).await?;
|
||||||
println!("Listening on http://{}", addr);
|
println!("Listening on http://{}", addr);
|
||||||
|
|
||||||
if let Err(e) = server.await {
|
loop {
|
||||||
eprintln!("server error: {}", e);
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
|
use hyper::server::conn::Http;
|
||||||
use hyper::service::Service;
|
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::future::Future;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
@@ -9,13 +12,23 @@ type Counter = i32;
|
|||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
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);
|
println!("Listening on http://{}", addr);
|
||||||
|
|
||||||
server.await?;
|
loop {
|
||||||
Ok(())
|
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 {
|
struct Svc {
|
||||||
@@ -54,23 +67,3 @@ impl Service<Request<Body>> for Svc {
|
|||||||
Box::pin(async { res })
|
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)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
use hyper::server::conn::Http;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use tokio::sync::oneshot;
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
use hyper::body::{Bytes, HttpBody};
|
use hyper::body::{Bytes, HttpBody};
|
||||||
use hyper::header::{HeaderMap, HeaderValue};
|
use hyper::header::{HeaderMap, HeaderValue};
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::service_fn;
|
||||||
use hyper::{Error, Response, Server};
|
use hyper::{Error, Response};
|
||||||
use std::marker::PhantomData;
|
use std::marker::PhantomData;
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::task::{Context, Poll};
|
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();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
// Configure a runtime that runs everything on the current thread
|
// 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...
|
// Combine it with a `LocalSet, which means it can spawn !Send futures...
|
||||||
let local = tokio::task::LocalSet::new();
|
let local = tokio::task::LocalSet::new();
|
||||||
local.block_on(&rt, run());
|
local.block_on(&rt, run())
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn run() {
|
async fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
let addr = ([127, 0, 0, 1], 3000).into();
|
let addr: SocketAddr = ([127, 0, 0, 1], 3000).into();
|
||||||
|
|
||||||
// Using a !Send request counter is fine on 1 thread...
|
// Using a !Send request counter is fine on 1 thread...
|
||||||
let counter = Rc::new(Cell::new(0));
|
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...
|
// For each connection, clone the counter to use in our service...
|
||||||
let cnt = counter.clone();
|
let cnt = counter.clone();
|
||||||
|
|
||||||
async move {
|
let service = service_fn(move |_| {
|
||||||
Ok::<_, Error>(service_fn(move |_| {
|
let prev = cnt.get();
|
||||||
let prev = cnt.get();
|
cnt.set(prev + 1);
|
||||||
cnt.set(prev + 1);
|
let value = cnt.get();
|
||||||
let value = cnt.get();
|
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) }
|
||||||
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) }
|
});
|
||||||
}))
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
let server = Server::bind(&addr).executor(LocalExec).serve(make_service);
|
tokio::task::spawn_local(async move {
|
||||||
|
if let Err(err) = Http::new()
|
||||||
// Just shows that with_graceful_shutdown compiles with !Send,
|
.with_executor(LocalExec)
|
||||||
// !Sync HttpBody.
|
.serve_connection(stream, service)
|
||||||
let (_tx, rx) = oneshot::channel::<()>();
|
.await
|
||||||
let server = server.with_graceful_shutdown(async move {
|
{
|
||||||
rx.await.ok();
|
println!("Error serving connection: {:?}", err);
|
||||||
});
|
}
|
||||||
|
});
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,52 +1,46 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::sync::{
|
use std::sync::{
|
||||||
atomic::{AtomicUsize, Ordering},
|
atomic::{AtomicUsize, Ordering},
|
||||||
Arc,
|
Arc,
|
||||||
};
|
};
|
||||||
|
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::{server::conn::Http, service::service_fn};
|
||||||
use hyper::{Body, Error, Response, Server};
|
use hyper::{Body, Error, Response};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
pretty_env_logger::init();
|
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
|
// 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.
|
// with each request, and we send its value back in the response.
|
||||||
let counter = Arc::new(AtomicUsize::new(0));
|
let counter = Arc::new(AtomicUsize::new(0));
|
||||||
|
|
||||||
// The closure inside `make_service_fn` is run for each connection,
|
let listener = TcpListener::bind(addr).await?;
|
||||||
// creating a 'service' to handle requests for that specific connection.
|
println!("Listening on http://{}", addr);
|
||||||
let make_service = make_service_fn(move |_| {
|
loop {
|
||||||
// While the state was moved into the make_service closure,
|
let (stream, _) = listener.accept().await?;
|
||||||
// we need to clone it here because this closure is called
|
|
||||||
// once for every connection.
|
|
||||||
//
|
|
||||||
// Each connection could send multiple requests, so
|
// Each connection could send multiple requests, so
|
||||||
// the `Service` needs a clone to handle later requests.
|
// the `Service` needs a clone to handle later requests.
|
||||||
let counter = counter.clone();
|
let counter = counter.clone();
|
||||||
|
|
||||||
async move {
|
// This is the `Service` that will handle the connection.
|
||||||
// This is the `Service` that will handle the connection.
|
// `service_fn` is a helper to convert a function that
|
||||||
// `service_fn` is a helper to convert a function that
|
// returns a Response into a `Service`.
|
||||||
// returns a Response into a `Service`.
|
let service = service_fn(move |_req| {
|
||||||
Ok::<_, Error>(service_fn(move |_req| {
|
// Get the current count, and also increment by 1, in a single
|
||||||
// Get the current count, and also increment by 1, in a single
|
// atomic operation.
|
||||||
// atomic operation.
|
let count = counter.fetch_add(1, Ordering::AcqRel);
|
||||||
let count = counter.fetch_add(1, Ordering::AcqRel);
|
async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) }
|
||||||
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,20 +1,20 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
use hyper::client::conn::Builder;
|
use std::future::Future;
|
||||||
use hyper::client::connect::HttpConnector;
|
use std::pin::Pin;
|
||||||
use hyper::client::service::Connect;
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use hyper::service::Service;
|
use hyper::service::Service;
|
||||||
use hyper::{Body, Request};
|
use hyper::{Body, Request, Response};
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
#[tokio::main]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync + 'static>> {
|
||||||
pretty_env_logger::init();
|
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 uri = "http://127.0.0.1:8080".parse::<http::Uri>()?;
|
||||||
|
|
||||||
let mut svc = mk_svc.call(uri.clone()).await?;
|
let mut svc = Connector;
|
||||||
|
|
||||||
let body = Body::empty();
|
let body = Body::empty();
|
||||||
|
|
||||||
@@ -25,3 +25,35 @@ async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
|||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct Connector;
|
||||||
|
|
||||||
|
impl Service<Request<Body>> for Connector {
|
||||||
|
type Response = Response<Body>;
|
||||||
|
type Error = Box<dyn std::error::Error + Send + Sync + 'static>;
|
||||||
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>>>>;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> std::task::Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<Body>) -> Self::Future {
|
||||||
|
Box::pin(async move {
|
||||||
|
let host = req.uri().host().expect("no host in uri");
|
||||||
|
let port = req.uri().port_u16().expect("no port in uri");
|
||||||
|
|
||||||
|
let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
|
||||||
|
|
||||||
|
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 res = sender.send_request(req).await?;
|
||||||
|
Ok(res)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use futures_util::future;
|
use futures_util::future;
|
||||||
|
use hyper::server::conn::Http;
|
||||||
use hyper::service::Service;
|
use hyper::service::Service;
|
||||||
use hyper::{Body, Request, Response, Server};
|
use hyper::{Body, Request, Response};
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
const ROOT: &str = "/";
|
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]
|
#[tokio::main]
|
||||||
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
async fn main() -> Result<(), Box<dyn std::error::Error>> {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let addr = "127.0.0.1:1337".parse().unwrap();
|
let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap();
|
||||||
|
|
||||||
let server = Server::bind(&addr).serve(MakeSvc);
|
|
||||||
|
|
||||||
|
let listener = TcpListener::bind(addr).await?;
|
||||||
println!("Listening on http://{}", addr);
|
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.
|
// Note: `hyper::upgrade` docs link to this upgrade.
|
||||||
use std::str;
|
use std::str;
|
||||||
|
|
||||||
|
use hyper::server::conn::Http;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
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::header::{HeaderValue, UPGRADE};
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::service_fn;
|
||||||
use hyper::upgrade::Upgraded;
|
use hyper::upgrade::Upgraded;
|
||||||
use hyper::{Body, Client, Request, Response, Server, StatusCode};
|
use hyper::{Body, Request, Response, StatusCode};
|
||||||
use std::net::SocketAddr;
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
// A simple type alias so as to DRY.
|
// A simple type alias so as to DRY.
|
||||||
@@ -92,7 +94,17 @@ async fn client_upgrade_request(addr: SocketAddr) -> Result<()> {
|
|||||||
.body(Body::empty())
|
.body(Body::empty())
|
||||||
.unwrap();
|
.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 {
|
if res.status() != StatusCode::SWITCHING_PROTOCOLS {
|
||||||
panic!("Our server didn't upgrade: {}", res.status());
|
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
|
// 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
|
// it, so the exact port isn't important. Instead, let the OS give us an
|
||||||
// unused port.
|
// unused port.
|
||||||
let addr = ([127, 0, 0, 1], 0).into();
|
let addr: SocketAddr = ([127, 0, 0, 1], 0).into();
|
||||||
|
|
||||||
let make_service =
|
let listener = TcpListener::bind(addr).await.expect("failed to bind");
|
||||||
make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(server_upgrade)) });
|
|
||||||
|
|
||||||
let server = Server::bind(&addr).serve(make_service);
|
|
||||||
|
|
||||||
// We need the assigned address for the client to send it messages.
|
// 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,
|
// For this example, a oneshot is used to signal that after 1 request,
|
||||||
// the server should be shutdown.
|
// the server should be shutdown.
|
||||||
let (tx, rx) = oneshot::channel::<()>();
|
let (tx, mut rx) = watch::channel(false);
|
||||||
let server = server.with_graceful_shutdown(async move {
|
|
||||||
rx.await.ok();
|
|
||||||
});
|
|
||||||
|
|
||||||
// Spawn server on the default executor,
|
// Spawn server on the default executor,
|
||||||
// which is usually a thread-pool from tokio default runtime.
|
// which is usually a thread-pool from tokio default runtime.
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
if let Err(e) = server.await {
|
loop {
|
||||||
eprintln!("server error: {}", e);
|
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
|
// Complete the oneshot so that the server stops
|
||||||
// listening and the process can close down.
|
// listening and the process can close down.
|
||||||
let _ = tx.send(());
|
let _ = tx.send(true);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,12 @@
|
|||||||
#![deny(warnings)]
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
use bytes::Buf;
|
use bytes::Buf;
|
||||||
use hyper::client::HttpConnector;
|
use hyper::server::conn::Http;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::service::service_fn;
|
||||||
use hyper::{header, Body, Client, Method, Request, Response, Server, StatusCode};
|
use hyper::{header, Body, Method, Request, Response, StatusCode};
|
||||||
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
type GenericError = Box<dyn std::error::Error + Send + Sync>;
|
type GenericError = Box<dyn std::error::Error + Send + Sync>;
|
||||||
type Result<T> = std::result::Result<T, GenericError>;
|
type Result<T> = std::result::Result<T, GenericError>;
|
||||||
@@ -14,7 +17,7 @@ static NOTFOUND: &[u8] = b"Not Found";
|
|||||||
static POST_DATA: &str = r#"{"original": "data"}"#;
|
static POST_DATA: &str = r#"{"original": "data"}"#;
|
||||||
static URL: &str = "http://127.0.0.1:1337/json_api";
|
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()
|
let req = Request::builder()
|
||||||
.method(Method::POST)
|
.method(Method::POST)
|
||||||
.uri(URL)
|
.uri(URL)
|
||||||
@@ -22,7 +25,19 @@ async fn client_request_response(client: &Client<HttpConnector>) -> Result<Respo
|
|||||||
.body(POST_DATA.into())
|
.body(POST_DATA.into())
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let web_res = client.request(req).await?;
|
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?;
|
||||||
|
|
||||||
|
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();
|
let res_body = web_res.into_body();
|
||||||
|
|
||||||
@@ -60,13 +75,10 @@ async fn api_get_response() -> Result<Response<Body>> {
|
|||||||
Ok(res)
|
Ok(res)
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn response_examples(
|
async fn response_examples(req: Request<Body>) -> Result<Response<Body>> {
|
||||||
req: Request<Body>,
|
|
||||||
client: Client<HttpConnector>,
|
|
||||||
) -> Result<Response<Body>> {
|
|
||||||
match (req.method(), req.uri().path()) {
|
match (req.method(), req.uri().path()) {
|
||||||
(&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())),
|
(&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::POST, "/json_api") => api_post_response(req).await,
|
||||||
(&Method::GET, "/json_api") => api_get_response().await,
|
(&Method::GET, "/json_api") => api_get_response().await,
|
||||||
_ => {
|
_ => {
|
||||||
@@ -83,27 +95,19 @@ async fn response_examples(
|
|||||||
async fn main() -> Result<()> {
|
async fn main() -> Result<()> {
|
||||||
pretty_env_logger::init();
|
pretty_env_logger::init();
|
||||||
|
|
||||||
let addr = "127.0.0.1:1337".parse().unwrap();
|
let addr: SocketAddr = "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 listener = TcpListener::bind(&addr).await?;
|
||||||
println!("Listening on http://{}", addr);
|
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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -608,6 +608,7 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn channel_abort() {
|
async fn channel_abort() {
|
||||||
let (tx, mut rx) = Body::channel();
|
let (tx, mut rx) = Body::channel();
|
||||||
@@ -618,6 +619,7 @@ mod tests {
|
|||||||
assert!(err.is_body_write_aborted(), "{:?}", err);
|
assert!(err.is_body_write_aborted(), "{:?}", err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn channel_abort_when_buffer_is_full() {
|
async fn channel_abort_when_buffer_is_full() {
|
||||||
let (mut tx, mut rx) = Body::channel();
|
let (mut tx, mut rx) = Body::channel();
|
||||||
@@ -644,6 +646,7 @@ mod tests {
|
|||||||
assert_eq!(chunk2, "chunk 2");
|
assert_eq!(chunk2, "chunk 2");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn channel_empty() {
|
async fn channel_empty() {
|
||||||
let (_, mut rx) = Body::channel();
|
let (_, mut rx) = Body::channel();
|
||||||
|
|||||||
@@ -17,17 +17,11 @@ use super::HttpBody;
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// # #[cfg(all(feature = "client", feature = "tcp", any(feature = "http1", feature = "http2")))]
|
|
||||||
/// # async fn doc() -> hyper::Result<()> {
|
/// # async fn doc() -> hyper::Result<()> {
|
||||||
/// use hyper::{body::HttpBody};
|
/// # use hyper::{Body, Response};
|
||||||
///
|
/// # use hyper::body::HttpBody;
|
||||||
/// # let request = hyper::Request::builder()
|
/// #
|
||||||
/// # .method(hyper::Method::POST)
|
/// let response = Response::new(Body::from("response body"));
|
||||||
/// # .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?;
|
|
||||||
///
|
///
|
||||||
/// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024;
|
/// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024;
|
||||||
///
|
///
|
||||||
|
|||||||
@@ -15,10 +15,11 @@ use super::connect::{self, sealed::Connect, Alpn, Connected, Connection};
|
|||||||
use super::pool::{
|
use super::pool::{
|
||||||
self, CheckoutIsClosedError, Key as PoolKey, Pool, Poolable, Pooled, Reservation,
|
self, CheckoutIsClosedError, Key as PoolKey, Pool, Poolable, Pooled, Reservation,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
use super::HttpConnector;
|
|
||||||
use crate::body::{Body, HttpBody};
|
use crate::body::{Body, HttpBody};
|
||||||
use crate::common::{exec::BoxSendFuture, sync_wrapper::SyncWrapper, lazy as hyper_lazy, task, Future, Lazy, Pin, Poll};
|
use crate::common::{
|
||||||
|
exec::BoxSendFuture, lazy as hyper_lazy, sync_wrapper::SyncWrapper, task, Future, Lazy, Pin,
|
||||||
|
Poll,
|
||||||
|
};
|
||||||
use crate::rt::Executor;
|
use crate::rt::Executor;
|
||||||
|
|
||||||
/// A Client to make outgoing HTTP requests.
|
/// A Client to make outgoing HTTP requests.
|
||||||
@@ -50,49 +51,8 @@ pub struct ResponseFuture {
|
|||||||
|
|
||||||
// ===== impl Client =====
|
// ===== impl Client =====
|
||||||
|
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
impl Client<HttpConnector, Body> {
|
|
||||||
/// Create a new Client with the default [config](Builder).
|
|
||||||
///
|
|
||||||
/// # Note
|
|
||||||
///
|
|
||||||
/// The default connector does **not** handle TLS. Speaking to `https`
|
|
||||||
/// destinations will require [configuring a connector that implements
|
|
||||||
/// TLS](https://hyper.rs/guides/client/configuration).
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "tcp")))]
|
|
||||||
#[inline]
|
|
||||||
pub fn new() -> Client<HttpConnector, Body> {
|
|
||||||
Builder::default().build_http()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
impl Default for Client<HttpConnector, Body> {
|
|
||||||
fn default() -> Client<HttpConnector, Body> {
|
|
||||||
Client::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Client<(), Body> {
|
impl Client<(), Body> {
|
||||||
/// Create a builder to configure a new `Client`.
|
/// Create a builder to configure a new `Client`.
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # #[cfg(feature = "runtime")]
|
|
||||||
/// # fn run () {
|
|
||||||
/// use std::time::Duration;
|
|
||||||
/// use hyper::Client;
|
|
||||||
///
|
|
||||||
/// let client = Client::builder()
|
|
||||||
/// .pool_idle_timeout(Duration::from_secs(30))
|
|
||||||
/// .http2_only(true)
|
|
||||||
/// .build_http();
|
|
||||||
/// # let infer: Client<_, hyper::Body> = client;
|
|
||||||
/// # drop(infer);
|
|
||||||
/// # }
|
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
|
||||||
#[inline]
|
#[inline]
|
||||||
pub fn builder() -> Builder {
|
pub fn builder() -> Builder {
|
||||||
Builder::default()
|
Builder::default()
|
||||||
@@ -113,20 +73,6 @@ where
|
|||||||
/// This requires that the `HttpBody` type have a `Default` implementation.
|
/// This requires that the `HttpBody` type have a `Default` implementation.
|
||||||
/// It *should* return an "empty" version of itself, such that
|
/// It *should* return an "empty" version of itself, such that
|
||||||
/// `HttpBody::is_end_stream` is `true`.
|
/// `HttpBody::is_end_stream` is `true`.
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # #[cfg(feature = "runtime")]
|
|
||||||
/// # fn run () {
|
|
||||||
/// use hyper::{Client, Uri};
|
|
||||||
///
|
|
||||||
/// let client = Client::new();
|
|
||||||
///
|
|
||||||
/// let future = client.get(Uri::from_static("http://httpbin.org/ip"));
|
|
||||||
/// # }
|
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
|
||||||
pub fn get(&self, uri: Uri) -> ResponseFuture
|
pub fn get(&self, uri: Uri) -> ResponseFuture
|
||||||
where
|
where
|
||||||
B: Default,
|
B: Default,
|
||||||
@@ -142,26 +88,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Send a constructed `Request` using this `Client`.
|
/// Send a constructed `Request` using this `Client`.
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # #[cfg(feature = "runtime")]
|
|
||||||
/// # fn run () {
|
|
||||||
/// use hyper::{Body, Method, Client, Request};
|
|
||||||
///
|
|
||||||
/// let client = Client::new();
|
|
||||||
///
|
|
||||||
/// let req = Request::builder()
|
|
||||||
/// .method(Method::POST)
|
|
||||||
/// .uri("http://httpbin.org/post")
|
|
||||||
/// .body(Body::from("Hallo!"))
|
|
||||||
/// .expect("request builder");
|
|
||||||
///
|
|
||||||
/// let future = client.request(req);
|
|
||||||
/// # }
|
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
|
||||||
pub fn request(&self, mut req: Request<B>) -> ResponseFuture {
|
pub fn request(&self, mut req: Request<B>) -> ResponseFuture {
|
||||||
let is_http_connect = req.method() == Method::CONNECT;
|
let is_http_connect = req.method() == Method::CONNECT;
|
||||||
match req.version() {
|
match req.version() {
|
||||||
@@ -586,7 +512,7 @@ impl ResponseFuture {
|
|||||||
F: Future<Output = crate::Result<Response<Body>>> + Send + 'static,
|
F: Future<Output = crate::Result<Response<Body>>> + Send + 'static,
|
||||||
{
|
{
|
||||||
Self {
|
Self {
|
||||||
inner: SyncWrapper::new(Box::pin(value))
|
inner: SyncWrapper::new(Box::pin(value)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -872,24 +798,6 @@ fn is_schema_secure(uri: &Uri) -> bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// A builder to configure a new [`Client`](Client).
|
/// A builder to configure a new [`Client`](Client).
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # #[cfg(feature = "runtime")]
|
|
||||||
/// # fn run () {
|
|
||||||
/// use std::time::Duration;
|
|
||||||
/// use hyper::Client;
|
|
||||||
///
|
|
||||||
/// let client = Client::builder()
|
|
||||||
/// .pool_idle_timeout(Duration::from_secs(30))
|
|
||||||
/// .http2_only(true)
|
|
||||||
/// .build_http();
|
|
||||||
/// # let infer: Client<_, hyper::Body> = client;
|
|
||||||
/// # drop(infer);
|
|
||||||
/// # }
|
|
||||||
/// # fn main() {}
|
|
||||||
/// ```
|
|
||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct Builder {
|
pub struct Builder {
|
||||||
@@ -1316,20 +1224,6 @@ impl Builder {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Builder a client with this configuration and the default `HttpConnector`.
|
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
pub fn build_http<B>(&self) -> Client<HttpConnector, B>
|
|
||||||
where
|
|
||||||
B: HttpBody + Send,
|
|
||||||
B::Data: Send,
|
|
||||||
{
|
|
||||||
let mut connector = HttpConnector::new();
|
|
||||||
if self.pool_config.is_enabled() {
|
|
||||||
connector.set_keepalive(self.pool_config.idle_timeout);
|
|
||||||
}
|
|
||||||
self.build(connector)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Combine the configuration of this builder with a connector to create a `Client`.
|
/// Combine the configuration of this builder with a connector to create a `Client`.
|
||||||
pub fn build<C, B>(&self, connector: C) -> Client<C, B>
|
pub fn build<C, B>(&self, connector: C) -> Client<C, B>
|
||||||
where
|
where
|
||||||
|
|||||||
@@ -22,18 +22,9 @@
|
|||||||
//! });
|
//! });
|
||||||
//! ```
|
//! ```
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::future::Future;
|
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs};
|
|
||||||
use std::pin::Pin;
|
|
||||||
use std::str::FromStr;
|
use std::str::FromStr;
|
||||||
use std::task::{self, Poll};
|
use std::{fmt, vec};
|
||||||
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.
|
/// A domain name to resolve into IP addresses.
|
||||||
#[derive(Clone, Hash, Eq, PartialEq)]
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
@@ -52,11 +43,6 @@ pub struct GaiAddrs {
|
|||||||
inner: SocketAddrs,
|
inner: SocketAddrs,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A future to resolve a name returned by `GaiResolver`.
|
|
||||||
pub struct GaiFuture {
|
|
||||||
inner: JoinHandle<Result<SocketAddrs, io::Error>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Name {
|
impl Name {
|
||||||
pub(super) fn new(host: Box<str>) -> Name {
|
pub(super) fn new(host: Box<str>) -> Name {
|
||||||
Name { host }
|
Name { host }
|
||||||
@@ -108,63 +94,12 @@ impl GaiResolver {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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 {
|
impl fmt::Debug for GaiResolver {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.pad("GaiResolver")
|
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 {
|
impl Iterator for GaiAddrs {
|
||||||
type Item = SocketAddr;
|
type Item = SocketAddr;
|
||||||
|
|
||||||
@@ -190,22 +125,6 @@ impl SocketAddrs {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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]
|
#[inline]
|
||||||
fn filter(self, predicate: impl FnMut(&SocketAddr) -> bool) -> SocketAddrs {
|
fn filter(self, predicate: impl FnMut(&SocketAddr) -> bool) -> SocketAddrs {
|
||||||
SocketAddrs::new(self.iter.filter(predicate).collect())
|
SocketAddrs::new(self.iter.filter(predicate).collect())
|
||||||
@@ -239,10 +158,6 @@ impl SocketAddrs {
|
|||||||
pub(super) fn is_empty(&self) -> bool {
|
pub(super) fn is_empty(&self) -> bool {
|
||||||
self.iter.as_slice().is_empty()
|
self.iter.as_slice().is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn len(&self) -> usize {
|
|
||||||
self.iter.as_slice().len()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Iterator for SocketAddrs {
|
impl Iterator for SocketAddrs {
|
||||||
@@ -318,12 +233,12 @@ impl Future for TokioThreadpoolGaiFuture {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
mod sealed {
|
mod sealed {
|
||||||
use super::{SocketAddr, Name};
|
use super::{Name, SocketAddr};
|
||||||
use crate::common::{task, Future, Poll};
|
use crate::common::{task, Future, Poll};
|
||||||
use tower_service::Service;
|
use tower_service::Service;
|
||||||
|
|
||||||
// "Trait alias" for `Service<Name, Response = Addrs>`
|
// "Trait alias" for `Service<Name, Response = Addrs>`
|
||||||
pub trait Resolve {
|
pub(crate) trait Resolve {
|
||||||
type Addrs: Iterator<Item = SocketAddr>;
|
type Addrs: Iterator<Item = SocketAddr>;
|
||||||
type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
|
type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
|
||||||
type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
|
type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
|
||||||
@@ -352,14 +267,6 @@ mod sealed {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
|
|||||||
@@ -1,23 +1,9 @@
|
|||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::future::Future;
|
|
||||||
use std::io;
|
|
||||||
use std::marker::PhantomData;
|
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
use std::pin::Pin;
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::{self, Poll};
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use futures_util::future::Either;
|
|
||||||
use http::uri::{Scheme, Uri};
|
|
||||||
use pin_project_lite::pin_project;
|
|
||||||
use tokio::net::{TcpSocket, TcpStream};
|
|
||||||
use tokio::time::Sleep;
|
|
||||||
use tracing::{debug, trace, warn};
|
|
||||||
|
|
||||||
use super::dns::{self, resolve, GaiResolver, Resolve};
|
|
||||||
use super::{Connected, Connection};
|
|
||||||
//#[cfg(feature = "runtime")] use super::dns::TokioThreadpoolGaiResolver;
|
//#[cfg(feature = "runtime")] use super::dns::TokioThreadpoolGaiResolver;
|
||||||
|
|
||||||
/// A connector for the `http` scheme.
|
/// A connector for the `http` scheme.
|
||||||
@@ -28,36 +14,13 @@ use super::{Connected, Connection};
|
|||||||
///
|
///
|
||||||
/// Sets the [`HttpInfo`](HttpInfo) value on responses, which includes
|
/// Sets the [`HttpInfo`](HttpInfo) value on responses, which includes
|
||||||
/// transport information such as the remote socket address used.
|
/// transport information such as the remote socket address used.
|
||||||
#[cfg_attr(docsrs, doc(cfg(feature = "tcp")))]
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
pub struct HttpConnector<R = GaiResolver> {
|
pub struct HttpConnector {
|
||||||
config: Arc<Config>,
|
config: Arc<Config>,
|
||||||
resolver: R,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Extra information about the transport when an HttpConnector is used.
|
/// Extra information about the transport when an HttpConnector is used.
|
||||||
///
|
///
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # async fn doc() -> hyper::Result<()> {
|
|
||||||
/// use hyper::Uri;
|
|
||||||
/// use hyper::client::{Client, connect::HttpInfo};
|
|
||||||
///
|
|
||||||
/// let client = Client::new();
|
|
||||||
/// let uri = Uri::from_static("http://example.com");
|
|
||||||
///
|
|
||||||
/// let res = client.get(uri).await?;
|
|
||||||
/// res
|
|
||||||
/// .extensions()
|
|
||||||
/// .get::<HttpInfo>()
|
|
||||||
/// .map(|info| {
|
|
||||||
/// println!("remote addr = {}", info.remote_addr());
|
|
||||||
/// });
|
|
||||||
/// # Ok(())
|
|
||||||
/// # }
|
|
||||||
/// ```
|
|
||||||
///
|
|
||||||
/// # Note
|
/// # Note
|
||||||
///
|
///
|
||||||
/// If a different connector is used besides [`HttpConnector`](HttpConnector),
|
/// If a different connector is used besides [`HttpConnector`](HttpConnector),
|
||||||
@@ -88,7 +51,20 @@ struct Config {
|
|||||||
impl HttpConnector {
|
impl HttpConnector {
|
||||||
/// Construct a new HttpConnector.
|
/// Construct a new HttpConnector.
|
||||||
pub fn new() -> HttpConnector {
|
pub fn new() -> HttpConnector {
|
||||||
HttpConnector::new_with_resolver(GaiResolver::new())
|
HttpConnector {
|
||||||
|
config: Arc::new(Config {
|
||||||
|
connect_timeout: None,
|
||||||
|
enforce_http: true,
|
||||||
|
happy_eyeballs_timeout: Some(Duration::from_millis(300)),
|
||||||
|
keep_alive_timeout: None,
|
||||||
|
local_address_ipv4: None,
|
||||||
|
local_address_ipv6: None,
|
||||||
|
nodelay: false,
|
||||||
|
reuse_address: false,
|
||||||
|
send_buffer_size: None,
|
||||||
|
recv_buffer_size: None,
|
||||||
|
}),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -104,28 +80,7 @@ impl HttpConnector<TokioThreadpoolGaiResolver> {
|
|||||||
}
|
}
|
||||||
*/
|
*/
|
||||||
|
|
||||||
impl<R> HttpConnector<R> {
|
impl HttpConnector {
|
||||||
/// Construct a new HttpConnector.
|
|
||||||
///
|
|
||||||
/// Takes a [`Resolver`](crate::client::connect::dns#resolvers-are-services) to handle DNS lookups.
|
|
||||||
pub fn new_with_resolver(resolver: R) -> HttpConnector<R> {
|
|
||||||
HttpConnector {
|
|
||||||
config: Arc::new(Config {
|
|
||||||
connect_timeout: None,
|
|
||||||
enforce_http: true,
|
|
||||||
happy_eyeballs_timeout: Some(Duration::from_millis(300)),
|
|
||||||
keep_alive_timeout: None,
|
|
||||||
local_address_ipv4: None,
|
|
||||||
local_address_ipv6: None,
|
|
||||||
nodelay: false,
|
|
||||||
reuse_address: false,
|
|
||||||
send_buffer_size: None,
|
|
||||||
recv_buffer_size: None,
|
|
||||||
}),
|
|
||||||
resolver,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Option to enforce all `Uri`s have the `http` scheme.
|
/// Option to enforce all `Uri`s have the `http` scheme.
|
||||||
///
|
///
|
||||||
/// Enabled by default.
|
/// Enabled by default.
|
||||||
@@ -240,135 +195,13 @@ impl<R> HttpConnector<R> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static INVALID_NOT_HTTP: &str = "invalid URL, scheme is not http";
|
|
||||||
static INVALID_MISSING_SCHEME: &str = "invalid URL, scheme is missing";
|
|
||||||
static INVALID_MISSING_HOST: &str = "invalid URL, host is missing";
|
|
||||||
|
|
||||||
// R: Debug required for now to allow adding it to debug output later...
|
// R: Debug required for now to allow adding it to debug output later...
|
||||||
impl<R: fmt::Debug> fmt::Debug for HttpConnector<R> {
|
impl fmt::Debug for HttpConnector {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
f.debug_struct("HttpConnector").finish()
|
f.debug_struct("HttpConnector").finish()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<R> tower_service::Service<Uri> for HttpConnector<R>
|
|
||||||
where
|
|
||||||
R: Resolve + Clone + Send + Sync + 'static,
|
|
||||||
R::Future: Send,
|
|
||||||
{
|
|
||||||
type Response = TcpStream;
|
|
||||||
type Error = ConnectError;
|
|
||||||
type Future = HttpConnecting<R>;
|
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
|
||||||
ready!(self.resolver.poll_ready(cx)).map_err(ConnectError::dns)?;
|
|
||||||
Poll::Ready(Ok(()))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn call(&mut self, dst: Uri) -> Self::Future {
|
|
||||||
let mut self_ = self.clone();
|
|
||||||
HttpConnecting {
|
|
||||||
fut: Box::pin(async move { self_.call_async(dst).await }),
|
|
||||||
_marker: PhantomData,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_host_port<'u>(config: &Config, dst: &'u Uri) -> Result<(&'u str, u16), ConnectError> {
|
|
||||||
trace!(
|
|
||||||
"Http::connect; scheme={:?}, host={:?}, port={:?}",
|
|
||||||
dst.scheme(),
|
|
||||||
dst.host(),
|
|
||||||
dst.port(),
|
|
||||||
);
|
|
||||||
|
|
||||||
if config.enforce_http {
|
|
||||||
if dst.scheme() != Some(&Scheme::HTTP) {
|
|
||||||
return Err(ConnectError {
|
|
||||||
msg: INVALID_NOT_HTTP.into(),
|
|
||||||
cause: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if dst.scheme().is_none() {
|
|
||||||
return Err(ConnectError {
|
|
||||||
msg: INVALID_MISSING_SCHEME.into(),
|
|
||||||
cause: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
let host = match dst.host() {
|
|
||||||
Some(s) => s,
|
|
||||||
None => {
|
|
||||||
return Err(ConnectError {
|
|
||||||
msg: INVALID_MISSING_HOST.into(),
|
|
||||||
cause: None,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let port = match dst.port() {
|
|
||||||
Some(port) => port.as_u16(),
|
|
||||||
None => {
|
|
||||||
if dst.scheme() == Some(&Scheme::HTTPS) {
|
|
||||||
443
|
|
||||||
} else {
|
|
||||||
80
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok((host, port))
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<R> HttpConnector<R>
|
|
||||||
where
|
|
||||||
R: Resolve,
|
|
||||||
{
|
|
||||||
async fn call_async(&mut self, dst: Uri) -> Result<TcpStream, ConnectError> {
|
|
||||||
let config = &self.config;
|
|
||||||
|
|
||||||
let (host, port) = get_host_port(config, &dst)?;
|
|
||||||
let host = host.trim_start_matches('[').trim_end_matches(']');
|
|
||||||
|
|
||||||
// If the host is already an IP addr (v4 or v6),
|
|
||||||
// skip resolving the dns and start connecting right away.
|
|
||||||
let addrs = if let Some(addrs) = dns::SocketAddrs::try_parse(host, port) {
|
|
||||||
addrs
|
|
||||||
} else {
|
|
||||||
let addrs = resolve(&mut self.resolver, dns::Name::new(host.into()))
|
|
||||||
.await
|
|
||||||
.map_err(ConnectError::dns)?;
|
|
||||||
let addrs = addrs
|
|
||||||
.map(|mut addr| {
|
|
||||||
addr.set_port(port);
|
|
||||||
addr
|
|
||||||
})
|
|
||||||
.collect();
|
|
||||||
dns::SocketAddrs::new(addrs)
|
|
||||||
};
|
|
||||||
|
|
||||||
let c = ConnectingTcp::new(addrs, config);
|
|
||||||
|
|
||||||
let sock = c.connect().await?;
|
|
||||||
|
|
||||||
if let Err(e) = sock.set_nodelay(config.nodelay) {
|
|
||||||
warn!("tcp set_nodelay error: {}", e);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(sock)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Connection for TcpStream {
|
|
||||||
fn connected(&self) -> Connected {
|
|
||||||
let connected = Connected::new();
|
|
||||||
if let (Ok(remote_addr), Ok(local_addr)) = (self.peer_addr(), self.local_addr()) {
|
|
||||||
connected.extra(HttpInfo { remote_addr, local_addr })
|
|
||||||
} else {
|
|
||||||
connected
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl HttpInfo {
|
impl HttpInfo {
|
||||||
/// Get the remote address of the transport used.
|
/// Get the remote address of the transport used.
|
||||||
pub fn remote_addr(&self) -> SocketAddr {
|
pub fn remote_addr(&self) -> SocketAddr {
|
||||||
@@ -381,66 +214,12 @@ impl HttpInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pin_project! {
|
|
||||||
// Not publicly exported (so missing_docs doesn't trigger).
|
|
||||||
//
|
|
||||||
// We return this `Future` instead of the `Pin<Box<dyn Future>>` directly
|
|
||||||
// so that users don't rely on it fitting in a `Pin<Box<dyn Future>>` slot
|
|
||||||
// (and thus we can change the type in the future).
|
|
||||||
#[must_use = "futures do nothing unless polled"]
|
|
||||||
#[allow(missing_debug_implementations)]
|
|
||||||
pub struct HttpConnecting<R> {
|
|
||||||
#[pin]
|
|
||||||
fut: BoxConnecting,
|
|
||||||
_marker: PhantomData<R>,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type ConnectResult = Result<TcpStream, ConnectError>;
|
|
||||||
type BoxConnecting = Pin<Box<dyn Future<Output = ConnectResult> + Send>>;
|
|
||||||
|
|
||||||
impl<R: Resolve> Future for HttpConnecting<R> {
|
|
||||||
type Output = ConnectResult;
|
|
||||||
|
|
||||||
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
|
||||||
self.project().fut.poll(cx)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Not publicly exported (so missing_docs doesn't trigger).
|
// Not publicly exported (so missing_docs doesn't trigger).
|
||||||
pub struct ConnectError {
|
pub(crate) struct ConnectError {
|
||||||
msg: Box<str>,
|
msg: Box<str>,
|
||||||
cause: Option<Box<dyn StdError + Send + Sync>>,
|
cause: Option<Box<dyn StdError + Send + Sync>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ConnectError {
|
|
||||||
fn new<S, E>(msg: S, cause: E) -> ConnectError
|
|
||||||
where
|
|
||||||
S: Into<Box<str>>,
|
|
||||||
E: Into<Box<dyn StdError + Send + Sync>>,
|
|
||||||
{
|
|
||||||
ConnectError {
|
|
||||||
msg: msg.into(),
|
|
||||||
cause: Some(cause.into()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn dns<E>(cause: E) -> ConnectError
|
|
||||||
where
|
|
||||||
E: Into<Box<dyn StdError + Send + Sync>>,
|
|
||||||
{
|
|
||||||
ConnectError::new("dns error", cause)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn m<S, E>(msg: S) -> impl FnOnce(E) -> ConnectError
|
|
||||||
where
|
|
||||||
S: Into<Box<str>>,
|
|
||||||
E: Into<Box<dyn StdError + Send + Sync>>,
|
|
||||||
{
|
|
||||||
move |cause| ConnectError::new(msg, cause)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl fmt::Debug for ConnectError {
|
impl fmt::Debug for ConnectError {
|
||||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
if let Some(ref cause) = self.cause {
|
if let Some(ref cause) = self.cause {
|
||||||
@@ -471,537 +250,3 @@ impl StdError for ConnectError {
|
|||||||
self.cause.as_ref().map(|e| &**e as _)
|
self.cause.as_ref().map(|e| &**e as _)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct ConnectingTcp<'a> {
|
|
||||||
preferred: ConnectingTcpRemote,
|
|
||||||
fallback: Option<ConnectingTcpFallback>,
|
|
||||||
config: &'a Config,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> ConnectingTcp<'a> {
|
|
||||||
fn new(remote_addrs: dns::SocketAddrs, config: &'a Config) -> Self {
|
|
||||||
if let Some(fallback_timeout) = config.happy_eyeballs_timeout {
|
|
||||||
let (preferred_addrs, fallback_addrs) = remote_addrs
|
|
||||||
.split_by_preference(config.local_address_ipv4, config.local_address_ipv6);
|
|
||||||
if fallback_addrs.is_empty() {
|
|
||||||
return ConnectingTcp {
|
|
||||||
preferred: ConnectingTcpRemote::new(preferred_addrs, config.connect_timeout),
|
|
||||||
fallback: None,
|
|
||||||
config,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
ConnectingTcp {
|
|
||||||
preferred: ConnectingTcpRemote::new(preferred_addrs, config.connect_timeout),
|
|
||||||
fallback: Some(ConnectingTcpFallback {
|
|
||||||
delay: tokio::time::sleep(fallback_timeout),
|
|
||||||
remote: ConnectingTcpRemote::new(fallback_addrs, config.connect_timeout),
|
|
||||||
}),
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
ConnectingTcp {
|
|
||||||
preferred: ConnectingTcpRemote::new(remote_addrs, config.connect_timeout),
|
|
||||||
fallback: None,
|
|
||||||
config,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ConnectingTcpFallback {
|
|
||||||
delay: Sleep,
|
|
||||||
remote: ConnectingTcpRemote,
|
|
||||||
}
|
|
||||||
|
|
||||||
struct ConnectingTcpRemote {
|
|
||||||
addrs: dns::SocketAddrs,
|
|
||||||
connect_timeout: Option<Duration>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectingTcpRemote {
|
|
||||||
fn new(addrs: dns::SocketAddrs, connect_timeout: Option<Duration>) -> Self {
|
|
||||||
let connect_timeout = connect_timeout.map(|t| t / (addrs.len() as u32));
|
|
||||||
|
|
||||||
Self {
|
|
||||||
addrs,
|
|
||||||
connect_timeout,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectingTcpRemote {
|
|
||||||
async fn connect(&mut self, config: &Config) -> Result<TcpStream, ConnectError> {
|
|
||||||
let mut err = None;
|
|
||||||
for addr in &mut self.addrs {
|
|
||||||
debug!("connecting to {}", addr);
|
|
||||||
match connect(&addr, config, self.connect_timeout)?.await {
|
|
||||||
Ok(tcp) => {
|
|
||||||
debug!("connected to {}", addr);
|
|
||||||
return Ok(tcp);
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
trace!("connect error for {}: {:?}", addr, e);
|
|
||||||
err = Some(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
match err {
|
|
||||||
Some(e) => Err(e),
|
|
||||||
None => Err(ConnectError::new(
|
|
||||||
"tcp connect error",
|
|
||||||
std::io::Error::new(std::io::ErrorKind::NotConnected, "Network unreachable"),
|
|
||||||
)),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn bind_local_address(
|
|
||||||
socket: &socket2::Socket,
|
|
||||||
dst_addr: &SocketAddr,
|
|
||||||
local_addr_ipv4: &Option<Ipv4Addr>,
|
|
||||||
local_addr_ipv6: &Option<Ipv6Addr>,
|
|
||||||
) -> io::Result<()> {
|
|
||||||
match (*dst_addr, local_addr_ipv4, local_addr_ipv6) {
|
|
||||||
(SocketAddr::V4(_), Some(addr), _) => {
|
|
||||||
socket.bind(&SocketAddr::new(addr.clone().into(), 0).into())?;
|
|
||||||
}
|
|
||||||
(SocketAddr::V6(_), _, Some(addr)) => {
|
|
||||||
socket.bind(&SocketAddr::new(addr.clone().into(), 0).into())?;
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
if cfg!(windows) {
|
|
||||||
// Windows requires a socket be bound before calling connect
|
|
||||||
let any: SocketAddr = match *dst_addr {
|
|
||||||
SocketAddr::V4(_) => ([0, 0, 0, 0], 0).into(),
|
|
||||||
SocketAddr::V6(_) => ([0, 0, 0, 0, 0, 0, 0, 0], 0).into(),
|
|
||||||
};
|
|
||||||
socket.bind(&any.into())?;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn connect(
|
|
||||||
addr: &SocketAddr,
|
|
||||||
config: &Config,
|
|
||||||
connect_timeout: Option<Duration>,
|
|
||||||
) -> Result<impl Future<Output = Result<TcpStream, ConnectError>>, ConnectError> {
|
|
||||||
// TODO(eliza): if Tokio's `TcpSocket` gains support for setting the
|
|
||||||
// keepalive timeout, it would be nice to use that instead of socket2,
|
|
||||||
// and avoid the unsafe `into_raw_fd`/`from_raw_fd` dance...
|
|
||||||
use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type};
|
|
||||||
use std::convert::TryInto;
|
|
||||||
|
|
||||||
let domain = Domain::for_address(*addr);
|
|
||||||
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))
|
|
||||||
.map_err(ConnectError::m("tcp open error"))?;
|
|
||||||
|
|
||||||
// When constructing a Tokio `TcpSocket` from a raw fd/socket, the user is
|
|
||||||
// responsible for ensuring O_NONBLOCK is set.
|
|
||||||
socket
|
|
||||||
.set_nonblocking(true)
|
|
||||||
.map_err(ConnectError::m("tcp set_nonblocking error"))?;
|
|
||||||
|
|
||||||
if let Some(dur) = config.keep_alive_timeout {
|
|
||||||
let conf = TcpKeepalive::new().with_time(dur);
|
|
||||||
if let Err(e) = socket.set_tcp_keepalive(&conf) {
|
|
||||||
warn!("tcp set_keepalive error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bind_local_address(
|
|
||||||
&socket,
|
|
||||||
addr,
|
|
||||||
&config.local_address_ipv4,
|
|
||||||
&config.local_address_ipv6,
|
|
||||||
)
|
|
||||||
.map_err(ConnectError::m("tcp bind local error"))?;
|
|
||||||
|
|
||||||
#[cfg(unix)]
|
|
||||||
let socket = unsafe {
|
|
||||||
// Safety: `from_raw_fd` is only safe to call if ownership of the raw
|
|
||||||
// file descriptor is transferred. Since we call `into_raw_fd` on the
|
|
||||||
// socket2 socket, it gives up ownership of the fd and will not close
|
|
||||||
// it, so this is safe.
|
|
||||||
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
|
||||||
TcpSocket::from_raw_fd(socket.into_raw_fd())
|
|
||||||
};
|
|
||||||
#[cfg(windows)]
|
|
||||||
let socket = unsafe {
|
|
||||||
// Safety: `from_raw_socket` is only safe to call if ownership of the raw
|
|
||||||
// Windows SOCKET is transferred. Since we call `into_raw_socket` on the
|
|
||||||
// socket2 socket, it gives up ownership of the SOCKET and will not close
|
|
||||||
// it, so this is safe.
|
|
||||||
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
|
||||||
TcpSocket::from_raw_socket(socket.into_raw_socket())
|
|
||||||
};
|
|
||||||
|
|
||||||
if config.reuse_address {
|
|
||||||
if let Err(e) = socket.set_reuseaddr(true) {
|
|
||||||
warn!("tcp set_reuse_address error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(size) = config.send_buffer_size {
|
|
||||||
if let Err(e) = socket.set_send_buffer_size(size.try_into().unwrap_or(std::u32::MAX)) {
|
|
||||||
warn!("tcp set_buffer_size error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(size) = config.recv_buffer_size {
|
|
||||||
if let Err(e) = socket.set_recv_buffer_size(size.try_into().unwrap_or(std::u32::MAX)) {
|
|
||||||
warn!("tcp set_recv_buffer_size error: {}", e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let connect = socket.connect(*addr);
|
|
||||||
Ok(async move {
|
|
||||||
match connect_timeout {
|
|
||||||
Some(dur) => match tokio::time::timeout(dur, connect).await {
|
|
||||||
Ok(Ok(s)) => Ok(s),
|
|
||||||
Ok(Err(e)) => Err(e),
|
|
||||||
Err(e) => Err(io::Error::new(io::ErrorKind::TimedOut, e)),
|
|
||||||
},
|
|
||||||
None => connect.await,
|
|
||||||
}
|
|
||||||
.map_err(ConnectError::m("tcp connect error"))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ConnectingTcp<'_> {
|
|
||||||
async fn connect(mut self) -> Result<TcpStream, ConnectError> {
|
|
||||||
match self.fallback {
|
|
||||||
None => self.preferred.connect(self.config).await,
|
|
||||||
Some(mut fallback) => {
|
|
||||||
let preferred_fut = self.preferred.connect(self.config);
|
|
||||||
futures_util::pin_mut!(preferred_fut);
|
|
||||||
|
|
||||||
let fallback_fut = fallback.remote.connect(self.config);
|
|
||||||
futures_util::pin_mut!(fallback_fut);
|
|
||||||
|
|
||||||
let fallback_delay = fallback.delay;
|
|
||||||
futures_util::pin_mut!(fallback_delay);
|
|
||||||
|
|
||||||
let (result, future) =
|
|
||||||
match futures_util::future::select(preferred_fut, fallback_delay).await {
|
|
||||||
Either::Left((result, _fallback_delay)) => {
|
|
||||||
(result, Either::Right(fallback_fut))
|
|
||||||
}
|
|
||||||
Either::Right(((), preferred_fut)) => {
|
|
||||||
// Delay is done, start polling both the preferred and the fallback
|
|
||||||
futures_util::future::select(preferred_fut, fallback_fut)
|
|
||||||
.await
|
|
||||||
.factor_first()
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if result.is_err() {
|
|
||||||
// Fallback to the remaining future (could be preferred or fallback)
|
|
||||||
// if we get an error
|
|
||||||
future.await
|
|
||||||
} else {
|
|
||||||
result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use std::io;
|
|
||||||
|
|
||||||
use ::http::Uri;
|
|
||||||
|
|
||||||
use super::super::sealed::{Connect, ConnectSvc};
|
|
||||||
use super::{Config, ConnectError, HttpConnector};
|
|
||||||
|
|
||||||
async fn connect<C>(
|
|
||||||
connector: C,
|
|
||||||
dst: Uri,
|
|
||||||
) -> Result<<C::_Svc as ConnectSvc>::Connection, <C::_Svc as ConnectSvc>::Error>
|
|
||||||
where
|
|
||||||
C: Connect,
|
|
||||||
{
|
|
||||||
connector.connect(super::super::sealed::Internal, dst).await
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_errors_enforce_http() {
|
|
||||||
let dst = "https://example.domain/foo/bar?baz".parse().unwrap();
|
|
||||||
let connector = HttpConnector::new();
|
|
||||||
|
|
||||||
let err = connect(connector, dst).await.unwrap_err();
|
|
||||||
assert_eq!(&*err.msg, super::INVALID_NOT_HTTP);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
fn get_local_ips() -> (Option<std::net::Ipv4Addr>, Option<std::net::Ipv6Addr>) {
|
|
||||||
use std::net::{IpAddr, TcpListener};
|
|
||||||
|
|
||||||
let mut ip_v4 = None;
|
|
||||||
let mut ip_v6 = None;
|
|
||||||
|
|
||||||
let ips = pnet_datalink::interfaces()
|
|
||||||
.into_iter()
|
|
||||||
.flat_map(|i| i.ips.into_iter().map(|n| n.ip()));
|
|
||||||
|
|
||||||
for ip in ips {
|
|
||||||
match ip {
|
|
||||||
IpAddr::V4(ip) if TcpListener::bind((ip, 0)).is_ok() => ip_v4 = Some(ip),
|
|
||||||
IpAddr::V6(ip) if TcpListener::bind((ip, 0)).is_ok() => ip_v6 = Some(ip),
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
|
|
||||||
if ip_v4.is_some() && ip_v6.is_some() {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
(ip_v4, ip_v6)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[tokio::test]
|
|
||||||
async fn test_errors_missing_scheme() {
|
|
||||||
let dst = "example.domain".parse().unwrap();
|
|
||||||
let mut connector = HttpConnector::new();
|
|
||||||
connector.enforce_http(false);
|
|
||||||
|
|
||||||
let err = connect(connector, dst).await.unwrap_err();
|
|
||||||
assert_eq!(&*err.msg, super::INVALID_MISSING_SCHEME);
|
|
||||||
}
|
|
||||||
|
|
||||||
// NOTE: pnet crate that we use in this test doesn't compile on Windows
|
|
||||||
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
|
||||||
#[tokio::test]
|
|
||||||
async fn local_address() {
|
|
||||||
use std::net::{IpAddr, TcpListener};
|
|
||||||
let _ = pretty_env_logger::try_init();
|
|
||||||
|
|
||||||
let (bind_ip_v4, bind_ip_v6) = get_local_ips();
|
|
||||||
let server4 = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
||||||
let port = server4.local_addr().unwrap().port();
|
|
||||||
let server6 = TcpListener::bind(&format!("[::1]:{}", port)).unwrap();
|
|
||||||
|
|
||||||
let assert_client_ip = |dst: String, server: TcpListener, expected_ip: IpAddr| async move {
|
|
||||||
let mut connector = HttpConnector::new();
|
|
||||||
|
|
||||||
match (bind_ip_v4, bind_ip_v6) {
|
|
||||||
(Some(v4), Some(v6)) => connector.set_local_addresses(v4, v6),
|
|
||||||
(Some(v4), None) => connector.set_local_address(Some(v4.into())),
|
|
||||||
(None, Some(v6)) => connector.set_local_address(Some(v6.into())),
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
|
|
||||||
connect(connector, dst.parse().unwrap()).await.unwrap();
|
|
||||||
|
|
||||||
let (_, client_addr) = server.accept().unwrap();
|
|
||||||
|
|
||||||
assert_eq!(client_addr.ip(), expected_ip);
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(ip) = bind_ip_v4 {
|
|
||||||
assert_client_ip(format!("http://127.0.0.1:{}", port), server4, ip.into()).await;
|
|
||||||
}
|
|
||||||
|
|
||||||
if let Some(ip) = bind_ip_v6 {
|
|
||||||
assert_client_ip(format!("http://[::1]:{}", port), server6, ip.into()).await;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
#[cfg_attr(not(feature = "__internal_happy_eyeballs_tests"), ignore)]
|
|
||||||
fn client_happy_eyeballs() {
|
|
||||||
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, TcpListener};
|
|
||||||
use std::time::{Duration, Instant};
|
|
||||||
|
|
||||||
use super::dns;
|
|
||||||
use super::ConnectingTcp;
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
|
||||||
let server4 = TcpListener::bind("127.0.0.1:0").unwrap();
|
|
||||||
let addr = server4.local_addr().unwrap();
|
|
||||||
let _server6 = TcpListener::bind(&format!("[::1]:{}", addr.port())).unwrap();
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
|
||||||
.enable_all()
|
|
||||||
.build()
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
let local_timeout = Duration::default();
|
|
||||||
let unreachable_v4_timeout = measure_connect(unreachable_ipv4_addr()).1;
|
|
||||||
let unreachable_v6_timeout = measure_connect(unreachable_ipv6_addr()).1;
|
|
||||||
let fallback_timeout = std::cmp::max(unreachable_v4_timeout, unreachable_v6_timeout)
|
|
||||||
+ Duration::from_millis(250);
|
|
||||||
|
|
||||||
let scenarios = &[
|
|
||||||
// Fast primary, without fallback.
|
|
||||||
(&[local_ipv4_addr()][..], 4, local_timeout, false),
|
|
||||||
(&[local_ipv6_addr()][..], 6, local_timeout, false),
|
|
||||||
// Fast primary, with (unused) fallback.
|
|
||||||
(
|
|
||||||
&[local_ipv4_addr(), local_ipv6_addr()][..],
|
|
||||||
4,
|
|
||||||
local_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
&[local_ipv6_addr(), local_ipv4_addr()][..],
|
|
||||||
6,
|
|
||||||
local_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
// Unreachable + fast primary, without fallback.
|
|
||||||
(
|
|
||||||
&[unreachable_ipv4_addr(), local_ipv4_addr()][..],
|
|
||||||
4,
|
|
||||||
unreachable_v4_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
&[unreachable_ipv6_addr(), local_ipv6_addr()][..],
|
|
||||||
6,
|
|
||||||
unreachable_v6_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
// Unreachable + fast primary, with (unused) fallback.
|
|
||||||
(
|
|
||||||
&[
|
|
||||||
unreachable_ipv4_addr(),
|
|
||||||
local_ipv4_addr(),
|
|
||||||
local_ipv6_addr(),
|
|
||||||
][..],
|
|
||||||
4,
|
|
||||||
unreachable_v4_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
&[
|
|
||||||
unreachable_ipv6_addr(),
|
|
||||||
local_ipv6_addr(),
|
|
||||||
local_ipv4_addr(),
|
|
||||||
][..],
|
|
||||||
6,
|
|
||||||
unreachable_v6_timeout,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
// Slow primary, with (used) fallback.
|
|
||||||
(
|
|
||||||
&[slow_ipv4_addr(), local_ipv4_addr(), local_ipv6_addr()][..],
|
|
||||||
6,
|
|
||||||
fallback_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
&[slow_ipv6_addr(), local_ipv6_addr(), local_ipv4_addr()][..],
|
|
||||||
4,
|
|
||||||
fallback_timeout,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
// Slow primary, with (used) unreachable + fast fallback.
|
|
||||||
(
|
|
||||||
&[slow_ipv4_addr(), unreachable_ipv6_addr(), local_ipv6_addr()][..],
|
|
||||||
6,
|
|
||||||
fallback_timeout + unreachable_v6_timeout,
|
|
||||||
false,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
&[slow_ipv6_addr(), unreachable_ipv4_addr(), local_ipv4_addr()][..],
|
|
||||||
4,
|
|
||||||
fallback_timeout + unreachable_v4_timeout,
|
|
||||||
true,
|
|
||||||
),
|
|
||||||
];
|
|
||||||
|
|
||||||
// Scenarios for IPv6 -> IPv4 fallback require that host can access IPv6 network.
|
|
||||||
// Otherwise, connection to "slow" IPv6 address will error-out immediately.
|
|
||||||
let ipv6_accessible = measure_connect(slow_ipv6_addr()).0;
|
|
||||||
|
|
||||||
for &(hosts, family, timeout, needs_ipv6_access) in scenarios {
|
|
||||||
if needs_ipv6_access && !ipv6_accessible {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
let (start, stream) = rt
|
|
||||||
.block_on(async move {
|
|
||||||
let addrs = hosts
|
|
||||||
.iter()
|
|
||||||
.map(|host| (host.clone(), addr.port()).into())
|
|
||||||
.collect();
|
|
||||||
let cfg = Config {
|
|
||||||
local_address_ipv4: None,
|
|
||||||
local_address_ipv6: None,
|
|
||||||
connect_timeout: None,
|
|
||||||
keep_alive_timeout: None,
|
|
||||||
happy_eyeballs_timeout: Some(fallback_timeout),
|
|
||||||
nodelay: false,
|
|
||||||
reuse_address: false,
|
|
||||||
enforce_http: false,
|
|
||||||
send_buffer_size: None,
|
|
||||||
recv_buffer_size: None,
|
|
||||||
};
|
|
||||||
let connecting_tcp = ConnectingTcp::new(dns::SocketAddrs::new(addrs), &cfg);
|
|
||||||
let start = Instant::now();
|
|
||||||
Ok::<_, ConnectError>((start, ConnectingTcp::connect(connecting_tcp).await?))
|
|
||||||
})
|
|
||||||
.unwrap();
|
|
||||||
let res = if stream.peer_addr().unwrap().is_ipv4() {
|
|
||||||
4
|
|
||||||
} else {
|
|
||||||
6
|
|
||||||
};
|
|
||||||
let duration = start.elapsed();
|
|
||||||
|
|
||||||
// Allow actual duration to be +/- 150ms off.
|
|
||||||
let min_duration = if timeout >= Duration::from_millis(150) {
|
|
||||||
timeout - Duration::from_millis(150)
|
|
||||||
} else {
|
|
||||||
Duration::default()
|
|
||||||
};
|
|
||||||
let max_duration = timeout + Duration::from_millis(150);
|
|
||||||
|
|
||||||
assert_eq!(res, family);
|
|
||||||
assert!(duration >= min_duration);
|
|
||||||
assert!(duration <= max_duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn local_ipv4_addr() -> IpAddr {
|
|
||||||
Ipv4Addr::new(127, 0, 0, 1).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn local_ipv6_addr() -> IpAddr {
|
|
||||||
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unreachable_ipv4_addr() -> IpAddr {
|
|
||||||
Ipv4Addr::new(127, 0, 0, 2).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn unreachable_ipv6_addr() -> IpAddr {
|
|
||||||
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slow_ipv4_addr() -> IpAddr {
|
|
||||||
// RFC 6890 reserved IPv4 address.
|
|
||||||
Ipv4Addr::new(198, 18, 0, 25).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn slow_ipv6_addr() -> IpAddr {
|
|
||||||
// RFC 6890 reserved IPv6 address.
|
|
||||||
Ipv6Addr::new(2001, 2, 0, 0, 0, 0, 0, 254).into()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn measure_connect(addr: IpAddr) -> (bool, Duration) {
|
|
||||||
let start = Instant::now();
|
|
||||||
let result =
|
|
||||||
std::net::TcpStream::connect_timeout(&(addr, 80).into(), Duration::from_secs(1));
|
|
||||||
|
|
||||||
let reachable = result.is_ok() || result.unwrap_err().kind() == io::ErrorKind::TimedOut;
|
|
||||||
let duration = start.elapsed();
|
|
||||||
(reachable, duration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -55,42 +55,19 @@
|
|||||||
//! # }
|
//! # }
|
||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! 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
|
//! [`Uri`]: ::http::Uri
|
||||||
//! [`AsyncRead`]: tokio::io::AsyncRead
|
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||||
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||||
//! [`Connection`]: Connection
|
//! [`Connection`]: Connection
|
||||||
|
//! [`Service`]: crate::service::Service
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
|
|
||||||
use ::http::Extensions;
|
use ::http::Extensions;
|
||||||
|
|
||||||
cfg_feature! {
|
pub use self::http::{HttpConnector, HttpInfo};
|
||||||
#![feature = "tcp"]
|
|
||||||
|
|
||||||
pub use self::http::{HttpConnector, HttpInfo};
|
pub mod dns;
|
||||||
|
mod http;
|
||||||
pub mod dns;
|
|
||||||
mod http;
|
|
||||||
}
|
|
||||||
|
|
||||||
cfg_feature! {
|
cfg_feature! {
|
||||||
#![any(feature = "http1", feature = "http2")]
|
#![any(feature = "http1", feature = "http2")]
|
||||||
|
|||||||
@@ -301,6 +301,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn drop_receiver_sends_cancel_errors() {
|
async fn drop_receiver_sends_cancel_errors() {
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
@@ -323,6 +324,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(not(miri))]
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn sender_checks_for_want_on_send() {
|
async fn sender_checks_for_want_on_send() {
|
||||||
let (mut tx, mut rx) = channel::<Custom, ()>();
|
let (mut tx, mut rx) = channel::<Custom, ()>();
|
||||||
@@ -363,7 +365,6 @@ mod tests {
|
|||||||
use crate::{Body, Request, Response};
|
use crate::{Body, Request, Response};
|
||||||
|
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>();
|
let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>();
|
||||||
@@ -386,7 +387,6 @@ mod tests {
|
|||||||
#[bench]
|
#[bench]
|
||||||
fn giver_queue_not_ready(b: &mut test::Bencher) {
|
fn giver_queue_not_ready(b: &mut test::Bencher) {
|
||||||
let rt = tokio::runtime::Builder::new_current_thread()
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
.enable_all()
|
|
||||||
.build()
|
.build()
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let (_tx, mut rx) = channel::<i32, ()>();
|
let (_tx, mut rx) = channel::<i32, ()>();
|
||||||
|
|||||||
@@ -26,30 +26,6 @@
|
|||||||
//! For a small example program simply fetching a URL, take a look at the
|
//! 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).
|
//! [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;
|
pub mod connect;
|
||||||
#[cfg(all(test, feature = "runtime"))]
|
#[cfg(all(test, feature = "runtime"))]
|
||||||
|
|||||||
@@ -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`
|
// FIXME: re-implement tests with `async/await`
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@@ -44,13 +44,13 @@ impl Exec {
|
|||||||
{
|
{
|
||||||
match *self {
|
match *self {
|
||||||
Exec::Default => {
|
Exec::Default => {
|
||||||
#[cfg(feature = "tcp")]
|
#[cfg(feature = "runtime")]
|
||||||
{
|
{
|
||||||
tokio::task::spawn(fut);
|
tokio::task::spawn(fut);
|
||||||
}
|
}
|
||||||
#[cfg(not(feature = "tcp"))]
|
|
||||||
|
#[cfg(not(feature = "runtime"))]
|
||||||
{
|
{
|
||||||
// If no runtime, we need an executor!
|
|
||||||
panic!("executor must be set")
|
panic!("executor must be set")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -51,7 +51,6 @@
|
|||||||
//! - `server`: Enables the HTTP `server`.
|
//! - `server`: Enables the HTTP `server`.
|
||||||
//! - `runtime`: Enables convenient integration with `tokio`, providing
|
//! - `runtime`: Enables convenient integration with `tokio`, providing
|
||||||
//! connectors and acceptors for TCP, and a default executor.
|
//! connectors and acceptors for TCP, and a default executor.
|
||||||
//! - `tcp`: Enables convenient implementations over TCP (using tokio).
|
|
||||||
//!
|
//!
|
||||||
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
|
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
|
||||||
|
|
||||||
|
|||||||
@@ -324,7 +324,7 @@ impl<E> Http<E> {
|
|||||||
self
|
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.
|
/// transmit the entire header within this time, the connection is closed.
|
||||||
///
|
///
|
||||||
/// Default is None.
|
/// Default is None.
|
||||||
|
|||||||
@@ -15,143 +15,8 @@
|
|||||||
//! be executed to start serving requests.
|
//! be executed to start serving requests.
|
||||||
//!
|
//!
|
||||||
//! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default.
|
//! [`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};
|
|
||||||
//! # #[cfg(feature = "runtime")]
|
|
||||||
//! use tokio::net::TcpStream;
|
|
||||||
//!
|
|
||||||
//! #[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: &TcpStream| {
|
|
||||||
//! // 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.peer_addr().unwrap();
|
|
||||||
//!
|
|
||||||
//! // 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;
|
pub mod accept;
|
||||||
pub mod conn;
|
pub mod conn;
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
mod tcp;
|
|
||||||
|
|
||||||
pub use self::server::Server;
|
pub use self::server::Server;
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use std::error::Error as StdError;
|
use std::error::Error as StdError;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
#[cfg(feature = "tcp")]
|
#[cfg(feature = "http1")]
|
||||||
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
|
||||||
#[cfg(any(feature = "tcp", feature = "http1"))]
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use pin_project_lite::pin_project;
|
use pin_project_lite::pin_project;
|
||||||
@@ -10,8 +8,6 @@ use tokio::io::{AsyncRead, AsyncWrite};
|
|||||||
use tracing::trace;
|
use tracing::trace;
|
||||||
|
|
||||||
use super::accept::Accept;
|
use super::accept::Accept;
|
||||||
#[cfg(all(feature = "tcp"))]
|
|
||||||
use super::tcp::AddrIncoming;
|
|
||||||
use crate::body::{Body, HttpBody};
|
use crate::body::{Body, HttpBody};
|
||||||
use crate::common::exec::Exec;
|
use crate::common::exec::Exec;
|
||||||
use crate::common::exec::{ConnStreamExec, NewSvcExec};
|
use crate::common::exec::{ConnStreamExec, NewSvcExec};
|
||||||
@@ -60,48 +56,6 @@ impl<I> Server<I, ()> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
#[cfg_attr(
|
|
||||||
docsrs,
|
|
||||||
doc(cfg(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 = "tcp")]
|
|
||||||
#[cfg_attr(
|
|
||||||
docsrs,
|
|
||||||
doc(cfg(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.incoming.local_addr()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(docsrs, doc(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>
|
impl<I, IO, IE, S, E, B> Server<I, S, E>
|
||||||
where
|
where
|
||||||
@@ -116,40 +70,6 @@ where
|
|||||||
{
|
{
|
||||||
/// Prepares a server to handle graceful shutdown when the provided future
|
/// Prepares a server to handle graceful shutdown when the provided future
|
||||||
/// completes.
|
/// 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>
|
pub fn with_graceful_shutdown<F>(self, signal: F) -> Graceful<I, S, F, E>
|
||||||
where
|
where
|
||||||
F: Future<Output = ()>,
|
F: Future<Output = ()>,
|
||||||
@@ -237,8 +157,6 @@ impl<I: fmt::Debug, S: fmt::Debug> fmt::Debug for Server<I, S> {
|
|||||||
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
impl<I, E> Builder<I, E> {
|
impl<I, E> Builder<I, E> {
|
||||||
/// Start a new builder, wrapping an incoming stream and low-level options.
|
/// 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 {
|
pub fn new(incoming: I, protocol: Http_<E>) -> Self {
|
||||||
Builder { incoming, protocol }
|
Builder { incoming, protocol }
|
||||||
}
|
}
|
||||||
@@ -504,35 +422,6 @@ impl<I, E> Builder<I, E> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Consume this `Builder`, creating a [`Server`](Server).
|
/// 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, make_service: S) -> Server<I, S, E>
|
pub fn serve<S, B>(self, make_service: S) -> Server<I, S, E>
|
||||||
where
|
where
|
||||||
I: Accept,
|
I: Accept,
|
||||||
@@ -553,49 +442,6 @@ impl<I, E> Builder<I, E> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(feature = "tcp")]
|
|
||||||
#[cfg_attr(
|
|
||||||
docsrs,
|
|
||||||
doc(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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Used by `Server` to optionally watch a `Connection` future.
|
// Used by `Server` to optionally watch a `Connection` future.
|
||||||
//
|
//
|
||||||
// The regular `hyper::Server` just uses a `NoopWatcher`, which does
|
// The regular `hyper::Server` just uses a `NoopWatcher`, which does
|
||||||
|
|||||||
@@ -1,192 +0,0 @@
|
|||||||
use std::fmt;
|
|
||||||
use std::io;
|
|
||||||
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
|
||||||
use std::time::Duration;
|
|
||||||
|
|
||||||
use tokio::net::{TcpListener, TcpStream};
|
|
||||||
use tokio::time::Sleep;
|
|
||||||
use tracing::{debug, error, trace};
|
|
||||||
|
|
||||||
use crate::common::{task, Future, Pin, Poll};
|
|
||||||
|
|
||||||
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<TcpStream>> {
|
|
||||||
// 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, _)) => {
|
|
||||||
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(socket));
|
|
||||||
}
|
|
||||||
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 = TcpStream;
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -100,41 +100,6 @@ where
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a `MakeService` from a function.
|
/// Create a `MakeService` from a function.
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```
|
|
||||||
/// # #[cfg(feature = "runtime")]
|
|
||||||
/// # async fn run() {
|
|
||||||
/// use std::convert::Infallible;
|
|
||||||
/// use hyper::{Body, Request, Response, Server};
|
|
||||||
/// use tokio::net::TcpStream;
|
|
||||||
/// use hyper::service::{make_service_fn, service_fn};
|
|
||||||
///
|
|
||||||
/// let addr = ([127, 0, 0, 1], 3000).into();
|
|
||||||
///
|
|
||||||
/// let make_svc = make_service_fn(|socket: &TcpStream| {
|
|
||||||
/// let remote_addr = socket.peer_addr().unwrap();
|
|
||||||
/// 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>
|
pub fn make_service_fn<F, Target, Ret>(f: F) -> MakeServiceFn<F>
|
||||||
where
|
where
|
||||||
F: FnMut(&Target) -> Ret,
|
F: FnMut(&Target) -> Ret,
|
||||||
|
|||||||
323
tests/client.rs
323
tests/client.rs
@@ -5,6 +5,7 @@
|
|||||||
extern crate matches;
|
extern crate matches;
|
||||||
|
|
||||||
use std::convert::Infallible;
|
use std::convert::Infallible;
|
||||||
|
use std::fmt;
|
||||||
use std::io::{Read, Write};
|
use std::io::{Read, Write};
|
||||||
use std::net::{SocketAddr, TcpListener};
|
use std::net::{SocketAddr, TcpListener};
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
@@ -12,9 +13,11 @@ use std::task::{Context, Poll};
|
|||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use http::uri::PathAndQuery;
|
||||||
use http_body_util::{BodyExt, StreamBody};
|
use http_body_util::{BodyExt, StreamBody};
|
||||||
use hyper::body::to_bytes as concat;
|
use hyper::body::to_bytes as concat;
|
||||||
use hyper::{Body, Client, Method, Request, StatusCode};
|
use hyper::header::HeaderValue;
|
||||||
|
use hyper::{Body, Method, Request, StatusCode, Uri, Version};
|
||||||
|
|
||||||
use bytes::Bytes;
|
use bytes::Bytes;
|
||||||
use futures_channel::oneshot;
|
use futures_channel::oneshot;
|
||||||
@@ -31,6 +34,71 @@ fn tcp_connect(addr: &SocketAddr) -> impl Future<Output = std::io::Result<TcpStr
|
|||||||
TcpStream::connect(*addr)
|
TcpStream::connect(*addr)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
struct HttpInfo {
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum Error {
|
||||||
|
Io(std::io::Error),
|
||||||
|
Hyper(hyper::Error),
|
||||||
|
AbsoluteUriRequired,
|
||||||
|
UnsupportedVersion,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Error {
|
||||||
|
fn is_incomplete_message(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Hyper(err) => err.is_incomplete_message(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parse(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Hyper(err) => err.is_parse(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parse_too_large(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Hyper(err) => err.is_parse_too_large(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_parse_status(&self) -> bool {
|
||||||
|
match self {
|
||||||
|
Self::Hyper(err) => err.is_parse_status(),
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, fmt: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
Self::Io(err) => err.fmt(fmt),
|
||||||
|
Self::Hyper(err) => err.fmt(fmt),
|
||||||
|
Self::AbsoluteUriRequired => write!(fmt, "client requires absolute-form URIs"),
|
||||||
|
Self::UnsupportedVersion => write!(fmt, "request has unsupported HTTP version"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<std::io::Error> for Error {
|
||||||
|
fn from(err: std::io::Error) -> Self {
|
||||||
|
Self::Io(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<hyper::Error> for Error {
|
||||||
|
fn from(err: hyper::Error) -> Self {
|
||||||
|
Self::Hyper(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
macro_rules! test {
|
macro_rules! test {
|
||||||
(
|
(
|
||||||
name: $name:ident,
|
name: $name:ident,
|
||||||
@@ -110,7 +178,7 @@ macro_rules! test {
|
|||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let rt = support::runtime();
|
let rt = support::runtime();
|
||||||
|
|
||||||
let err: ::hyper::Error = test! {
|
let err: Error = test! {
|
||||||
INNER;
|
INNER;
|
||||||
name: $name,
|
name: $name,
|
||||||
runtime: &rt,
|
runtime: &rt,
|
||||||
@@ -123,7 +191,7 @@ macro_rules! test {
|
|||||||
)*},
|
)*},
|
||||||
}.unwrap_err();
|
}.unwrap_err();
|
||||||
|
|
||||||
fn infer_closure<F: FnOnce(&::hyper::Error) -> bool>(f: F) -> F { f }
|
fn infer_closure<F: FnOnce(&Error) -> bool>(f: F) -> F { f }
|
||||||
|
|
||||||
let closure = infer_closure($err);
|
let closure = infer_closure($err);
|
||||||
if !closure(&err) {
|
if !closure(&err) {
|
||||||
@@ -151,22 +219,123 @@ macro_rules! test {
|
|||||||
let addr = server.local_addr().expect("local_addr");
|
let addr = server.local_addr().expect("local_addr");
|
||||||
let rt = $runtime;
|
let rt = $runtime;
|
||||||
|
|
||||||
let connector = ::hyper::client::HttpConnector::new();
|
|
||||||
let client = Client::builder()
|
|
||||||
$($(.$c_opt_prop($c_opt_val))*)?
|
|
||||||
.build(connector);
|
|
||||||
|
|
||||||
#[allow(unused_assignments, unused_mut)]
|
#[allow(unused_assignments, unused_mut)]
|
||||||
let mut body = BodyExt::boxed(http_body_util::Empty::<bytes::Bytes>::new());
|
let mut body = BodyExt::boxed(http_body_util::Empty::<bytes::Bytes>::new());
|
||||||
let mut req_builder = Request::builder();
|
let mut req_builder = Request::builder();
|
||||||
$(
|
$(
|
||||||
test!(@client_request; req_builder, body, addr, $c_req_prop: $c_req_val);
|
test!(@client_request; req_builder, body, addr, $c_req_prop: $c_req_val);
|
||||||
)*
|
)*
|
||||||
let req = req_builder
|
let mut req = req_builder
|
||||||
.body(body)
|
.body(body)
|
||||||
.expect("request builder");
|
.expect("request builder");
|
||||||
|
|
||||||
let res = client.request(req);
|
let res = async move {
|
||||||
|
// Wrapper around hyper::client::conn::Builder with set_host field to mimic
|
||||||
|
// hyper::client::Builder.
|
||||||
|
struct Builder {
|
||||||
|
inner: hyper::client::conn::Builder,
|
||||||
|
set_host: bool,
|
||||||
|
http09_responses: bool,
|
||||||
|
http2_only: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Builder {
|
||||||
|
fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
inner: hyper::client::conn::Builder::new(),
|
||||||
|
set_host: true,
|
||||||
|
http09_responses: false,
|
||||||
|
http2_only: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn set_host(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.set_host = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn http09_responses(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.http09_responses = val;
|
||||||
|
self.inner.http09_responses(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused)]
|
||||||
|
fn http2_only(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.http2_only = val;
|
||||||
|
self.inner.http2_only(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::Deref for Builder {
|
||||||
|
type Target = hyper::client::conn::Builder;
|
||||||
|
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::ops::DerefMut for Builder {
|
||||||
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||||
|
&mut self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(unused_mut)]
|
||||||
|
let mut builder = Builder::new();
|
||||||
|
$(builder$(.$c_opt_prop($c_opt_val))*;)?
|
||||||
|
|
||||||
|
|
||||||
|
if req.version() == Version::HTTP_09 && !builder.http09_responses {
|
||||||
|
return Err(Error::UnsupportedVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
if req.version() == Version::HTTP_2 && !builder.http2_only {
|
||||||
|
return Err(Error::UnsupportedVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
let host = req.uri().host().ok_or(Error::AbsoluteUriRequired)?;
|
||||||
|
let port = req.uri().port_u16().unwrap_or(80);
|
||||||
|
|
||||||
|
let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
|
||||||
|
|
||||||
|
let extra = HttpInfo {
|
||||||
|
remote_addr: stream.peer_addr().unwrap(),
|
||||||
|
};
|
||||||
|
|
||||||
|
if builder.set_host {
|
||||||
|
let host = req.uri().host().expect("no host in uri");
|
||||||
|
let port = req.uri().port_u16().expect("no port in uri");
|
||||||
|
|
||||||
|
let host = format!("{}:{}", host, port);
|
||||||
|
|
||||||
|
req.headers_mut().append("Host", HeaderValue::from_str(&host).unwrap());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (mut sender, conn) = builder.handshake(stream).await?;
|
||||||
|
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
if let Err(err) = conn.await {
|
||||||
|
panic!("{}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut builder = Uri::builder();
|
||||||
|
if req.method() == Method::CONNECT {
|
||||||
|
builder = builder.path_and_query(format!("{}:{}", req.uri().host().unwrap(), req.uri().port_u16().unwrap()));
|
||||||
|
} else {
|
||||||
|
builder = builder.path_and_query(req.uri().path_and_query().cloned().unwrap_or(PathAndQuery::from_static("/")));
|
||||||
|
}
|
||||||
|
*req.uri_mut() = builder.build().unwrap();
|
||||||
|
|
||||||
|
let mut resp = sender.send_request(req).await?;
|
||||||
|
|
||||||
|
resp.extensions_mut().insert(extra);
|
||||||
|
Ok(resp)
|
||||||
|
};
|
||||||
|
|
||||||
let (tx, rx) = oneshot::channel();
|
let (tx, rx) = oneshot::channel();
|
||||||
|
|
||||||
@@ -188,7 +357,7 @@ macro_rules! test {
|
|||||||
assert_eq!(s(&buf[..n]), expected);
|
assert_eq!(s(&buf[..n]), expected);
|
||||||
|
|
||||||
inc.write_all($server_reply.as_ref()).expect("write_all");
|
inc.write_all($server_reply.as_ref()).expect("write_all");
|
||||||
let _ = tx.send(Ok::<_, hyper::Error>(()));
|
let _ = tx.send(Ok::<_, Error>(()));
|
||||||
}).expect("thread spawn");
|
}).expect("thread spawn");
|
||||||
|
|
||||||
let rx = rx.expect("thread panicked");
|
let rx = rx.expect("thread panicked");
|
||||||
@@ -197,10 +366,10 @@ macro_rules! test {
|
|||||||
// Always check that HttpConnector has set the "extra" info...
|
// Always check that HttpConnector has set the "extra" info...
|
||||||
let extra = resp
|
let extra = resp
|
||||||
.extensions_mut()
|
.extensions_mut()
|
||||||
.remove::<::hyper::client::connect::HttpInfo>()
|
.remove::<HttpInfo>()
|
||||||
.expect("HttpConnector should set HttpInfo");
|
.expect("HttpConnector should set HttpInfo");
|
||||||
|
|
||||||
assert_eq!(extra.remote_addr(), addr, "HttpInfo should have server addr");
|
assert_eq!(extra.remote_addr, addr, "HttpInfo should have server addr");
|
||||||
|
|
||||||
resp
|
resp
|
||||||
})
|
})
|
||||||
@@ -1174,7 +1343,7 @@ mod dispatch_impl {
|
|||||||
|
|
||||||
use super::support;
|
use super::support;
|
||||||
use hyper::body::HttpBody;
|
use hyper::body::HttpBody;
|
||||||
use hyper::client::connect::{Connected, Connection, HttpConnector};
|
use hyper::client::connect::{Connected, Connection};
|
||||||
use hyper::Client;
|
use hyper::Client;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -1186,10 +1355,7 @@ mod dispatch_impl {
|
|||||||
let addr = server.local_addr().unwrap();
|
let addr = server.local_addr().unwrap();
|
||||||
let rt = support::runtime();
|
let rt = support::runtime();
|
||||||
let (closes_tx, closes) = mpsc::channel(10);
|
let (closes_tx, closes) = mpsc::channel(10);
|
||||||
let client = Client::builder().build(DebugConnector::with_http_and_closes(
|
let client = Client::builder().build(DebugConnector::with_closes(closes_tx));
|
||||||
HttpConnector::new(),
|
|
||||||
closes_tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
let (tx1, rx1) = oneshot::channel();
|
let (tx1, rx1) = oneshot::channel();
|
||||||
|
|
||||||
@@ -1259,10 +1425,7 @@ mod dispatch_impl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let res = {
|
let res = {
|
||||||
let client = Client::builder().build(DebugConnector::with_http_and_closes(
|
let client = Client::builder().build(DebugConnector::with_closes(closes_tx));
|
||||||
HttpConnector::new(),
|
|
||||||
closes_tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.uri(&*format!("http://{}/a", addr))
|
.uri(&*format!("http://{}/a", addr))
|
||||||
@@ -1322,10 +1485,7 @@ mod dispatch_impl {
|
|||||||
support::runtime().block_on(client_drop_rx.into_future())
|
support::runtime().block_on(client_drop_rx.into_future())
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = Client::builder().build(DebugConnector::with_http_and_closes(
|
let client = Client::builder().build(DebugConnector::with_closes(closes_tx));
|
||||||
HttpConnector::new(),
|
|
||||||
closes_tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.uri(&*format!("http://{}/a", addr))
|
.uri(&*format!("http://{}/a", addr))
|
||||||
@@ -1385,10 +1545,7 @@ mod dispatch_impl {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let res = {
|
let res = {
|
||||||
let client = Client::builder().build(DebugConnector::with_http_and_closes(
|
let client = Client::builder().build(DebugConnector::with_closes(closes_tx));
|
||||||
HttpConnector::new(),
|
|
||||||
closes_tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.uri(&*format!("http://{}/a", addr))
|
.uri(&*format!("http://{}/a", addr))
|
||||||
@@ -1438,10 +1595,7 @@ mod dispatch_impl {
|
|||||||
|
|
||||||
let rx = rx1.expect("thread panicked");
|
let rx = rx1.expect("thread panicked");
|
||||||
let res = {
|
let res = {
|
||||||
let client = Client::builder().build(DebugConnector::with_http_and_closes(
|
let client = Client::builder().build(DebugConnector::with_closes(closes_tx));
|
||||||
HttpConnector::new(),
|
|
||||||
closes_tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.uri(&*format!("http://{}/a", addr))
|
.uri(&*format!("http://{}/a", addr))
|
||||||
@@ -1490,9 +1644,9 @@ mod dispatch_impl {
|
|||||||
let _ = rx2.recv();
|
let _ = rx2.recv();
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = Client::builder().pool_max_idle_per_host(0).build(
|
let client = Client::builder()
|
||||||
DebugConnector::with_http_and_closes(HttpConnector::new(), closes_tx),
|
.pool_max_idle_per_host(0)
|
||||||
);
|
.build(DebugConnector::with_closes(closes_tx));
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.uri(&*format!("http://{}/a", addr))
|
.uri(&*format!("http://{}/a", addr))
|
||||||
@@ -1536,10 +1690,7 @@ mod dispatch_impl {
|
|||||||
let _ = tx1.send(());
|
let _ = tx1.send(());
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = Client::builder().build(DebugConnector::with_http_and_closes(
|
let client = Client::builder().build(DebugConnector::with_closes(closes_tx));
|
||||||
HttpConnector::new(),
|
|
||||||
closes_tx,
|
|
||||||
));
|
|
||||||
|
|
||||||
let req = Request::builder()
|
let req = Request::builder()
|
||||||
.uri(&*format!("http://{}/a", addr))
|
.uri(&*format!("http://{}/a", addr))
|
||||||
@@ -2085,7 +2236,6 @@ mod dispatch_impl {
|
|||||||
|
|
||||||
#[derive(Clone)]
|
#[derive(Clone)]
|
||||||
struct DebugConnector {
|
struct DebugConnector {
|
||||||
http: HttpConnector,
|
|
||||||
closes: mpsc::Sender<()>,
|
closes: mpsc::Sender<()>,
|
||||||
connects: Arc<AtomicUsize>,
|
connects: Arc<AtomicUsize>,
|
||||||
is_proxy: bool,
|
is_proxy: bool,
|
||||||
@@ -2094,14 +2244,12 @@ mod dispatch_impl {
|
|||||||
|
|
||||||
impl DebugConnector {
|
impl DebugConnector {
|
||||||
fn new() -> DebugConnector {
|
fn new() -> DebugConnector {
|
||||||
let http = HttpConnector::new();
|
|
||||||
let (tx, _) = mpsc::channel(10);
|
let (tx, _) = mpsc::channel(10);
|
||||||
DebugConnector::with_http_and_closes(http, tx)
|
DebugConnector::with_closes(tx)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn with_http_and_closes(http: HttpConnector, closes: mpsc::Sender<()>) -> DebugConnector {
|
fn with_closes(closes: mpsc::Sender<()>) -> DebugConnector {
|
||||||
DebugConnector {
|
DebugConnector {
|
||||||
http,
|
|
||||||
closes,
|
closes,
|
||||||
connects: Arc::new(AtomicUsize::new(0)),
|
connects: Arc::new(AtomicUsize::new(0)),
|
||||||
is_proxy: false,
|
is_proxy: false,
|
||||||
@@ -2117,12 +2265,11 @@ mod dispatch_impl {
|
|||||||
|
|
||||||
impl hyper::service::Service<Uri> for DebugConnector {
|
impl hyper::service::Service<Uri> for DebugConnector {
|
||||||
type Response = DebugStream;
|
type Response = DebugStream;
|
||||||
type Error = <HttpConnector as hyper::service::Service<Uri>>::Error;
|
type Error = std::io::Error;
|
||||||
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
|
||||||
|
|
||||||
fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
// don't forget to check inner service is ready :)
|
Poll::Ready(Ok(()))
|
||||||
hyper::service::Service::<Uri>::poll_ready(&mut self.http, cx)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn call(&mut self, dst: Uri) -> Self::Future {
|
fn call(&mut self, dst: Uri) -> Self::Future {
|
||||||
@@ -2130,12 +2277,20 @@ mod dispatch_impl {
|
|||||||
let closes = self.closes.clone();
|
let closes = self.closes.clone();
|
||||||
let is_proxy = self.is_proxy;
|
let is_proxy = self.is_proxy;
|
||||||
let is_alpn_h2 = self.alpn_h2;
|
let is_alpn_h2 = self.alpn_h2;
|
||||||
Box::pin(self.http.call(dst).map_ok(move |tcp| DebugStream {
|
|
||||||
tcp,
|
Box::pin(async move {
|
||||||
on_drop: closes,
|
let host = dst.host().expect("no host in uri");
|
||||||
is_alpn_h2,
|
let port = dst.port_u16().expect("no port in uri");
|
||||||
is_proxy,
|
|
||||||
}))
|
let stream = TcpStream::connect(format!("{}:{}", host, port)).await?;
|
||||||
|
|
||||||
|
Ok(DebugStream {
|
||||||
|
tcp: stream,
|
||||||
|
on_drop: closes,
|
||||||
|
is_alpn_h2,
|
||||||
|
is_proxy,
|
||||||
|
})
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2188,7 +2343,7 @@ mod dispatch_impl {
|
|||||||
|
|
||||||
impl Connection for DebugStream {
|
impl Connection for DebugStream {
|
||||||
fn connected(&self) -> Connected {
|
fn connected(&self) -> Connected {
|
||||||
let connected = self.tcp.connected().proxy(self.is_proxy);
|
let connected = Connected::new().proxy(self.is_proxy);
|
||||||
|
|
||||||
if self.is_alpn_h2 {
|
if self.is_alpn_h2 {
|
||||||
connected.negotiated_h2()
|
connected.negotiated_h2()
|
||||||
@@ -2744,27 +2899,45 @@ mod conn {
|
|||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http2_detect_conn_eof() {
|
async fn http2_detect_conn_eof() {
|
||||||
use futures_util::future;
|
use futures_util::future;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
|
||||||
use hyper::{Response, Server};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
let server = Server::bind(&([127, 0, 0, 1], 0).into())
|
let addr = SocketAddr::from(([127, 0, 0, 1], 0));
|
||||||
.http2_only(true)
|
let listener = TkTcpListener::bind(addr).await.unwrap();
|
||||||
.serve(make_service_fn(|_| async move {
|
|
||||||
Ok::<_, hyper::Error>(service_fn(|_req| {
|
let addr = listener.local_addr().unwrap();
|
||||||
future::ok::<_, hyper::Error>(Response::new(Body::empty()))
|
let (shdn_tx, mut shdn_rx) = tokio::sync::watch::channel(false);
|
||||||
}))
|
|
||||||
}));
|
|
||||||
let addr = server.local_addr();
|
|
||||||
let (shdn_tx, shdn_rx) = oneshot::channel();
|
|
||||||
tokio::task::spawn(async move {
|
tokio::task::spawn(async move {
|
||||||
server
|
use hyper::server::conn::Http;
|
||||||
.with_graceful_shutdown(async move {
|
use hyper::service::service_fn;
|
||||||
let _ = shdn_rx.await;
|
|
||||||
})
|
loop {
|
||||||
.await
|
tokio::select! {
|
||||||
.expect("server")
|
res = listener.accept() => {
|
||||||
|
let (stream, _) = res.unwrap();
|
||||||
|
|
||||||
|
let service = service_fn(|_:Request<Body>| future::ok::<Response<Body>, hyper::Error>(Response::new(Body::empty())));
|
||||||
|
|
||||||
|
let mut shdn_rx = shdn_rx.clone();
|
||||||
|
tokio::task::spawn(async move {
|
||||||
|
let mut conn = Http::new().http2_only(true).serve_connection(stream, service);
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
res = &mut conn => {
|
||||||
|
res.unwrap();
|
||||||
|
}
|
||||||
|
_ = shdn_rx.changed() => {
|
||||||
|
Pin::new(&mut conn).graceful_shutdown();
|
||||||
|
conn.await.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
_ = shdn_rx.changed() => {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let io = tcp_connect(&addr).await.expect("tcp connect");
|
let io = tcp_connect(&addr).await.expect("tcp connect");
|
||||||
@@ -2796,7 +2969,7 @@ mod conn {
|
|||||||
.expect("client poll ready after");
|
.expect("client poll ready after");
|
||||||
|
|
||||||
// Trigger the server shutdown...
|
// Trigger the server shutdown...
|
||||||
let _ = shdn_tx.send(());
|
let _ = shdn_tx.send(true);
|
||||||
|
|
||||||
// Allow time for graceful shutdown roundtrips...
|
// Allow time for graceful shutdown roundtrips...
|
||||||
tokio::time::sleep(Duration::from_millis(100)).await;
|
tokio::time::sleep(Duration::from_millis(100)).await;
|
||||||
|
|||||||
223
tests/server.rs
223
tests/server.rs
@@ -21,15 +21,14 @@ use h2::client::SendRequest;
|
|||||||
use h2::{RecvStream, SendStream};
|
use h2::{RecvStream, SendStream};
|
||||||
use http::header::{HeaderName, HeaderValue};
|
use http::header::{HeaderName, HeaderValue};
|
||||||
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
use http_body_util::{combinators::BoxBody, BodyExt, StreamBody};
|
||||||
use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf};
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
use tokio::net::{TcpListener, TcpStream as TkTcpStream};
|
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
||||||
|
use tokio::net::{TcpListener as TkTcpListener, TcpListener, TcpStream as TkTcpStream};
|
||||||
|
|
||||||
use hyper::body::HttpBody as _;
|
use hyper::body::HttpBody;
|
||||||
use hyper::client::Client;
|
|
||||||
use hyper::server::conn::Http;
|
use hyper::server::conn::Http;
|
||||||
use hyper::server::Server;
|
use hyper::service::service_fn;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::{Body, Method, Request, Response, StatusCode, Uri, Version};
|
||||||
use hyper::{Body, Request, Response, StatusCode, Version};
|
|
||||||
|
|
||||||
mod support;
|
mod support;
|
||||||
|
|
||||||
@@ -320,15 +319,11 @@ mod response_body_lengths {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http2_auto_response_with_known_length() {
|
async fn http2_auto_response_with_known_length() {
|
||||||
use http_body::Body;
|
|
||||||
|
|
||||||
let server = serve();
|
let server = serve();
|
||||||
let addr_str = format!("http://{}", server.addr());
|
let addr_str = format!("http://{}", server.addr());
|
||||||
server.reply().body("Hello, World!");
|
server.reply().body("Hello, World!");
|
||||||
|
|
||||||
let client = Client::builder()
|
let client = TestClient::new().http2_only();
|
||||||
.http2_only(true)
|
|
||||||
.build_http::<hyper::Body>();
|
|
||||||
let uri = addr_str
|
let uri = addr_str
|
||||||
.parse::<hyper::Uri>()
|
.parse::<hyper::Uri>()
|
||||||
.expect("server addr should parse");
|
.expect("server addr should parse");
|
||||||
@@ -340,8 +335,6 @@ mod response_body_lengths {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http2_auto_response_with_conflicting_lengths() {
|
async fn http2_auto_response_with_conflicting_lengths() {
|
||||||
use http_body::Body;
|
|
||||||
|
|
||||||
let server = serve();
|
let server = serve();
|
||||||
let addr_str = format!("http://{}", server.addr());
|
let addr_str = format!("http://{}", server.addr());
|
||||||
server
|
server
|
||||||
@@ -349,9 +342,7 @@ mod response_body_lengths {
|
|||||||
.header("content-length", "10")
|
.header("content-length", "10")
|
||||||
.body("Hello, World!");
|
.body("Hello, World!");
|
||||||
|
|
||||||
let client = Client::builder()
|
let client = TestClient::new().http2_only();
|
||||||
.http2_only(true)
|
|
||||||
.build_http::<hyper::Body>();
|
|
||||||
let uri = addr_str
|
let uri = addr_str
|
||||||
.parse::<hyper::Uri>()
|
.parse::<hyper::Uri>()
|
||||||
.expect("server addr should parse");
|
.expect("server addr should parse");
|
||||||
@@ -363,15 +354,11 @@ mod response_body_lengths {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http2_implicit_empty_size_hint() {
|
async fn http2_implicit_empty_size_hint() {
|
||||||
use http_body::Body;
|
|
||||||
|
|
||||||
let server = serve();
|
let server = serve();
|
||||||
let addr_str = format!("http://{}", server.addr());
|
let addr_str = format!("http://{}", server.addr());
|
||||||
server.reply();
|
server.reply();
|
||||||
|
|
||||||
let client = Client::builder()
|
let client = TestClient::new().http2_only();
|
||||||
.http2_only(true)
|
|
||||||
.build_http::<hyper::Body>();
|
|
||||||
let uri = addr_str
|
let uri = addr_str
|
||||||
.parse::<hyper::Uri>()
|
.parse::<hyper::Uri>()
|
||||||
.expect("server addr should parse");
|
.expect("server addr should parse");
|
||||||
@@ -1480,8 +1467,6 @@ async fn header_read_timeout_slow_writes_multiple_requests() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn upgrades() {
|
async fn upgrades() {
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
@@ -1539,8 +1524,6 @@ async fn upgrades() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http_connect() {
|
async fn http_connect() {
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
@@ -1675,15 +1658,19 @@ async fn upgrades_ignored() {
|
|||||||
future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty()))
|
future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty()))
|
||||||
});
|
});
|
||||||
|
|
||||||
let (socket, _) = listener.accept().await.unwrap();
|
loop {
|
||||||
Http::new()
|
let (socket, _) = listener.accept().await.unwrap();
|
||||||
.serve_connection(socket, svc)
|
tokio::task::spawn(async move {
|
||||||
.with_upgrades()
|
Http::new()
|
||||||
.await
|
.serve_connection(socket, svc)
|
||||||
.expect("server task");
|
.with_upgrades()
|
||||||
|
.await
|
||||||
|
.expect("server task");
|
||||||
|
});
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let client = hyper::Client::new();
|
let client = TestClient::new();
|
||||||
let url = format!("http://{}/", addr);
|
let url = format!("http://{}/", addr);
|
||||||
|
|
||||||
let make_req = || {
|
let make_req = || {
|
||||||
@@ -1705,8 +1692,6 @@ async fn upgrades_ignored() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn http_connect_new() {
|
async fn http_connect_new() {
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
@@ -1771,8 +1756,6 @@ async fn http_connect_new() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn h2_connect() {
|
async fn h2_connect() {
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
@@ -1843,7 +1826,6 @@ async fn h2_connect() {
|
|||||||
async fn h2_connect_multiplex() {
|
async fn h2_connect_multiplex() {
|
||||||
use futures_util::stream::FuturesUnordered;
|
use futures_util::stream::FuturesUnordered;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
@@ -1954,8 +1936,6 @@ async fn h2_connect_multiplex() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn h2_connect_large_body() {
|
async fn h2_connect_large_body() {
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
@@ -2031,8 +2011,6 @@ async fn h2_connect_large_body() {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn h2_connect_empty_frames() {
|
async fn h2_connect_empty_frames() {
|
||||||
use tokio::io::{AsyncReadExt, AsyncWriteExt};
|
|
||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap();
|
||||||
let addr = listener.local_addr().unwrap();
|
let addr = listener.local_addr().unwrap();
|
||||||
@@ -2225,8 +2203,8 @@ fn http1_response_with_http2_version() {
|
|||||||
|
|
||||||
server.reply().version(hyper::Version::HTTP_2);
|
server.reply().version(hyper::Version::HTTP_2);
|
||||||
|
|
||||||
|
let client = TestClient::new();
|
||||||
rt.block_on({
|
rt.block_on({
|
||||||
let client = Client::new();
|
|
||||||
let uri = addr_str.parse().expect("server addr should parse");
|
let uri = addr_str.parse().expect("server addr should parse");
|
||||||
client.get(uri)
|
client.get(uri)
|
||||||
})
|
})
|
||||||
@@ -2240,10 +2218,8 @@ fn try_h2() {
|
|||||||
|
|
||||||
let rt = support::runtime();
|
let rt = support::runtime();
|
||||||
|
|
||||||
|
let client = TestClient::new().http2_only();
|
||||||
rt.block_on({
|
rt.block_on({
|
||||||
let client = Client::builder()
|
|
||||||
.http2_only(true)
|
|
||||||
.build_http::<hyper::Body>();
|
|
||||||
let uri = addr_str.parse().expect("server addr should parse");
|
let uri = addr_str.parse().expect("server addr should parse");
|
||||||
|
|
||||||
client.get(uri).map_ok(|_| ()).map_err(|_e| ())
|
client.get(uri).map_ok(|_| ()).map_err(|_e| ())
|
||||||
@@ -2260,10 +2236,8 @@ fn http1_only() {
|
|||||||
|
|
||||||
let rt = support::runtime();
|
let rt = support::runtime();
|
||||||
|
|
||||||
|
let client = TestClient::new().http2_only();
|
||||||
rt.block_on({
|
rt.block_on({
|
||||||
let client = Client::builder()
|
|
||||||
.http2_only(true)
|
|
||||||
.build_http::<hyper::Body>();
|
|
||||||
let uri = addr_str.parse().expect("server addr should parse");
|
let uri = addr_str.parse().expect("server addr should parse");
|
||||||
client.get(uri)
|
client.get(uri)
|
||||||
})
|
})
|
||||||
@@ -2283,9 +2257,8 @@ async fn http2_service_error_sends_reset_reason() {
|
|||||||
|
|
||||||
let uri = addr_str.parse().expect("server addr should parse");
|
let uri = addr_str.parse().expect("server addr should parse");
|
||||||
dbg!("start");
|
dbg!("start");
|
||||||
let err = dbg!(Client::builder()
|
let err = dbg!(TestClient::new()
|
||||||
.http2_only(true)
|
.http2_only()
|
||||||
.build_http::<hyper::Body>()
|
|
||||||
.get(uri)
|
.get(uri)
|
||||||
.await
|
.await
|
||||||
.expect_err("client.get"));
|
.expect_err("client.get"));
|
||||||
@@ -2314,9 +2287,8 @@ fn http2_body_user_error_sends_reset_reason() {
|
|||||||
|
|
||||||
let err: hyper::Error = rt
|
let err: hyper::Error = rt
|
||||||
.block_on(async move {
|
.block_on(async move {
|
||||||
let client = Client::builder()
|
let client = TestClient::new().http2_only();
|
||||||
.http2_only(true)
|
|
||||||
.build_http::<hyper::Body>();
|
|
||||||
let uri = addr_str.parse().expect("server addr should parse");
|
let uri = addr_str.parse().expect("server addr should parse");
|
||||||
|
|
||||||
let mut res = client.get(uri).await?;
|
let mut res = client.get(uri).await?;
|
||||||
@@ -2363,22 +2335,33 @@ async fn http2_service_poll_ready_error_sends_goaway() {
|
|||||||
|
|
||||||
let _ = pretty_env_logger::try_init();
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
let server = hyper::Server::bind(&([127, 0, 0, 1], 0).into())
|
let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||||
.http2_only(true)
|
.await
|
||||||
.serve(make_service_fn(|_| async move {
|
.unwrap();
|
||||||
Ok::<_, BoxError>(Http2ReadyErrorSvc)
|
|
||||||
}));
|
|
||||||
|
|
||||||
let addr_str = format!("http://{}", server.local_addr());
|
let addr_str = format!("http://{}", listener.local_addr().unwrap());
|
||||||
|
|
||||||
tokio::task::spawn(async move {
|
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 uri = addr_str.parse().expect("server addr should parse");
|
||||||
let err = dbg!(Client::builder()
|
let err = dbg!(TestClient::new()
|
||||||
.http2_only(true)
|
.http2_only()
|
||||||
.build_http::<hyper::Body>()
|
|
||||||
.get(uri)
|
.get(uri)
|
||||||
.await
|
.await
|
||||||
.expect_err("client.get should fail"));
|
.expect_err("client.get should fail"));
|
||||||
@@ -2948,9 +2931,9 @@ impl ServeOptions {
|
|||||||
let (addr_tx, addr_rx) = mpsc::channel();
|
let (addr_tx, addr_rx) = mpsc::channel();
|
||||||
let (msg_tx, msg_rx) = mpsc::channel();
|
let (msg_tx, msg_rx) = mpsc::channel();
|
||||||
let (reply_tx, reply_rx) = spmc::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!(
|
let thread_name = format!(
|
||||||
"test-server-{}",
|
"test-server-{}",
|
||||||
@@ -2961,36 +2944,46 @@ impl ServeOptions {
|
|||||||
let thread = thread::Builder::new()
|
let thread = thread::Builder::new()
|
||||||
.name(thread_name)
|
.name(thread_name)
|
||||||
.spawn(move || {
|
.spawn(move || {
|
||||||
support::runtime()
|
support::runtime().block_on(async move {
|
||||||
.block_on(async move {
|
let listener = TkTcpListener::bind(addr).await.unwrap();
|
||||||
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,
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
let builder = Server::bind(&addr);
|
addr_tx
|
||||||
|
.send(listener.local_addr().unwrap())
|
||||||
|
.expect("server addr tx");
|
||||||
|
|
||||||
#[cfg(feature = "http1")]
|
loop {
|
||||||
let builder = builder
|
let msg_tx = msg_tx.clone();
|
||||||
.http1_only(_options.http1_only)
|
let reply_rx = reply_rx.clone();
|
||||||
.http1_keepalive(_options.keep_alive)
|
|
||||||
.http1_pipeline_flush(_options.pipeline);
|
|
||||||
|
|
||||||
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
|
#[cfg(feature = "http1")]
|
||||||
.with_graceful_shutdown(async {
|
let http = http
|
||||||
let _ = shutdown_rx.await;
|
.http1_only(_options.http1_only)
|
||||||
})
|
.http1_keep_alive(_options.keep_alive)
|
||||||
.await
|
.pipeline_flush(_options.pipeline);
|
||||||
})
|
|
||||||
.expect("serve()");
|
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");
|
.expect("thread spawn");
|
||||||
|
|
||||||
@@ -3119,3 +3112,49 @@ impl Drop for Dropped {
|
|||||||
self.0.store(true, Ordering::SeqCst);
|
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,
|
Arc, Mutex,
|
||||||
};
|
};
|
||||||
|
|
||||||
use hyper::client::HttpConnector;
|
use hyper::client::conn::Builder;
|
||||||
use hyper::service::{make_service_fn, service_fn};
|
use hyper::server::conn::Http;
|
||||||
use hyper::{Body, Client, Request, Response, Server, Version};
|
use tokio::net::{TcpListener, TcpStream};
|
||||||
|
|
||||||
|
use hyper::service::service_fn;
|
||||||
|
use hyper::{Body, Request, Response, Version};
|
||||||
|
|
||||||
pub use futures_util::{
|
pub use futures_util::{
|
||||||
future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _,
|
future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _,
|
||||||
@@ -326,16 +329,20 @@ async fn async_test(cfg: __TestConfig) {
|
|||||||
Version::HTTP_11
|
Version::HTTP_11
|
||||||
};
|
};
|
||||||
|
|
||||||
let connector = HttpConnector::new();
|
let http2_only = cfg.server_version == 2;
|
||||||
let client = Client::builder()
|
|
||||||
.http2_only(cfg.client_version == 2)
|
|
||||||
.build::<_, Body>(connector);
|
|
||||||
|
|
||||||
let serve_handles = Arc::new(Mutex::new(cfg.server_msgs));
|
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 expected_connections = cfg.connections;
|
||||||
let mut cnt = 0;
|
tokio::task::spawn(async move {
|
||||||
let new_service = make_service_fn(move |_| {
|
let mut cnt = 0;
|
||||||
|
|
||||||
cnt += 1;
|
cnt += 1;
|
||||||
assert!(
|
assert!(
|
||||||
cnt <= expected_connections,
|
cnt <= expected_connections,
|
||||||
@@ -344,98 +351,108 @@ async fn async_test(cfg: __TestConfig) {
|
|||||||
cnt
|
cnt
|
||||||
);
|
);
|
||||||
|
|
||||||
// Move a clone into the service_fn
|
loop {
|
||||||
let serve_handles = serve_handles.clone();
|
let (stream, _) = listener.accept().await.expect("server error");
|
||||||
future::ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| {
|
|
||||||
let (sreq, sres) = serve_handles.lock().unwrap().remove(0);
|
|
||||||
|
|
||||||
assert_eq!(req.uri().path(), sreq.uri, "client path");
|
// Move a clone into the service_fn
|
||||||
assert_eq!(req.method(), &sreq.method, "client method");
|
let serve_handles = serve_handles.clone();
|
||||||
assert_eq!(req.version(), version, "client version");
|
let service = service_fn(move |req: Request<Body>| {
|
||||||
for func in &sreq.headers {
|
let (sreq, sres) = serve_handles.lock().unwrap().remove(0);
|
||||||
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()
|
assert_eq!(req.uri().path(), sreq.uri, "client path");
|
||||||
.status(sres.status)
|
assert_eq!(req.method(), &sreq.method, "client method");
|
||||||
.body(Body::from(sres.body))
|
assert_eq!(req.version(), version, "client version");
|
||||||
.expect("Response::build");
|
for func in &sreq.headers {
|
||||||
*res.headers_mut() = sres.headers;
|
func(&req.headers());
|
||||||
res
|
}
|
||||||
})
|
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 {
|
if cfg.proxy {
|
||||||
let (proxy_addr, proxy) = naive_proxy(ProxyConfig {
|
let (proxy_addr, proxy) = naive_proxy(ProxyConfig {
|
||||||
connections: cfg.connections,
|
connections: cfg.connections,
|
||||||
dst: addr,
|
dst: addr,
|
||||||
version: cfg.server_version,
|
version: cfg.server_version,
|
||||||
});
|
})
|
||||||
|
.await;
|
||||||
tokio::task::spawn(proxy);
|
tokio::task::spawn(proxy);
|
||||||
addr = proxy_addr;
|
addr = proxy_addr;
|
||||||
}
|
}
|
||||||
|
|
||||||
let make_request = Arc::new(
|
let make_request = Arc::new(move |creq: __CReq, cres: __CRes| {
|
||||||
move |client: &Client<HttpConnector>, creq: __CReq, cres: __CRes| {
|
let uri = format!("http://{}{}", addr, creq.uri);
|
||||||
let uri = format!("http://{}{}", addr, creq.uri);
|
let mut req = Request::builder()
|
||||||
let mut req = Request::builder()
|
.method(creq.method)
|
||||||
.method(creq.method)
|
.uri(uri)
|
||||||
.uri(uri)
|
//.headers(creq.headers)
|
||||||
//.headers(creq.headers)
|
.body(creq.body.into())
|
||||||
.body(creq.body.into())
|
.expect("Request::build");
|
||||||
.expect("Request::build");
|
*req.headers_mut() = creq.headers;
|
||||||
*req.headers_mut() = creq.headers;
|
let cstatus = cres.status;
|
||||||
let cstatus = cres.status;
|
let cheaders = cres.headers;
|
||||||
let cheaders = cres.headers;
|
let cbody = cres.body;
|
||||||
let cbody = cres.body;
|
|
||||||
|
|
||||||
client
|
async move {
|
||||||
.request(req)
|
let stream = TcpStream::connect(addr).await.unwrap();
|
||||||
.and_then(move |res| {
|
|
||||||
assert_eq!(res.status(), cstatus, "server status");
|
let (mut sender, conn) = hyper::client::conn::Builder::new()
|
||||||
assert_eq!(res.version(), version, "server version");
|
.http2_only(http2_only)
|
||||||
for func in &cheaders {
|
.handshake::<TcpStream, Body>(stream)
|
||||||
func(&res.headers());
|
.await
|
||||||
}
|
.unwrap();
|
||||||
hyper::body::to_bytes(res)
|
|
||||||
})
|
tokio::task::spawn(async move {
|
||||||
.map_ok(move |body| {
|
if let Err(err) = conn.await {
|
||||||
assert_eq!(body.as_ref(), cbody.as_slice(), "server body");
|
panic!("{:?}", err);
|
||||||
})
|
}
|
||||||
.map(|res| res.expect("client error"))
|
});
|
||||||
},
|
|
||||||
);
|
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 client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = if cfg.parallel {
|
||||||
let mut client_futures = vec![];
|
let mut client_futures = vec![];
|
||||||
for (creq, cres) in cfg.client_msgs {
|
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(|_| ()))
|
Box::pin(future::join_all(client_futures).map(|_| ()))
|
||||||
} else {
|
} else {
|
||||||
let mut client_futures: Pin<Box<dyn Future<Output = Client<HttpConnector>> + Send>> =
|
let mut client_futures: Pin<Box<dyn Future<Output = ()> + Send>> =
|
||||||
Box::pin(future::ready(client));
|
Box::pin(future::ready(()));
|
||||||
for (creq, cres) in cfg.client_msgs {
|
for (creq, cres) in cfg.client_msgs {
|
||||||
let mk_request = make_request.clone();
|
let mk_request = make_request.clone();
|
||||||
client_futures = Box::pin(client_futures.then(move |client| {
|
client_futures = Box::pin(client_futures.then(move |_| mk_request(creq, cres)));
|
||||||
let fut = mk_request(&client, creq, cres);
|
|
||||||
fut.map(move |()| client)
|
|
||||||
}));
|
|
||||||
}
|
}
|
||||||
Box::pin(client_futures.map(|_| ()))
|
Box::pin(client_futures.map(|_| ()))
|
||||||
};
|
};
|
||||||
@@ -449,27 +466,75 @@ struct ProxyConfig {
|
|||||||
version: usize,
|
version: usize,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) {
|
async fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) {
|
||||||
let client = Client::builder()
|
|
||||||
.http2_only(cfg.version == 2)
|
|
||||||
.build_http::<Body>();
|
|
||||||
|
|
||||||
let dst_addr = cfg.dst;
|
let dst_addr = cfg.dst;
|
||||||
let max_connections = cfg.connections;
|
let max_connections = cfg.connections;
|
||||||
let counter = AtomicUsize::new(0);
|
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 listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0)))
|
||||||
let prev = counter.fetch_add(1, Ordering::Relaxed);
|
.await
|
||||||
assert!(max_connections > prev, "proxy max connections");
|
.unwrap();
|
||||||
let client = client.clone();
|
|
||||||
future::ok::<_, hyper::Error>(service_fn(move |mut req| {
|
let proxy_addr = listener.local_addr().unwrap();
|
||||||
let uri = format!("http://{}{}", dst_addr, req.uri().path())
|
|
||||||
.parse()
|
let fut = async move {
|
||||||
.expect("proxy new uri parse");
|
tokio::task::spawn(async move {
|
||||||
*req.uri_mut() = uri;
|
let prev = counter.fetch_add(1, Ordering::Relaxed);
|
||||||
client.request(req)
|
assert!(max_connections > prev, "proxy max connections");
|
||||||
}))
|
|
||||||
}));
|
loop {
|
||||||
let proxy_addr = srv.local_addr();
|
let (stream, _) = listener.accept().await.unwrap();
|
||||||
(proxy_addr, srv.map(|res| res.expect("proxy error")))
|
|
||||||
|
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