From 0c8ee93d7f557afc63ca2a5686d19071813ab2b7 Mon Sep 17 00:00:00 2001 From: Sean McArthur Date: Fri, 29 Jul 2022 10:07:09 -0700 Subject: [PATCH] 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 --- Cargo.toml | 6 - benches/connect.rs | 61 +-- benches/end_to_end.rs | 665 ++++++++++++++------------- benches/pipeline.rs | 135 +++--- benches/server.rs | 209 ++++----- examples/client.rs | 18 +- examples/client_json.rs | 21 +- examples/echo.rs | 26 +- examples/gateway.rs | 80 ++-- examples/hello.rs | 35 +- examples/http_proxy.rs | 65 +-- examples/multi_server.rs | 48 +- examples/params.rs | 28 +- examples/send_file.rs | 32 +- examples/service_struct_impl.rs | 43 +- examples/single_threaded.rs | 60 ++- examples/state.rs | 52 +-- examples/tower_client.rs | 48 +- examples/tower_server.rs | 36 +- examples/upgrades.rs | 70 ++- examples/web_api.rs | 62 +-- src/body/body.rs | 3 + src/body/to_bytes.rs | 14 +- src/client/client.rs | 116 +---- src/client/connect/dns.rs | 101 +--- src/client/connect/http.rs | 791 +------------------------------- src/client/connect/mod.rs | 31 +- src/client/dispatch.rs | 4 +- src/client/mod.rs | 24 - src/client/tests.rs | 25 - src/common/exec.rs | 6 +- src/lib.rs | 1 - src/server/conn.rs | 2 +- src/server/mod.rs | 135 ------ src/server/server.rs | 156 +------ src/server/tcp.rs | 192 -------- src/service/make.rs | 35 -- tests/client.rs | 323 ++++++++++--- tests/server.rs | 223 +++++---- tests/support/mod.rs | 259 +++++++---- 40 files changed, 1565 insertions(+), 2676 deletions(-) delete mode 100644 src/server/tcp.rs diff --git a/Cargo.toml b/Cargo.toml index be09230f..f3538a31 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -94,12 +94,6 @@ server = [] # Tokio support runtime = [ - "tcp", - "tokio/rt", - "tokio/time", -] -tcp = [ - "socket2", "tokio/net", "tokio/rt", "tokio/time", diff --git a/benches/connect.rs b/benches/connect.rs index eafb8fd3..34769cf9 100644 --- a/benches/connect.rs +++ b/benches/connect.rs @@ -3,35 +3,38 @@ extern crate test; -use http::Uri; -use hyper::client::connect::HttpConnector; -use hyper::service::Service; -use std::net::SocketAddr; -use tokio::net::TcpListener; +// TODO: Reimplement http_connector bench using hyper::client::conn +// (instead of removed HttpConnector). -#[bench] -fn http_connector(b: &mut test::Bencher) { - let _ = pretty_env_logger::try_init(); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("rt build"); - let listener = rt - .block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0)))) - .expect("bind"); - let addr = listener.local_addr().expect("local_addr"); - let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse"); - let mut connector = HttpConnector::new(); +// use http::Uri; +// use hyper::client::connect::HttpConnector; +// use hyper::service::Service; +// use std::net::SocketAddr; +// use tokio::net::TcpListener; - rt.spawn(async move { - loop { - let _ = listener.accept().await; - } - }); +// #[bench] +// fn http_connector(b: &mut test::Bencher) { +// let _ = pretty_env_logger::try_init(); +// let rt = tokio::runtime::Builder::new_current_thread() +// .enable_all() +// .build() +// .expect("rt build"); +// let listener = rt +// .block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0)))) +// .expect("bind"); +// let addr = listener.local_addr().expect("local_addr"); +// let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse"); +// let mut connector = HttpConnector::new(); - b.iter(|| { - rt.block_on(async { - connector.call(dst.clone()).await.expect("connect"); - }); - }); -} +// rt.spawn(async move { +// loop { +// let _ = listener.accept().await; +// } +// }); + +// b.iter(|| { +// rt.block_on(async { +// connector.call(dst.clone()).await.expect("connect"); +// }); +// }); +// } diff --git a/benches/end_to_end.rs b/benches/end_to_end.rs index 64efb922..41dbde2a 100644 --- a/benches/end_to_end.rs +++ b/benches/end_to_end.rs @@ -3,380 +3,383 @@ extern crate test; -use std::net::SocketAddr; +// TODO: Reimplement Opts::bench using hyper::server::conn and hyper::client::conn +// (instead of Server and HttpClient). -use futures_util::future::join_all; +// use std::net::SocketAddr; -use hyper::client::HttpConnector; -use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server}; +// use futures_util::future::join_all; -// HTTP1 +// use hyper::client::HttpConnector; +// use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server}; -#[bench] -fn http1_consecutive_x1_empty(b: &mut test::Bencher) { - opts().bench(b) -} +// // HTTP1 -#[bench] -fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { - opts() - .method(Method::POST) - .request_body(&[b's'; 10]) - .bench(b) -} +// #[bench] +// fn http1_consecutive_x1_empty(b: &mut test::Bencher) { +// opts().bench(b) +// } -#[bench] -fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 100]; - opts() - .method(Method::POST) - .request_body(body) - .response_body(body) - .bench(b) -} +// #[bench] +// fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { +// opts() +// .method(Method::POST) +// .request_body(&[b's'; 10]) +// .bench(b) +// } -#[bench] -fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 10]; - opts() - .method(Method::POST) - .request_body(body) - .response_body(body) - .bench(b) -} +// #[bench] +// fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 100]; +// opts() +// .method(Method::POST) +// .request_body(body) +// .response_body(body) +// .bench(b) +// } -#[bench] -fn http1_parallel_x10_empty(b: &mut test::Bencher) { - opts().parallel(10).bench(b) -} +// #[bench] +// fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 10]; +// opts() +// .method(Method::POST) +// .request_body(body) +// .response_body(body) +// .bench(b) +// } -#[bench] -fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 10]; - opts() - .parallel(10) - .method(Method::POST) - .request_body(body) - .bench(b) -} +// #[bench] +// fn http1_parallel_x10_empty(b: &mut test::Bencher) { +// opts().parallel(10).bench(b) +// } -#[bench] -fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; - opts() - .parallel(10) - .method(Method::POST) - .request_chunks(body, 100) - .bench(b) -} +// #[bench] +// fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 10]; +// opts() +// .parallel(10) +// .method(Method::POST) +// .request_body(body) +// .bench(b) +// } -#[bench] -fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 1]; - opts().parallel(10).response_body(body).bench(b) -} +// #[bench] +// fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 10]; +// opts() +// .parallel(10) +// .method(Method::POST) +// .request_chunks(body, 100) +// .bench(b) +// } -#[bench] -fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 10]; - opts().parallel(10).response_body(body).bench(b) -} +// #[bench] +// fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 1]; +// opts().parallel(10).response_body(body).bench(b) +// } -// HTTP2 +// #[bench] +// fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 10]; +// opts().parallel(10).response_body(body).bench(b) +// } -const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1; +// // HTTP2 -#[bench] -fn http2_consecutive_x1_empty(b: &mut test::Bencher) { - opts().http2().bench(b) -} +// const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1; -#[bench] -fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) { - opts() - .http2() - .method(Method::POST) - .request_body(&[b's'; 10]) - .bench(b) -} +// #[bench] +// fn http2_consecutive_x1_empty(b: &mut test::Bencher) { +// opts().http2().bench(b) +// } -#[bench] -fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 100]; - opts() - .http2() - .method(Method::POST) - .request_body(body) - .bench(b) -} +// #[bench] +// fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) { +// opts() +// .http2() +// .method(Method::POST) +// .request_body(&[b's'; 10]) +// .bench(b) +// } -#[bench] -fn http2_parallel_x10_empty(b: &mut test::Bencher) { - opts().http2().parallel(10).bench(b) -} +// #[bench] +// fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 100]; +// opts() +// .http2() +// .method(Method::POST) +// .request_body(body) +// .bench(b) +// } -#[bench] -fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 10]; - opts() - .http2() - .parallel(10) - .method(Method::POST) - .request_body(body) - .http2_stream_window(HTTP2_MAX_WINDOW) - .http2_conn_window(HTTP2_MAX_WINDOW) - .bench(b) -} +// #[bench] +// fn http2_parallel_x10_empty(b: &mut test::Bencher) { +// opts().http2().parallel(10).bench(b) +// } -#[bench] -fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; - opts() - .http2() - .parallel(10) - .method(Method::POST) - .request_chunks(body, 100) - .bench(b) -} +// #[bench] +// fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 10]; +// opts() +// .http2() +// .parallel(10) +// .method(Method::POST) +// .request_body(body) +// .http2_stream_window(HTTP2_MAX_WINDOW) +// .http2_conn_window(HTTP2_MAX_WINDOW) +// .bench(b) +// } -#[bench] -fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; - opts() - .http2() - .parallel(10) - .method(Method::POST) - .request_chunks(body, 100) - .http2_adaptive_window() - .bench(b) -} +// #[bench] +// fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 10]; +// opts() +// .http2() +// .parallel(10) +// .method(Method::POST) +// .request_chunks(body, 100) +// .bench(b) +// } -#[bench] -fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 10]; - opts() - .http2() - .parallel(10) - .method(Method::POST) - .request_chunks(body, 100) - .http2_stream_window(HTTP2_MAX_WINDOW) - .http2_conn_window(HTTP2_MAX_WINDOW) - .bench(b) -} +// #[bench] +// fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 10]; +// opts() +// .http2() +// .parallel(10) +// .method(Method::POST) +// .request_chunks(body, 100) +// .http2_adaptive_window() +// .bench(b) +// } -#[bench] -fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 1]; - opts() - .http2() - .parallel(10) - .response_body(body) - .http2_stream_window(HTTP2_MAX_WINDOW) - .http2_conn_window(HTTP2_MAX_WINDOW) - .bench(b) -} +// #[bench] +// fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 10]; +// opts() +// .http2() +// .parallel(10) +// .method(Method::POST) +// .request_chunks(body, 100) +// .http2_stream_window(HTTP2_MAX_WINDOW) +// .http2_conn_window(HTTP2_MAX_WINDOW) +// .bench(b) +// } -#[bench] -fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) { - let body = &[b'x'; 1024 * 1024 * 10]; - opts() - .http2() - .parallel(10) - .response_body(body) - .http2_stream_window(HTTP2_MAX_WINDOW) - .http2_conn_window(HTTP2_MAX_WINDOW) - .bench(b) -} +// #[bench] +// fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 1]; +// opts() +// .http2() +// .parallel(10) +// .response_body(body) +// .http2_stream_window(HTTP2_MAX_WINDOW) +// .http2_conn_window(HTTP2_MAX_WINDOW) +// .bench(b) +// } -// ==== Benchmark Options ===== +// #[bench] +// fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) { +// let body = &[b'x'; 1024 * 1024 * 10]; +// opts() +// .http2() +// .parallel(10) +// .response_body(body) +// .http2_stream_window(HTTP2_MAX_WINDOW) +// .http2_conn_window(HTTP2_MAX_WINDOW) +// .bench(b) +// } -struct Opts { - http2: bool, - http2_stream_window: Option, - http2_conn_window: Option, - http2_adaptive_window: bool, - parallel_cnt: u32, - request_method: Method, - request_body: Option<&'static [u8]>, - request_chunks: usize, - response_body: &'static [u8], -} +// // ==== Benchmark Options ===== -fn opts() -> Opts { - Opts { - http2: false, - http2_stream_window: None, - http2_conn_window: None, - http2_adaptive_window: false, - parallel_cnt: 1, - request_method: Method::GET, - request_body: None, - request_chunks: 0, - response_body: b"", - } -} +// struct Opts { +// http2: bool, +// http2_stream_window: Option, +// http2_conn_window: Option, +// http2_adaptive_window: bool, +// parallel_cnt: u32, +// request_method: Method, +// request_body: Option<&'static [u8]>, +// request_chunks: usize, +// response_body: &'static [u8], +// } -impl Opts { - fn http2(mut self) -> Self { - self.http2 = true; - self - } +// fn opts() -> Opts { +// Opts { +// http2: false, +// http2_stream_window: None, +// http2_conn_window: None, +// http2_adaptive_window: false, +// parallel_cnt: 1, +// request_method: Method::GET, +// request_body: None, +// request_chunks: 0, +// response_body: b"", +// } +// } - fn http2_stream_window(mut self, sz: impl Into>) -> Self { - assert!(!self.http2_adaptive_window); - self.http2_stream_window = sz.into(); - self - } +// impl Opts { +// fn http2(mut self) -> Self { +// self.http2 = true; +// self +// } - fn http2_conn_window(mut self, sz: impl Into>) -> Self { - assert!(!self.http2_adaptive_window); - self.http2_conn_window = sz.into(); - self - } +// fn http2_stream_window(mut self, sz: impl Into>) -> Self { +// assert!(!self.http2_adaptive_window); +// self.http2_stream_window = sz.into(); +// self +// } - fn http2_adaptive_window(mut self) -> Self { - assert!(self.http2_stream_window.is_none()); - assert!(self.http2_conn_window.is_none()); - self.http2_adaptive_window = true; - self - } +// fn http2_conn_window(mut self, sz: impl Into>) -> Self { +// assert!(!self.http2_adaptive_window); +// self.http2_conn_window = sz.into(); +// self +// } - fn method(mut self, m: Method) -> Self { - self.request_method = m; - self - } +// fn http2_adaptive_window(mut self) -> Self { +// assert!(self.http2_stream_window.is_none()); +// assert!(self.http2_conn_window.is_none()); +// self.http2_adaptive_window = true; +// self +// } - fn request_body(mut self, body: &'static [u8]) -> Self { - self.request_body = Some(body); - self - } +// fn method(mut self, m: Method) -> Self { +// self.request_method = m; +// self +// } - fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self { - assert!(cnt > 0); - self.request_body = Some(chunk); - self.request_chunks = cnt; - self - } +// fn request_body(mut self, body: &'static [u8]) -> Self { +// self.request_body = Some(body); +// self +// } - fn response_body(mut self, body: &'static [u8]) -> Self { - self.response_body = body; - self - } +// fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self { +// assert!(cnt > 0); +// self.request_body = Some(chunk); +// self.request_chunks = cnt; +// self +// } - fn parallel(mut self, cnt: u32) -> Self { - assert!(cnt > 0, "parallel count must be larger than 0"); - self.parallel_cnt = cnt; - self - } +// fn response_body(mut self, body: &'static [u8]) -> Self { +// self.response_body = body; +// self +// } - fn bench(self, b: &mut test::Bencher) { - use std::sync::Arc; - let _ = pretty_env_logger::try_init(); - // Create a runtime of current thread. - let rt = Arc::new( - tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("rt build"), - ); - let exec = rt.clone(); +// fn parallel(mut self, cnt: u32) -> Self { +// assert!(cnt > 0, "parallel count must be larger than 0"); +// self.parallel_cnt = cnt; +// self +// } - let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64; - let req_len = if self.request_chunks > 0 { - req_len * self.request_chunks as u64 - } else { - req_len - }; - let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64; - b.bytes = bytes_per_iter; +// fn bench(self, b: &mut test::Bencher) { +// use std::sync::Arc; +// let _ = pretty_env_logger::try_init(); +// // Create a runtime of current thread. +// let rt = Arc::new( +// tokio::runtime::Builder::new_current_thread() +// .enable_all() +// .build() +// .expect("rt build"), +// ); +// let exec = rt.clone(); - let addr = spawn_server(&rt, &self); +// let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64; +// let req_len = if self.request_chunks > 0 { +// req_len * self.request_chunks as u64 +// } else { +// req_len +// }; +// let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64; +// b.bytes = bytes_per_iter; - let connector = HttpConnector::new(); - let client = hyper::Client::builder() - .http2_only(self.http2) - .http2_initial_stream_window_size(self.http2_stream_window) - .http2_initial_connection_window_size(self.http2_conn_window) - .http2_adaptive_window(self.http2_adaptive_window) - .build::<_, Body>(connector); +// let addr = spawn_server(&rt, &self); - let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap(); +// let connector = HttpConnector::new(); +// let client = hyper::Client::builder() +// .http2_only(self.http2) +// .http2_initial_stream_window_size(self.http2_stream_window) +// .http2_initial_connection_window_size(self.http2_conn_window) +// .http2_adaptive_window(self.http2_adaptive_window) +// .build::<_, Body>(connector); - let make_request = || { - let chunk_cnt = self.request_chunks; - let body = if chunk_cnt > 0 { - let (mut tx, body) = Body::channel(); - let chunk = self - .request_body - .expect("request_chunks means request_body"); - exec.spawn(async move { - for _ in 0..chunk_cnt { - tx.send_data(chunk.into()).await.expect("send_data"); - } - }); - body - } else { - self.request_body - .map(Body::from) - .unwrap_or_else(Body::empty) - }; - let mut req = Request::new(body); - *req.method_mut() = self.request_method.clone(); - *req.uri_mut() = url.clone(); - req - }; +// let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap(); - let send_request = |req: Request| { - let fut = client.request(req); - async { - let res = fut.await.expect("client wait"); - let mut body = res.into_body(); - while let Some(_chunk) = body.data().await {} - } - }; +// let make_request = || { +// let chunk_cnt = self.request_chunks; +// let body = if chunk_cnt > 0 { +// let (mut tx, body) = Body::channel(); +// let chunk = self +// .request_body +// .expect("request_chunks means request_body"); +// exec.spawn(async move { +// for _ in 0..chunk_cnt { +// tx.send_data(chunk.into()).await.expect("send_data"); +// } +// }); +// body +// } else { +// self.request_body +// .map(Body::from) +// .unwrap_or_else(Body::empty) +// }; +// let mut req = Request::new(body); +// *req.method_mut() = self.request_method.clone(); +// *req.uri_mut() = url.clone(); +// req +// }; - if self.parallel_cnt == 1 { - b.iter(|| { - let req = make_request(); - rt.block_on(send_request(req)); - }); - } else { - b.iter(|| { - let futs = (0..self.parallel_cnt).map(|_| { - let req = make_request(); - send_request(req) - }); - // Await all spawned futures becoming completed. - rt.block_on(join_all(futs)); - }); - } - } -} +// let send_request = |req: Request| { +// let fut = client.request(req); +// async { +// let res = fut.await.expect("client wait"); +// let mut body = res.into_body(); +// while let Some(_chunk) = body.data().await {} +// } +// }; -fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { - use hyper::service::{make_service_fn, service_fn}; - let addr = "127.0.0.1:0".parse().unwrap(); +// if self.parallel_cnt == 1 { +// b.iter(|| { +// let req = make_request(); +// rt.block_on(send_request(req)); +// }); +// } else { +// b.iter(|| { +// let futs = (0..self.parallel_cnt).map(|_| { +// let req = make_request(); +// send_request(req) +// }); +// // Await all spawned futures becoming completed. +// rt.block_on(join_all(futs)); +// }); +// } +// } +// } - let body = opts.response_body; - let srv = rt.block_on(async move { - Server::bind(&addr) - .http2_only(opts.http2) - .http2_initial_stream_window_size(opts.http2_stream_window) - .http2_initial_connection_window_size(opts.http2_conn_window) - .http2_adaptive_window(opts.http2_adaptive_window) - .serve(make_service_fn(move |_| async move { - Ok::<_, hyper::Error>(service_fn(move |req: Request| async move { - let mut req_body = req.into_body(); - while let Some(_chunk) = req_body.data().await {} - Ok::<_, hyper::Error>(Response::new(Body::from(body))) - })) - })) - }); - let addr = srv.local_addr(); - rt.spawn(async { - if let Err(err) = srv.await { - panic!("server error: {}", err); - } - }); - addr -} +// fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { +// use hyper::service::{make_service_fn, service_fn}; +// let addr = "127.0.0.1:0".parse().unwrap(); + +// let body = opts.response_body; +// let srv = rt.block_on(async move { +// Server::bind(&addr) +// .http2_only(opts.http2) +// .http2_initial_stream_window_size(opts.http2_stream_window) +// .http2_initial_connection_window_size(opts.http2_conn_window) +// .http2_adaptive_window(opts.http2_adaptive_window) +// .serve(make_service_fn(move |_| async move { +// Ok::<_, hyper::Error>(service_fn(move |req: Request| async move { +// let mut req_body = req.into_body(); +// while let Some(_chunk) = req_body.data().await {} +// Ok::<_, hyper::Error>(Response::new(Body::from(body))) +// })) +// })) +// }); +// let addr = srv.local_addr(); +// rt.spawn(async { +// if let Err(err) = srv.await { +// panic!("server error: {}", err); +// } +// }); +// addr +// } diff --git a/benches/pipeline.rs b/benches/pipeline.rs index d5cc67b4..f5b47d46 100644 --- a/benches/pipeline.rs +++ b/benches/pipeline.rs @@ -3,84 +3,87 @@ extern crate test; -use std::io::{Read, Write}; -use std::net::TcpStream; -use std::sync::mpsc; -use std::time::Duration; +// TODO: Reimplement hello_world_16 bench using hyper::server::conn +// (instead of Server). -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 hyper::{Body, Response, Server}; +// use tokio::sync::oneshot; -const PIPELINED_REQUESTS: usize = 16; +// use hyper::service::{make_service_fn, service_fn}; +// use hyper::{Body, Response, Server}; -#[bench] -fn hello_world_16(b: &mut test::Bencher) { - let _ = pretty_env_logger::try_init(); - let (_until_tx, until_rx) = oneshot::channel::<()>(); +// const PIPELINED_REQUESTS: usize = 16; - let addr = { - let (addr_tx, addr_rx) = mpsc::channel(); - std::thread::spawn(move || { - let addr = "127.0.0.1:0".parse().unwrap(); +// #[bench] +// fn hello_world_16(b: &mut test::Bencher) { +// let _ = pretty_env_logger::try_init(); +// let (_until_tx, until_rx) = oneshot::channel::<()>(); - let make_svc = make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(|_| async { - Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!"))) - })) - }); +// let addr = { +// let (addr_tx, addr_rx) = mpsc::channel(); +// std::thread::spawn(move || { +// let addr = "127.0.0.1:0".parse().unwrap(); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("rt build"); - let srv = rt.block_on(async move { - Server::bind(&addr) - .http1_pipeline_flush(true) - .serve(make_svc) - }); +// let make_svc = make_service_fn(|_| async { +// Ok::<_, hyper::Error>(service_fn(|_| async { +// Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!"))) +// })) +// }); - 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 { - until_rx.await.ok(); - }); +// addr_tx.send(srv.local_addr()).unwrap(); - rt.block_on(async { - if let Err(e) = graceful.await { - panic!("server error: {}", e); - } - }); - }); +// let graceful = srv.with_graceful_shutdown(async { +// until_rx.await.ok(); +// }); - addr_rx.recv().unwrap() - }; +// rt.block_on(async { +// if let Err(e) = graceful.await { +// panic!("server error: {}", e); +// } +// }); +// }); - let mut pipelined_reqs = Vec::new(); - for _ in 0..PIPELINED_REQUESTS { - pipelined_reqs.extend_from_slice(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"); - } +// addr_rx.recv().unwrap() +// }; - let total_bytes = { - 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") - .unwrap(); - let mut buf = Vec::new(); - tcp.read_to_end(&mut buf).unwrap() - } * PIPELINED_REQUESTS; +// let mut pipelined_reqs = Vec::new(); +// for _ in 0..PIPELINED_REQUESTS { +// pipelined_reqs.extend_from_slice(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n"); +// } - let mut tcp = TcpStream::connect(addr).unwrap(); - tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap(); - let mut buf = [0u8; 8192]; +// let total_bytes = { +// 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") +// .unwrap(); +// let mut buf = Vec::new(); +// tcp.read_to_end(&mut buf).unwrap() +// } * PIPELINED_REQUESTS; - b.bytes = (pipelined_reqs.len() + total_bytes) as u64; - b.iter(|| { - tcp.write_all(&pipelined_reqs).unwrap(); - let mut sum = 0; - while sum < total_bytes { - sum += tcp.read(&mut buf).unwrap(); - } - assert_eq!(sum, total_bytes); - }); -} +// let mut tcp = TcpStream::connect(addr).unwrap(); +// tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap(); +// let mut buf = [0u8; 8192]; + +// b.bytes = (pipelined_reqs.len() + total_bytes) as u64; +// b.iter(|| { +// tcp.write_all(&pipelined_reqs).unwrap(); +// let mut sum = 0; +// while sum < total_bytes { +// sum += tcp.read(&mut buf).unwrap(); +// } +// assert_eq!(sum, total_bytes); +// }); +// } diff --git a/benches/server.rs b/benches/server.rs index fed50c07..d610ad99 100644 --- a/benches/server.rs +++ b/benches/server.rs @@ -3,130 +3,133 @@ extern crate test; +// TODO: Reimplement bench_server using hyper::server::conn (instead +// of removed Server). + use std::io::{Read, Write}; use std::net::{TcpListener, TcpStream}; use std::sync::mpsc; -use std::time::Duration; +// use std::time::Duration; -use futures_util::{stream, StreamExt}; -use http_body_util::StreamBody; -use tokio::sync::oneshot; +// use futures_util::{stream, StreamExt}; +// use http_body_util::StreamBody; +// use tokio::sync::oneshot; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Response, Server}; +// use hyper::service::{make_service_fn, service_fn}; +// use hyper::{Response, Server}; -macro_rules! bench_server { - ($b:ident, $header:expr, $body:expr) => {{ - let _ = pretty_env_logger::try_init(); - let (_until_tx, until_rx) = oneshot::channel::<()>(); - let addr = { - let (addr_tx, addr_rx) = mpsc::channel(); - std::thread::spawn(move || { - let addr = "127.0.0.1:0".parse().unwrap(); - let make_svc = make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(|_| async { - Ok::<_, hyper::Error>( - Response::builder() - .header($header.0, $header.1) - .header("content-type", "text/plain") - .body($body()) - .unwrap(), - ) - })) - }); +// macro_rules! bench_server { +// ($b:ident, $header:expr, $body:expr) => {{ +// let _ = pretty_env_logger::try_init(); +// let (_until_tx, until_rx) = oneshot::channel::<()>(); +// let addr = { +// let (addr_tx, addr_rx) = mpsc::channel(); +// std::thread::spawn(move || { +// let addr = "127.0.0.1:0".parse().unwrap(); +// let make_svc = make_service_fn(|_| async { +// Ok::<_, hyper::Error>(service_fn(|_| async { +// Ok::<_, hyper::Error>( +// Response::builder() +// .header($header.0, $header.1) +// .header("content-type", "text/plain") +// .body($body()) +// .unwrap(), +// ) +// })) +// }); - let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() - .build() - .expect("rt build"); +// let rt = tokio::runtime::Builder::new_current_thread() +// .enable_all() +// .build() +// .expect("rt build"); - let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) }); +// let 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 { - until_rx.await.ok(); - }); - rt.block_on(async move { - if let Err(e) = graceful.await { - panic!("server error: {}", e); - } - }); - }); +// let graceful = srv.with_graceful_shutdown(async { +// until_rx.await.ok(); +// }); +// rt.block_on(async move { +// if let Err(e) = graceful.await { +// panic!("server error: {}", e); +// } +// }); +// }); - addr_rx.recv().unwrap() - }; +// addr_rx.recv().unwrap() +// }; - let total_bytes = { - 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") - .unwrap(); - let mut buf = Vec::new(); - tcp.read_to_end(&mut buf).unwrap() - }; +// let total_bytes = { +// 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") +// .unwrap(); +// let mut buf = Vec::new(); +// tcp.read_to_end(&mut buf).unwrap() +// }; - let mut tcp = TcpStream::connect(addr).unwrap(); - tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap(); - let mut buf = [0u8; 8192]; +// let mut tcp = TcpStream::connect(addr).unwrap(); +// tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap(); +// let mut buf = [0u8; 8192]; - $b.bytes = 35 + total_bytes as u64; - $b.iter(|| { - tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n") - .unwrap(); - let mut sum = 0; - while sum < total_bytes { - sum += tcp.read(&mut buf).unwrap(); - } - assert_eq!(sum, total_bytes); - }); - }}; -} +// $b.bytes = 35 + total_bytes as u64; +// $b.iter(|| { +// tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n") +// .unwrap(); +// let mut sum = 0; +// while sum < total_bytes { +// sum += tcp.read(&mut buf).unwrap(); +// } +// assert_eq!(sum, total_bytes); +// }); +// }}; +// } -fn body(b: &'static [u8]) -> hyper::Body { - b.into() -} +// fn body(b: &'static [u8]) -> hyper::Body { +// b.into() +// } -#[bench] -fn throughput_fixedsize_small_payload(b: &mut test::Bencher) { - bench_server!(b, ("content-length", "13"), || body(b"Hello, World!")) -} +// #[bench] +// fn throughput_fixedsize_small_payload(b: &mut test::Bencher) { +// bench_server!(b, ("content-length", "13"), || body(b"Hello, World!")) +// } -#[bench] -fn throughput_fixedsize_large_payload(b: &mut test::Bencher) { - bench_server!(b, ("content-length", "1000000"), || body( - &[b'x'; 1_000_000] - )) -} +// #[bench] +// fn throughput_fixedsize_large_payload(b: &mut test::Bencher) { +// bench_server!(b, ("content-length", "1000000"), || body( +// &[b'x'; 1_000_000] +// )) +// } -#[bench] -fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { - bench_server!(b, ("content-length", "1000000"), || { - static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; - StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) - }) -} +// #[bench] +// fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { +// bench_server!(b, ("content-length", "1000000"), || { +// static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; +// StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) +// }) +// } -#[bench] -fn throughput_chunked_small_payload(b: &mut test::Bencher) { - bench_server!(b, ("transfer-encoding", "chunked"), || body( - b"Hello, World!" - )) -} +// #[bench] +// fn throughput_chunked_small_payload(b: &mut test::Bencher) { +// bench_server!(b, ("transfer-encoding", "chunked"), || body( +// b"Hello, World!" +// )) +// } -#[bench] -fn throughput_chunked_large_payload(b: &mut test::Bencher) { - bench_server!(b, ("transfer-encoding", "chunked"), || body( - &[b'x'; 1_000_000] - )) -} +// #[bench] +// fn throughput_chunked_large_payload(b: &mut test::Bencher) { +// bench_server!(b, ("transfer-encoding", "chunked"), || body( +// &[b'x'; 1_000_000] +// )) +// } -#[bench] -fn throughput_chunked_many_chunks(b: &mut test::Bencher) { - bench_server!(b, ("transfer-encoding", "chunked"), || { - static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; - StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) - }) -} +// #[bench] +// fn throughput_chunked_many_chunks(b: &mut test::Bencher) { +// bench_server!(b, ("transfer-encoding", "chunked"), || { +// static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; +// StreamBody::new(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) +// }) +// } #[bench] fn raw_tcp_throughput_small_payload(b: &mut test::Bencher) { diff --git a/examples/client.rs b/examples/client.rs index 23337469..9a239b71 100644 --- a/examples/client.rs +++ b/examples/client.rs @@ -2,8 +2,9 @@ #![warn(rust_2018_idioms)] use std::env; -use hyper::{body::HttpBody as _, Client}; +use hyper::{body::HttpBody as _, Body, Request}; use tokio::io::{self, AsyncWriteExt as _}; +use tokio::net::TcpStream; // A simple type alias so as to DRY. type Result = std::result::Result>; @@ -33,9 +34,20 @@ async fn main() -> Result<()> { } async fn fetch_url(url: hyper::Uri) -> Result<()> { - let client = Client::new(); + let host = url.host().expect("uri has no host"); + let port = url.port_u16().unwrap_or(80); + let addr = format!("{}:{}", host, port); + let stream = TcpStream::connect(addr).await?; - let mut res = client.get(url).await?; + let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + let req = Request::builder().uri(url).body(Body::empty()).unwrap(); + let mut res = sender.send_request(req).await?; println!("Response: {}", res.status()); println!("Headers: {:#?}\n", res.headers()); diff --git a/examples/client_json.rs b/examples/client_json.rs index ef92f14b..addd7ab5 100644 --- a/examples/client_json.rs +++ b/examples/client_json.rs @@ -1,9 +1,10 @@ #![deny(warnings)] #![warn(rust_2018_idioms)] -use hyper::body::Buf; -use hyper::Client; +use hyper::Body; +use hyper::{body::Buf, Request}; use serde::Deserialize; +use tokio::net::TcpStream; // A simple type alias so as to DRY. type Result = std::result::Result>; @@ -22,10 +23,22 @@ async fn main() -> Result<()> { } async fn fetch_json(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 sender, conn) = hyper::client::conn::handshake(stream).await?; + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); // Fetch the url... - let res = client.get(url).await?; + let req = Request::builder().uri(url).body(Body::empty()).unwrap(); + let res = sender.send_request(req).await?; // asynchronously aggregate the chunks of the body let body = hyper::body::aggregate(res).await?; diff --git a/examples/echo.rs b/examples/echo.rs index 42404b5f..09785179 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -1,7 +1,11 @@ #![deny(warnings)] -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Method, Request, Response, Server, StatusCode}; +use std::net::SocketAddr; + +use hyper::server::conn::Http; +use hyper::service::service_fn; +use hyper::{Body, Method, Request, Response, StatusCode}; +use tokio::net::TcpListener; /// This is our service handler. It receives a Request, routes on its /// path, and returns a Future of a Response. @@ -51,15 +55,17 @@ async fn echo(req: Request) -> Result, hyper::Error> { #[tokio::main] async fn main() -> Result<(), Box> { - let addr = ([127, 0, 0, 1], 3000).into(); - - let service = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(echo)) }); - - let server = Server::bind(&addr).serve(service); + let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); + loop { + let (stream, _) = listener.accept().await?; - server.await?; - - Ok(()) + tokio::task::spawn(async move { + if let Err(err) = Http::new().serve_connection(stream, service_fn(echo)).await { + println!("Error serving connection: {:?}", err); + } + }); + } } diff --git a/examples/gateway.rs b/examples/gateway.rs index bd61130a..373b36ec 100644 --- a/examples/gateway.rs +++ b/examples/gateway.rs @@ -1,51 +1,63 @@ #![deny(warnings)] -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Client, Error, Server}; +use hyper::{server::conn::Http, service::service_fn}; use std::net::SocketAddr; +use tokio::net::{TcpListener, TcpStream}; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let in_addr = ([127, 0, 0, 1], 3001).into(); + let in_addr: SocketAddr = ([127, 0, 0, 1], 3001).into(); let out_addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); - let client_main = Client::new(); - let out_addr_clone = out_addr.clone(); - // The closure inside `make_service_fn` is run for each connection, - // creating a 'service' to handle requests for that specific connection. - let make_service = make_service_fn(move |_| { - let client = client_main.clone(); - - async move { - // This is the `Service` that will handle the connection. - // `service_fn` is a helper to convert a function that - // returns a Response into a `Service`. - Ok::<_, Error>(service_fn(move |mut req| { - let uri_string = format!( - "http://{}{}", - out_addr_clone, - req.uri() - .path_and_query() - .map(|x| x.as_str()) - .unwrap_or("/") - ); - let uri = uri_string.parse().unwrap(); - *req.uri_mut() = uri; - client.request(req) - })) - } - }); - - let server = Server::bind(&in_addr).serve(make_service); + let listener = TcpListener::bind(in_addr).await?; println!("Listening on http://{}", in_addr); println!("Proxying on http://{}", out_addr); - if let Err(e) = server.await { - eprintln!("server error: {}", e); + loop { + let (stream, _) = listener.accept().await?; + + // This is the `Service` that will handle the connection. + // `service_fn` is a helper to convert a function that + // returns a Response into a `Service`. + let service = service_fn(move |mut req| { + let uri_string = format!( + "http://{}{}", + out_addr_clone, + req.uri() + .path_and_query() + .map(|x| x.as_str()) + .unwrap_or("/") + ); + let uri = uri_string.parse().unwrap(); + *req.uri_mut() = uri; + + let host = req.uri().host().expect("uri has no host"); + let port = req.uri().port_u16().unwrap_or(80); + let addr = format!("{}:{}", host, port); + + async move { + let client_stream = TcpStream::connect(addr).await.unwrap(); + + let (mut sender, conn) = hyper::client::conn::handshake(client_stream).await?; + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + sender.send_request(req).await + } + }); + + tokio::task::spawn(async move { + if let Err(err) = Http::new().serve_connection(stream, service).await { + println!("Failed to servce connection: {:?}", err); + } + }); } } diff --git a/examples/hello.rs b/examples/hello.rs index e6c97b69..528b0c65 100644 --- a/examples/hello.rs +++ b/examples/hello.rs @@ -1,9 +1,12 @@ #![deny(warnings)] use std::convert::Infallible; +use std::net::SocketAddr; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Request, Response, Server}; +use hyper::server::conn::Http; +use hyper::service::service_fn; +use hyper::{Body, Request, Response}; +use tokio::net::TcpListener; async fn hello(_: Request) -> Result, Infallible> { Ok(Response::new(Body::from("Hello World!"))) @@ -13,22 +16,20 @@ async fn hello(_: Request) -> Result, Infallible> { pub async fn main() -> Result<(), Box> { pretty_env_logger::init(); - // For every connection, we must make a `Service` to handle all - // incoming HTTP requests on said connection. - let make_svc = make_service_fn(|_conn| { - // This is the `Service` that will handle the connection. - // `service_fn` is a helper to convert a function that - // returns a Response into a `Service`. - async { Ok::<_, Infallible>(service_fn(hello)) } - }); - - let addr = ([127, 0, 0, 1], 3000).into(); - - let server = Server::bind(&addr).serve(make_svc); + let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); + loop { + let (stream, _) = listener.accept().await?; - server.await?; - - Ok(()) + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .serve_connection(stream, service_fn(hello)) + .await + { + println!("Error serving connection: {:?}", err); + } + }); + } } diff --git a/examples/http_proxy.rs b/examples/http_proxy.rs index bc56db1a..d0699927 100644 --- a/examples/http_proxy.rs +++ b/examples/http_proxy.rs @@ -1,15 +1,14 @@ #![deny(warnings)] -use std::convert::Infallible; use std::net::SocketAddr; -use hyper::service::{make_service_fn, service_fn}; +use hyper::client::conn::Builder; +use hyper::server::conn::Http; +use hyper::service::service_fn; use hyper::upgrade::Upgraded; -use hyper::{Body, Client, Method, Request, Response, Server}; +use hyper::{Body, Method, Request, Response}; -use tokio::net::TcpStream; - -type HttpClient = Client; +use tokio::net::{TcpListener, TcpStream}; // To try this example: // 1. cargo run --example http_proxy @@ -19,32 +18,29 @@ type HttpClient = Client; // 3. send requests // $ curl -i https://www.some_domain.com/ #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); - let client = Client::builder() - .http1_title_case_headers(true) - .http1_preserve_header_case(true) - .build_http(); - - let make_service = make_service_fn(move |_| { - let client = client.clone(); - async move { Ok::<_, Infallible>(service_fn(move |req| proxy(client.clone(), req))) } - }); - - let server = Server::bind(&addr) - .http1_preserve_header_case(true) - .http1_title_case_headers(true) - .serve(make_service); - + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); - if let Err(e) = server.await { - eprintln!("server error: {}", e); + loop { + let (stream, _) = listener.accept().await?; + + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .http1_preserve_header_case(true) + .http1_title_case_headers(true) + .serve_connection(stream, service_fn(proxy)) + .await + { + println!("Failed to serve connection: {:?}", err); + } + }); } } -async fn proxy(client: HttpClient, req: Request) -> Result, hyper::Error> { +async fn proxy(req: Request) -> Result, hyper::Error> { println!("req: {:?}", req); if Method::CONNECT == req.method() { @@ -82,7 +78,24 @@ async fn proxy(client: HttpClient, req: Request) -> Result, Ok(resp) } } else { - client.request(req).await + let host = req.uri().host().expect("uri has no host"); + let port = req.uri().port_u16().unwrap_or(80); + let addr = format!("{}:{}", host, port); + + let stream = TcpStream::connect(addr).await.unwrap(); + + let (mut sender, conn) = Builder::new() + .http1_preserve_header_case(true) + .http1_title_case_headers(true) + .handshake(stream) + .await?; + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + sender.send_request(req).await } } diff --git a/examples/multi_server.rs b/examples/multi_server.rs index 811376a0..d69f65fb 100644 --- a/examples/multi_server.rs +++ b/examples/multi_server.rs @@ -1,9 +1,13 @@ #![deny(warnings)] #![warn(rust_2018_idioms)] +use std::net::SocketAddr; + use futures_util::future::join; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Request, Response, Server}; +use hyper::server::conn::Http; +use hyper::service::service_fn; +use hyper::{Body, Request, Response}; +use tokio::net::TcpListener; static INDEX1: &[u8] = b"The 1st service!"; static INDEX2: &[u8] = b"The 2nd service!"; @@ -20,16 +24,40 @@ async fn index2(_: Request) -> Result, hyper::Error> { async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let addr1 = ([127, 0, 0, 1], 1337).into(); - let addr2 = ([127, 0, 0, 1], 1338).into(); + let addr1: SocketAddr = ([127, 0, 0, 1], 1337).into(); + let addr2: SocketAddr = ([127, 0, 0, 1], 1338).into(); - let srv1 = Server::bind(&addr1).serve(make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(index1)) - })); + let srv1 = async move { + let listener = TcpListener::bind(addr1).await.unwrap(); + loop { + let (stream, _) = listener.accept().await.unwrap(); - let srv2 = Server::bind(&addr2).serve(make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(index2)) - })); + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .serve_connection(stream, service_fn(index1)) + .await + { + println!("Error serving connection: {:?}", err); + } + }); + } + }; + + let srv2 = async move { + let listener = TcpListener::bind(addr2).await.unwrap(); + loop { + let (stream, _) = listener.accept().await.unwrap(); + + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .serve_connection(stream, service_fn(index2)) + .await + { + println!("Error serving connection: {:?}", err); + } + }); + } + }; println!("Listening on http://{} and http://{}", addr1, addr2); diff --git a/examples/params.rs b/examples/params.rs index 87c23689..a0ca3e1b 100644 --- a/examples/params.rs +++ b/examples/params.rs @@ -1,10 +1,13 @@ // #![deny(warnings)] // FIXME: https://github.com/rust-lang/rust/issues/62411 #![warn(rust_2018_idioms)] -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Method, Request, Response, Server, StatusCode}; +use hyper::server::conn::Http; +use hyper::service::service_fn; +use hyper::{Body, Method, Request, Response, StatusCode}; +use tokio::net::TcpListener; use std::collections::HashMap; +use std::net::SocketAddr; use url::form_urlencoded; static INDEX: &[u8] = b"
Name:
Number:
"; @@ -102,15 +105,20 @@ async fn param_example(req: Request) -> Result, hyper::Erro async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let addr = ([127, 0, 0, 1], 1337).into(); - - let server = Server::bind(&addr).serve(make_service_fn(|_| async { - Ok::<_, hyper::Error>(service_fn(param_example)) - })); + let addr: SocketAddr = ([127, 0, 0, 1], 1337).into(); + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); + loop { + let (stream, _) = listener.accept().await?; - server.await?; - - Ok(()) + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .serve_connection(stream, service_fn(param_example)) + .await + { + println!("Error serving connection: {:?}", err); + } + }); + } } diff --git a/examples/send_file.rs b/examples/send_file.rs index 84562687..5bc90d57 100644 --- a/examples/send_file.rs +++ b/examples/send_file.rs @@ -1,26 +1,36 @@ #![deny(warnings)] -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Method, Request, Response, Result, Server, StatusCode}; +use std::net::SocketAddr; + +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 NOTFOUND: &[u8] = b"Not Found"; #[tokio::main] -async fn main() { +async fn main() -> std::result::Result<(), Box> { pretty_env_logger::init(); - let addr = "127.0.0.1:1337".parse().unwrap(); - - let make_service = - make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(response_examples)) }); - - let server = Server::bind(&addr).serve(make_service); + let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); - if let Err(e) = server.await { - eprintln!("server error: {}", e); + loop { + let (stream, _) = listener.accept().await?; + + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .serve_connection(stream, service_fn(response_examples)) + .await + { + println!("Failed to serve connection: {:?}", err); + } + }); } } diff --git a/examples/service_struct_impl.rs b/examples/service_struct_impl.rs index 8bc3f10f..0f1ca81c 100644 --- a/examples/service_struct_impl.rs +++ b/examples/service_struct_impl.rs @@ -1,7 +1,10 @@ +use hyper::server::conn::Http; use hyper::service::Service; -use hyper::{Body, Request, Response, Server}; +use hyper::{Body, Request, Response}; +use tokio::net::TcpListener; use std::future::Future; +use std::net::SocketAddr; use std::pin::Pin; use std::task::{Context, Poll}; @@ -9,13 +12,23 @@ type Counter = i32; #[tokio::main] async fn main() -> Result<(), Box> { - let addr = ([127, 0, 0, 1], 3000).into(); + let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); - let server = Server::bind(&addr).serve(MakeSvc { counter: 81818 }); + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); - server.await?; - Ok(()) + loop { + let (stream, _) = listener.accept().await?; + + tokio::task::spawn(async move { + if let Err(err) = Http::new() + .serve_connection(stream, Svc { counter: 81818 }) + .await + { + println!("Failed to serve connection: {:?}", err); + } + }); + } } struct Svc { @@ -54,23 +67,3 @@ impl Service> for Svc { Box::pin(async { res }) } } - -struct MakeSvc { - counter: Counter, -} - -impl Service for MakeSvc { - type Response = Svc; - type Error = hyper::Error; - type Future = Pin> + Send>>; - - fn poll_ready(&mut self, _: &mut Context) -> Poll> { - 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) - } -} diff --git a/examples/single_threaded.rs b/examples/single_threaded.rs index ba3e449c..788bbceb 100644 --- a/examples/single_threaded.rs +++ b/examples/single_threaded.rs @@ -1,13 +1,15 @@ #![deny(warnings)] +use hyper::server::conn::Http; use std::cell::Cell; +use std::net::SocketAddr; use std::rc::Rc; -use tokio::sync::oneshot; +use tokio::net::TcpListener; use hyper::body::{Bytes, HttpBody}; use hyper::header::{HeaderMap, HeaderValue}; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Error, Response, Server}; +use hyper::service::service_fn; +use hyper::{Error, Response}; use std::marker::PhantomData; use std::pin::Pin; use std::task::{Context, Poll}; @@ -46,7 +48,7 @@ impl HttpBody for Body { } } -fn main() { +fn main() -> Result<(), Box> { pretty_env_logger::init(); // Configure a runtime that runs everything on the current thread @@ -57,43 +59,39 @@ fn main() { // Combine it with a `LocalSet, which means it can spawn !Send futures... let local = tokio::task::LocalSet::new(); - local.block_on(&rt, run()); + local.block_on(&rt, run()) } -async fn run() { - let addr = ([127, 0, 0, 1], 3000).into(); +async fn run() -> Result<(), Box> { + let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); // Using a !Send request counter is fine on 1 thread... let counter = Rc::new(Cell::new(0)); - let make_service = make_service_fn(move |_| { + let listener = TcpListener::bind(addr).await?; + println!("Listening on http://{}", addr); + loop { + let (stream, _) = listener.accept().await?; + // For each connection, clone the counter to use in our service... let cnt = counter.clone(); - async move { - Ok::<_, Error>(service_fn(move |_| { - let prev = cnt.get(); - cnt.set(prev + 1); - let value = cnt.get(); - async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) } - })) - } - }); + let service = service_fn(move |_| { + let prev = cnt.get(); + cnt.set(prev + 1); + let value = cnt.get(); + async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) } + }); - let server = Server::bind(&addr).executor(LocalExec).serve(make_service); - - // Just shows that with_graceful_shutdown compiles with !Send, - // !Sync HttpBody. - let (_tx, rx) = oneshot::channel::<()>(); - let server = server.with_graceful_shutdown(async move { - rx.await.ok(); - }); - - println!("Listening on http://{}", addr); - - // The server would block on current thread to await !Send futures. - if let Err(e) = server.await { - eprintln!("server error: {}", e); + tokio::task::spawn_local(async move { + if let Err(err) = Http::new() + .with_executor(LocalExec) + .serve_connection(stream, service) + .await + { + println!("Error serving connection: {:?}", err); + } + }); } } diff --git a/examples/state.rs b/examples/state.rs index cb063b5a..3590e6c9 100644 --- a/examples/state.rs +++ b/examples/state.rs @@ -1,52 +1,46 @@ #![deny(warnings)] +use std::net::SocketAddr; use std::sync::{ atomic::{AtomicUsize, Ordering}, Arc, }; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Error, Response, Server}; +use hyper::{server::conn::Http, service::service_fn}; +use hyper::{Body, Error, Response}; +use tokio::net::TcpListener; #[tokio::main] -async fn main() { +async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let addr = ([127, 0, 0, 1], 3000).into(); + let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); // For the most basic of state, we just share a counter, that increments // with each request, and we send its value back in the response. let counter = Arc::new(AtomicUsize::new(0)); - // The closure inside `make_service_fn` is run for each connection, - // creating a 'service' to handle requests for that specific connection. - let make_service = make_service_fn(move |_| { - // While the state was moved into the make_service closure, - // we need to clone it here because this closure is called - // once for every connection. - // + let listener = TcpListener::bind(addr).await?; + println!("Listening on http://{}", addr); + loop { + let (stream, _) = listener.accept().await?; + // Each connection could send multiple requests, so // the `Service` needs a clone to handle later requests. let counter = counter.clone(); - async move { - // This is the `Service` that will handle the connection. - // `service_fn` is a helper to convert a function that - // returns a Response into a `Service`. - Ok::<_, Error>(service_fn(move |_req| { - // Get the current count, and also increment by 1, in a single - // atomic operation. - let count = counter.fetch_add(1, Ordering::AcqRel); - async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) } - })) + // This is the `Service` that will handle the connection. + // `service_fn` is a helper to convert a function that + // returns a Response into a `Service`. + let service = service_fn(move |_req| { + // Get the current count, and also increment by 1, in a single + // atomic operation. + let count = counter.fetch_add(1, Ordering::AcqRel); + async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) } + }); + + if let Err(err) = Http::new().serve_connection(stream, service).await { + println!("Error serving connection: {:?}", err); } - }); - - let server = Server::bind(&addr).serve(make_service); - - println!("Listening on http://{}", addr); - - if let Err(e) = server.await { - eprintln!("server error: {}", e); } } diff --git a/examples/tower_client.rs b/examples/tower_client.rs index 5a2a6e78..81b7488f 100644 --- a/examples/tower_client.rs +++ b/examples/tower_client.rs @@ -1,20 +1,20 @@ #![deny(warnings)] -use hyper::client::conn::Builder; -use hyper::client::connect::HttpConnector; -use hyper::client::service::Connect; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + use hyper::service::Service; -use hyper::{Body, Request}; +use hyper::{Body, Request, Response}; +use tokio::net::TcpStream; #[tokio::main] -async fn main() -> Result<(), Box> { +async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let mut mk_svc = Connect::new(HttpConnector::new(), Builder::new()); - let uri = "http://127.0.0.1:8080".parse::()?; - let mut svc = mk_svc.call(uri.clone()).await?; + let mut svc = Connector; let body = Body::empty(); @@ -25,3 +25,35 @@ async fn main() -> Result<(), Box> { Ok(()) } + +struct Connector; + +impl Service> for Connector { + type Response = Response; + type Error = Box; + type Future = Pin>>>; + + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> std::task::Poll> { + Poll::Ready(Ok(())) + } + + fn call(&mut self, req: Request) -> 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) + }) + } +} diff --git a/examples/tower_server.rs b/examples/tower_server.rs index 95414876..feaa3de0 100644 --- a/examples/tower_server.rs +++ b/examples/tower_server.rs @@ -1,10 +1,13 @@ #![deny(warnings)] +use std::net::SocketAddr; use std::task::{Context, Poll}; use futures_util::future; +use hyper::server::conn::Http; use hyper::service::Service; -use hyper::{Body, Request, Response, Server}; +use hyper::{Body, Request, Response}; +use tokio::net::TcpListener; const ROOT: &str = "/"; @@ -36,33 +39,22 @@ impl Service> for Svc { } } -pub struct MakeSvc; - -impl Service for MakeSvc { - type Response = Svc; - type Error = std::io::Error; - type Future = future::Ready>; - - fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { - Ok(()).into() - } - - fn call(&mut self, _: T) -> Self::Future { - future::ok(Svc) - } -} - #[tokio::main] async fn main() -> Result<(), Box> { pretty_env_logger::init(); - let addr = "127.0.0.1:1337".parse().unwrap(); - - let server = Server::bind(&addr).serve(MakeSvc); + let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); + let listener = TcpListener::bind(addr).await?; println!("Listening on http://{}", addr); - server.await?; + loop { + let (stream, _) = listener.accept().await?; - Ok(()) + tokio::task::spawn(async move { + if let Err(err) = Http::new().serve_connection(stream, Svc).await { + println!("Failed to serve connection: {:?}", err); + } + }); + } } diff --git a/examples/upgrades.rs b/examples/upgrades.rs index 38cfded3..de78eea7 100644 --- a/examples/upgrades.rs +++ b/examples/upgrades.rs @@ -3,13 +3,15 @@ // Note: `hyper::upgrade` docs link to this upgrade. use std::str; +use hyper::server::conn::Http; use tokio::io::{AsyncReadExt, AsyncWriteExt}; -use tokio::sync::oneshot; +use tokio::net::{TcpListener, TcpStream}; +use tokio::sync::watch; use hyper::header::{HeaderValue, UPGRADE}; -use hyper::service::{make_service_fn, service_fn}; +use hyper::service::service_fn; use hyper::upgrade::Upgraded; -use hyper::{Body, Client, Request, Response, Server, StatusCode}; +use hyper::{Body, Request, Response, StatusCode}; use std::net::SocketAddr; // A simple type alias so as to DRY. @@ -92,7 +94,17 @@ async fn client_upgrade_request(addr: SocketAddr) -> Result<()> { .body(Body::empty()) .unwrap(); - let res = Client::new().request(req).await?; + let stream = TcpStream::connect(addr).await?; + let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; + + tokio::task::spawn(async move { + if let Err(err) = conn.await { + println!("Connection failed: {:?}", err); + } + }); + + let res = sender.send_request(req).await?; + if res.status() != StatusCode::SWITCHING_PROTOCOLS { panic!("Our server didn't upgrade: {}", res.status()); } @@ -114,28 +126,52 @@ async fn main() { // For this example, we just make a server and our own client to talk to // it, so the exact port isn't important. Instead, let the OS give us an // unused port. - let addr = ([127, 0, 0, 1], 0).into(); + let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); - let make_service = - make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(server_upgrade)) }); - - let server = Server::bind(&addr).serve(make_service); + let listener = TcpListener::bind(addr).await.expect("failed to bind"); // We need the assigned address for the client to send it messages. - let addr = server.local_addr(); + let addr = listener.local_addr().unwrap(); // For this example, a oneshot is used to signal that after 1 request, // the server should be shutdown. - let (tx, rx) = oneshot::channel::<()>(); - let server = server.with_graceful_shutdown(async move { - rx.await.ok(); - }); + let (tx, mut rx) = watch::channel(false); // Spawn server on the default executor, // which is usually a thread-pool from tokio default runtime. tokio::task::spawn(async move { - if let Err(e) = server.await { - eprintln!("server error: {}", e); + loop { + tokio::select! { + res = listener.accept() => { + let (stream, _) = res.expect("Failed to accept"); + + let mut rx = rx.clone(); + tokio::task::spawn(async move { + let conn = Http::new().serve_connection(stream, service_fn(server_upgrade)); + + // Don't forget to enable upgrades on the connection. + let mut conn = conn.with_upgrades(); + + let mut conn = Pin::new(&mut conn); + + tokio::select! { + res = &mut conn => { + if let Err(err) = res { + println!("Error serving connection: {:?}", err); + return; + } + } + // Continue polling the connection after enabling graceful shutdown. + _ = rx.changed() => { + conn.graceful_shutdown(); + } + } + }); + } + _ = rx.changed() => { + break; + } + } } }); @@ -147,5 +183,5 @@ async fn main() { // Complete the oneshot so that the server stops // listening and the process can close down. - let _ = tx.send(()); + let _ = tx.send(true); } diff --git a/examples/web_api.rs b/examples/web_api.rs index 855ce5bc..7db23681 100644 --- a/examples/web_api.rs +++ b/examples/web_api.rs @@ -1,9 +1,12 @@ #![deny(warnings)] +use std::net::SocketAddr; + use bytes::Buf; -use hyper::client::HttpConnector; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{header, Body, Client, Method, Request, Response, Server, StatusCode}; +use hyper::server::conn::Http; +use hyper::service::service_fn; +use hyper::{header, Body, Method, Request, Response, StatusCode}; +use tokio::net::{TcpListener, TcpStream}; type GenericError = Box; type Result = std::result::Result; @@ -14,7 +17,7 @@ static NOTFOUND: &[u8] = b"Not Found"; static POST_DATA: &str = r#"{"original": "data"}"#; static URL: &str = "http://127.0.0.1:1337/json_api"; -async fn client_request_response(client: &Client) -> Result> { +async fn client_request_response() -> Result> { let req = Request::builder() .method(Method::POST) .uri(URL) @@ -22,7 +25,19 @@ async fn client_request_response(client: &Client) -> Result Result> { Ok(res) } -async fn response_examples( - req: Request, - client: Client, -) -> Result> { +async fn response_examples(req: Request) -> Result> { match (req.method(), req.uri().path()) { (&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())), - (&Method::GET, "/test.html") => client_request_response(&client).await, + (&Method::GET, "/test.html") => client_request_response().await, (&Method::POST, "/json_api") => api_post_response(req).await, (&Method::GET, "/json_api") => api_get_response().await, _ => { @@ -83,27 +95,19 @@ async fn response_examples( async fn main() -> Result<()> { pretty_env_logger::init(); - let addr = "127.0.0.1:1337".parse().unwrap(); - - // Share a `Client` with all `Service`s - let client = Client::new(); - - let new_service = make_service_fn(move |_| { - // Move a clone of `client` into the `service_fn`. - let client = client.clone(); - async { - Ok::<_, GenericError>(service_fn(move |req| { - // Clone again to ensure that client outlives this closure. - response_examples(req, client.to_owned()) - })) - } - }); - - let server = Server::bind(&addr).serve(new_service); + let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); + let listener = TcpListener::bind(&addr).await?; println!("Listening on http://{}", addr); + loop { + let (stream, _) = listener.accept().await?; - server.await?; + tokio::task::spawn(async move { + let service = service_fn(move |req| response_examples(req)); - Ok(()) + if let Err(err) = Http::new().serve_connection(stream, service).await { + println!("Failed to serve connection: {:?}", err); + } + }); + } } diff --git a/src/body/body.rs b/src/body/body.rs index 0ba63a4b..856aea3e 100644 --- a/src/body/body.rs +++ b/src/body/body.rs @@ -608,6 +608,7 @@ mod tests { ); } + #[cfg(not(miri))] #[tokio::test] async fn channel_abort() { let (tx, mut rx) = Body::channel(); @@ -618,6 +619,7 @@ mod tests { assert!(err.is_body_write_aborted(), "{:?}", err); } + #[cfg(not(miri))] #[tokio::test] async fn channel_abort_when_buffer_is_full() { let (mut tx, mut rx) = Body::channel(); @@ -644,6 +646,7 @@ mod tests { assert_eq!(chunk2, "chunk 2"); } + #[cfg(not(miri))] #[tokio::test] async fn channel_empty() { let (_, mut rx) = Body::channel(); diff --git a/src/body/to_bytes.rs b/src/body/to_bytes.rs index 62b15a54..3fbb859c 100644 --- a/src/body/to_bytes.rs +++ b/src/body/to_bytes.rs @@ -17,17 +17,11 @@ use super::HttpBody; /// # Example /// /// ``` -/// # #[cfg(all(feature = "client", feature = "tcp", any(feature = "http1", feature = "http2")))] /// # async fn doc() -> hyper::Result<()> { -/// use hyper::{body::HttpBody}; -/// -/// # let request = hyper::Request::builder() -/// # .method(hyper::Method::POST) -/// # .uri("http://httpbin.org/post") -/// # .header("content-type", "application/json") -/// # .body(hyper::Body::from(r#"{"library":"hyper"}"#)).unwrap(); -/// # let client = hyper::Client::new(); -/// let response = client.request(request).await?; +/// # use hyper::{Body, Response}; +/// # use hyper::body::HttpBody; +/// # +/// let response = Response::new(Body::from("response body")); /// /// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024; /// diff --git a/src/client/client.rs b/src/client/client.rs index cfdd267a..e605b450 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -15,10 +15,11 @@ use super::connect::{self, sealed::Connect, Alpn, Connected, Connection}; use super::pool::{ self, CheckoutIsClosedError, Key as PoolKey, Pool, Poolable, Pooled, Reservation, }; -#[cfg(feature = "tcp")] -use super::HttpConnector; 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; /// A Client to make outgoing HTTP requests. @@ -50,49 +51,8 @@ pub struct ResponseFuture { // ===== impl Client ===== -#[cfg(feature = "tcp")] -impl Client { - /// 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 { - Builder::default().build_http() - } -} - -#[cfg(feature = "tcp")] -impl Default for Client { - fn default() -> Client { - Client::new() - } -} - impl Client<(), Body> { /// 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] pub fn builder() -> Builder { Builder::default() @@ -113,20 +73,6 @@ where /// This requires that the `HttpBody` type have a `Default` implementation. /// It *should* return an "empty" version of itself, such that /// `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 where B: Default, @@ -142,26 +88,6 @@ where } /// 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) -> ResponseFuture { let is_http_connect = req.method() == Method::CONNECT; match req.version() { @@ -586,7 +512,7 @@ impl ResponseFuture { F: Future>> + Send + 'static, { 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). -/// -/// # 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"))))] #[derive(Clone)] pub struct Builder { @@ -1316,20 +1224,6 @@ impl Builder { self } - /// Builder a client with this configuration and the default `HttpConnector`. - #[cfg(feature = "tcp")] - pub fn build_http(&self) -> Client - 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`. pub fn build(&self, connector: C) -> Client where diff --git a/src/client/connect/dns.rs b/src/client/connect/dns.rs index e4465078..604476df 100644 --- a/src/client/connect/dns.rs +++ b/src/client/connect/dns.rs @@ -22,18 +22,9 @@ //! }); //! ``` use std::error::Error; -use std::future::Future; -use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; -use std::pin::Pin; +use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr}; use std::str::FromStr; -use std::task::{self, Poll}; -use std::{fmt, io, vec}; - -use tokio::task::JoinHandle; -use tower_service::Service; -use tracing::debug; - -pub(super) use self::sealed::Resolve; +use std::{fmt, vec}; /// A domain name to resolve into IP addresses. #[derive(Clone, Hash, Eq, PartialEq)] @@ -52,11 +43,6 @@ pub struct GaiAddrs { inner: SocketAddrs, } -/// A future to resolve a name returned by `GaiResolver`. -pub struct GaiFuture { - inner: JoinHandle>, -} - impl Name { pub(super) fn new(host: Box) -> Name { Name { host } @@ -108,63 +94,12 @@ impl GaiResolver { } } -impl Service for GaiResolver { - type Response = GaiAddrs; - type Error = io::Error; - type Future = GaiFuture; - - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { - Poll::Ready(Ok(())) - } - - fn call(&mut self, name: Name) -> Self::Future { - let blocking = tokio::task::spawn_blocking(move || { - debug!("resolving host={:?}", name.host); - (&*name.host, 0) - .to_socket_addrs() - .map(|i| SocketAddrs { iter: i }) - }); - - GaiFuture { inner: blocking } - } -} - impl fmt::Debug for GaiResolver { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.pad("GaiResolver") } } -impl Future for GaiFuture { - type Output = Result; - - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - Pin::new(&mut self.inner).poll(cx).map(|res| match res { - Ok(Ok(addrs)) => Ok(GaiAddrs { inner: addrs }), - Ok(Err(err)) => Err(err), - Err(join_err) => { - if join_err.is_cancelled() { - Err(io::Error::new(io::ErrorKind::Interrupted, join_err)) - } else { - panic!("gai background task failed: {:?}", join_err) - } - } - }) - } -} - -impl fmt::Debug for GaiFuture { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.pad("GaiFuture") - } -} - -impl Drop for GaiFuture { - fn drop(&mut self) { - self.inner.abort(); - } -} - impl Iterator for GaiAddrs { type Item = SocketAddr; @@ -190,22 +125,6 @@ impl SocketAddrs { } } - pub(super) fn try_parse(host: &str, port: u16) -> Option { - if let Ok(addr) = host.parse::() { - let addr = SocketAddrV4::new(addr, port); - return Some(SocketAddrs { - iter: vec![SocketAddr::V4(addr)].into_iter(), - }); - } - if let Ok(addr) = host.parse::() { - let addr = SocketAddrV6::new(addr, port, 0, 0); - return Some(SocketAddrs { - iter: vec![SocketAddr::V6(addr)].into_iter(), - }); - } - None - } - #[inline] fn filter(self, predicate: impl FnMut(&SocketAddr) -> bool) -> SocketAddrs { SocketAddrs::new(self.iter.filter(predicate).collect()) @@ -239,10 +158,6 @@ impl SocketAddrs { pub(super) fn is_empty(&self) -> bool { self.iter.as_slice().is_empty() } - - pub(super) fn len(&self) -> usize { - self.iter.as_slice().len() - } } impl Iterator for SocketAddrs { @@ -318,12 +233,12 @@ impl Future for TokioThreadpoolGaiFuture { */ mod sealed { - use super::{SocketAddr, Name}; + use super::{Name, SocketAddr}; use crate::common::{task, Future, Poll}; use tower_service::Service; // "Trait alias" for `Service` - pub trait Resolve { + pub(crate) trait Resolve { type Addrs: Iterator; type Error: Into>; type Future: Future>; @@ -352,14 +267,6 @@ mod sealed { } } -pub(super) async fn resolve(resolver: &mut R, name: Name) -> Result -where - R: Resolve, -{ - futures_util::future::poll_fn(|cx| resolver.poll_ready(cx)).await?; - resolver.resolve(name).await -} - #[cfg(test)] mod tests { use super::*; diff --git a/src/client/connect/http.rs b/src/client/connect/http.rs index afe7b155..6ca52942 100644 --- a/src/client/connect/http.rs +++ b/src/client/connect/http.rs @@ -1,23 +1,9 @@ use std::error::Error as StdError; use std::fmt; -use std::future::Future; -use std::io; -use std::marker::PhantomData; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr}; -use std::pin::Pin; use std::sync::Arc; -use std::task::{self, Poll}; 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; /// A connector for the `http` scheme. @@ -28,36 +14,13 @@ use super::{Connected, Connection}; /// /// Sets the [`HttpInfo`](HttpInfo) value on responses, which includes /// transport information such as the remote socket address used. -#[cfg_attr(docsrs, doc(cfg(feature = "tcp")))] #[derive(Clone)] -pub struct HttpConnector { +pub struct HttpConnector { config: Arc, - resolver: R, } /// 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::() -/// .map(|info| { -/// println!("remote addr = {}", info.remote_addr()); -/// }); -/// # Ok(()) -/// # } -/// ``` -/// /// # Note /// /// If a different connector is used besides [`HttpConnector`](HttpConnector), @@ -88,7 +51,20 @@ struct Config { impl HttpConnector { /// Construct a 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 { } */ -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 { - 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, - } - } - +impl HttpConnector { /// Option to enforce all `Uri`s have the `http` scheme. /// /// Enabled by default. @@ -240,135 +195,13 @@ impl HttpConnector { } } -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... -impl fmt::Debug for HttpConnector { +impl fmt::Debug for HttpConnector { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("HttpConnector").finish() } } -impl tower_service::Service for HttpConnector -where - R: Resolve + Clone + Send + Sync + 'static, - R::Future: Send, -{ - type Response = TcpStream; - type Error = ConnectError; - type Future = HttpConnecting; - - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { - 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 HttpConnector -where - R: Resolve, -{ - async fn call_async(&mut self, dst: Uri) -> Result { - 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 { /// Get the remote address of the transport used. 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>` directly - // so that users don't rely on it fitting in a `Pin>` 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 { - #[pin] - fut: BoxConnecting, - _marker: PhantomData, - } -} - -type ConnectResult = Result; -type BoxConnecting = Pin + Send>>; - -impl Future for HttpConnecting { - type Output = ConnectResult; - - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { - self.project().fut.poll(cx) - } -} - // Not publicly exported (so missing_docs doesn't trigger). -pub struct ConnectError { +pub(crate) struct ConnectError { msg: Box, cause: Option>, } -impl ConnectError { - fn new(msg: S, cause: E) -> ConnectError - where - S: Into>, - E: Into>, - { - ConnectError { - msg: msg.into(), - cause: Some(cause.into()), - } - } - - fn dns(cause: E) -> ConnectError - where - E: Into>, - { - ConnectError::new("dns error", cause) - } - - fn m(msg: S) -> impl FnOnce(E) -> ConnectError - where - S: Into>, - E: Into>, - { - move |cause| ConnectError::new(msg, cause) - } -} - impl fmt::Debug for ConnectError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { if let Some(ref cause) = self.cause { @@ -471,537 +250,3 @@ impl StdError for ConnectError { self.cause.as_ref().map(|e| &**e as _) } } - -struct ConnectingTcp<'a> { - preferred: ConnectingTcpRemote, - fallback: Option, - 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, -} - -impl ConnectingTcpRemote { - fn new(addrs: dns::SocketAddrs, connect_timeout: Option) -> 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 { - 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, - local_addr_ipv6: &Option, -) -> 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, -) -> Result>, 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 { - 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( - connector: C, - dst: Uri, - ) -> Result<::Connection, ::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, Option) { - 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) - } - } -} diff --git a/src/client/connect/mod.rs b/src/client/connect/mod.rs index 862a0e65..7a54048d 100644 --- a/src/client/connect/mod.rs +++ b/src/client/connect/mod.rs @@ -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 //! [`AsyncRead`]: tokio::io::AsyncRead //! [`AsyncWrite`]: tokio::io::AsyncWrite //! [`Connection`]: Connection +//! [`Service`]: crate::service::Service use std::fmt; use ::http::Extensions; -cfg_feature! { - #![feature = "tcp"] +pub use self::http::{HttpConnector, HttpInfo}; - pub use self::http::{HttpConnector, HttpInfo}; - - pub mod dns; - mod http; -} +pub mod dns; +mod http; cfg_feature! { #![any(feature = "http1", feature = "http2")] diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index bbc29385..21b111bb 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -301,6 +301,7 @@ mod tests { } } + #[cfg(not(miri))] #[tokio::test] async fn drop_receiver_sends_cancel_errors() { let _ = pretty_env_logger::try_init(); @@ -323,6 +324,7 @@ mod tests { } } + #[cfg(not(miri))] #[tokio::test] async fn sender_checks_for_want_on_send() { let (mut tx, mut rx) = channel::(); @@ -363,7 +365,6 @@ mod tests { use crate::{Body, Request, Response}; let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() .build() .unwrap(); let (mut tx, mut rx) = channel::, Response>(); @@ -386,7 +387,6 @@ mod tests { #[bench] fn giver_queue_not_ready(b: &mut test::Bencher) { let rt = tokio::runtime::Builder::new_current_thread() - .enable_all() .build() .unwrap(); let (_tx, mut rx) = channel::(); diff --git a/src/client/mod.rs b/src/client/mod.rs index 734bda88..e4499ec7 100644 --- a/src/client/mod.rs +++ b/src/client/mod.rs @@ -26,30 +26,6 @@ //! For a small example program simply fetching a URL, take a look at the //! [full client example](https://github.com/hyperium/hyper/blob/master/examples/client.rs). //! -//! ``` -//! # #[cfg(all(feature = "tcp", feature = "client", any(feature = "http1", feature = "http2")))] -//! # async fn fetch_httpbin() -> hyper::Result<()> { -//! use hyper::{body::HttpBody as _, Client, Uri}; -//! -//! let client = Client::new(); -//! -//! // Make a GET /ip to 'http://httpbin.org' -//! let res = client.get(Uri::from_static("http://httpbin.org/ip")).await?; -//! -//! // And then, if the request gets a response... -//! println!("status: {}", res.status()); -//! -//! // Concatenate the body stream into a single buffer... -//! let buf = hyper::body::to_bytes(res).await?; -//! -//! println!("body: {:?}", buf); -//! # Ok(()) -//! # } -//! # fn main () {} -//! ``` - -#[cfg(feature = "tcp")] -pub use self::connect::HttpConnector; pub mod connect; #[cfg(all(test, feature = "runtime"))] diff --git a/src/client/tests.rs b/src/client/tests.rs index 0a281a63..144349e5 100644 --- a/src/client/tests.rs +++ b/src/client/tests.rs @@ -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::(io::Error::new(io::ErrorKind::Other, "expect me")) - }); - - let client = Client::builder().build::<_, crate::Body>(connector); - let _ = client - .get("http://example.local/and/a/path".parse().unwrap()) - .await - .expect_err("response should fail"); -} - /* // FIXME: re-implement tests with `async/await` #[test] diff --git a/src/common/exec.rs b/src/common/exec.rs index 76f61618..29f374de 100644 --- a/src/common/exec.rs +++ b/src/common/exec.rs @@ -44,13 +44,13 @@ impl Exec { { match *self { Exec::Default => { - #[cfg(feature = "tcp")] + #[cfg(feature = "runtime")] { tokio::task::spawn(fut); } - #[cfg(not(feature = "tcp"))] + + #[cfg(not(feature = "runtime"))] { - // If no runtime, we need an executor! panic!("executor must be set") } } diff --git a/src/lib.rs b/src/lib.rs index e1a70955..7a99ce96 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -51,7 +51,6 @@ //! - `server`: Enables the HTTP `server`. //! - `runtime`: Enables convenient integration with `tokio`, providing //! connectors and acceptors for TCP, and a default executor. -//! - `tcp`: Enables convenient implementations over TCP (using tokio). //! //! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section diff --git a/src/server/conn.rs b/src/server/conn.rs index 90d92edd..0649f22d 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -324,7 +324,7 @@ impl Http { self } - /// Set a timeout for reading client request headers. If a client does not + /// Set a timeout for reading client request headers. If a client does not /// transmit the entire header within this time, the connection is closed. /// /// Default is None. diff --git a/src/server/mod.rs b/src/server/mod.rs index f2fdee76..ad9b8013 100644 --- a/src/server/mod.rs +++ b/src/server/mod.rs @@ -15,143 +15,8 @@ //! be executed to start serving requests. //! //! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default. -//! -//! ## Examples -//! -//! ```no_run -//! use std::convert::Infallible; -//! use std::net::SocketAddr; -//! use hyper::{Body, Request, Response, Server}; -//! use hyper::service::{make_service_fn, service_fn}; -//! -//! async fn handle(_req: Request) -> Result, 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) -> Result, 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 -//! ) -> Result, 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 conn; -#[cfg(feature = "tcp")] -mod tcp; pub use self::server::Server; diff --git a/src/server/server.rs b/src/server/server.rs index e3058da4..61ef4ab4 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,8 +1,6 @@ use std::error::Error as StdError; use std::fmt; -#[cfg(feature = "tcp")] -use std::net::{SocketAddr, TcpListener as StdTcpListener}; -#[cfg(any(feature = "tcp", feature = "http1"))] +#[cfg(feature = "http1")] use std::time::Duration; use pin_project_lite::pin_project; @@ -10,8 +8,6 @@ use tokio::io::{AsyncRead, AsyncWrite}; use tracing::trace; use super::accept::Accept; -#[cfg(all(feature = "tcp"))] -use super::tcp::AddrIncoming; use crate::body::{Body, HttpBody}; use crate::common::exec::Exec; use crate::common::exec::{ConnStreamExec, NewSvcExec}; @@ -60,48 +56,6 @@ impl Server { } } -#[cfg(feature = "tcp")] -#[cfg_attr( - docsrs, - doc(cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))) -)] -impl Server { - /// 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 { - 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> { - AddrIncoming::new(addr).map(Server::builder) - } - - /// Create a new instance from a `std::net::TcpListener` instance. - pub fn from_tcp(listener: StdTcpListener) -> Result, 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 Server { - /// 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"))))] impl Server where @@ -116,40 +70,6 @@ where { /// Prepares a server to handle graceful shutdown when the provided future /// completes. - /// - /// # Example - /// - /// ``` - /// # fn main() {} - /// # #[cfg(feature = "tcp")] - /// # async fn run() { - /// # use hyper::{Body, Response, Server, Error}; - /// # use hyper::service::{make_service_fn, service_fn}; - /// # let make_service = make_service_fn(|_| async { - /// # Ok::<_, Error>(service_fn(|_req| async { - /// # Ok::<_, Error>(Response::new(Body::from("Hello World"))) - /// # })) - /// # }); - /// // Make a server from the previous examples... - /// let server = Server::bind(&([127, 0, 0, 1], 3000).into()) - /// .serve(make_service); - /// - /// // Prepare some signal for when the server should start shutting down... - /// let (tx, rx) = tokio::sync::oneshot::channel::<()>(); - /// let graceful = server - /// .with_graceful_shutdown(async { - /// rx.await.ok(); - /// }); - /// - /// // Await the `server` receiving the signal... - /// if let Err(e) = graceful.await { - /// eprintln!("server error: {}", e); - /// } - /// - /// // And later, trigger the signal by calling `tx.send(())`. - /// let _ = tx.send(()); - /// # } - /// ``` pub fn with_graceful_shutdown(self, signal: F) -> Graceful where F: Future, @@ -237,8 +157,6 @@ impl fmt::Debug for Server { #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] impl Builder { /// 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_) -> Self { Builder { incoming, protocol } } @@ -504,35 +422,6 @@ impl Builder { } /// 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(self, make_service: S) -> Server where I: Accept, @@ -553,49 +442,6 @@ impl Builder { } } -#[cfg(feature = "tcp")] -#[cfg_attr( - docsrs, - doc(cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))) -)] -impl Builder { - /// 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) -> 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. // // The regular `hyper::Server` just uses a `NoopWatcher`, which does diff --git a/src/server/tcp.rs b/src/server/tcp.rs deleted file mode 100644 index 3fc45ce0..00000000 --- a/src/server/tcp.rs +++ /dev/null @@ -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, - tcp_nodelay: bool, - timeout: Option>>, -} - -impl AddrIncoming { - pub(super) fn new(addr: &SocketAddr) -> crate::Result { - 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 { - // 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 { - AddrIncoming::new(addr) - } - - /// Creates a new `AddrIncoming` from an existing `tokio::net::TcpListener`. - pub fn from_listener(listener: TcpListener) -> crate::Result { - 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) -> &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> { - // 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>> { - 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() - } -} diff --git a/src/service/make.rs b/src/service/make.rs index f178ecec..1d4347a7 100644 --- a/src/service/make.rs +++ b/src/service/make.rs @@ -100,41 +100,6 @@ where } /// 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| 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: F) -> MakeServiceFn where F: FnMut(&Target) -> Ret, diff --git a/tests/client.rs b/tests/client.rs index 2f9b3ab8..34085478 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -5,6 +5,7 @@ extern crate matches; use std::convert::Infallible; +use std::fmt; use std::io::{Read, Write}; use std::net::{SocketAddr, TcpListener}; use std::pin::Pin; @@ -12,9 +13,11 @@ use std::task::{Context, Poll}; use std::thread; use std::time::Duration; +use http::uri::PathAndQuery; use http_body_util::{BodyExt, StreamBody}; 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 futures_channel::oneshot; @@ -31,6 +34,71 @@ fn tcp_connect(addr: &SocketAddr) -> impl Future 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 for Error { + fn from(err: std::io::Error) -> Self { + Self::Io(err) + } +} + +impl From for Error { + fn from(err: hyper::Error) -> Self { + Self::Hyper(err) + } +} + macro_rules! test { ( name: $name:ident, @@ -110,7 +178,7 @@ macro_rules! test { let _ = pretty_env_logger::try_init(); let rt = support::runtime(); - let err: ::hyper::Error = test! { + let err: Error = test! { INNER; name: $name, runtime: &rt, @@ -123,7 +191,7 @@ macro_rules! test { )*}, }.unwrap_err(); - fn infer_closure bool>(f: F) -> F { f } + fn infer_closure bool>(f: F) -> F { f } let closure = infer_closure($err); if !closure(&err) { @@ -151,22 +219,123 @@ macro_rules! test { let addr = server.local_addr().expect("local_addr"); 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)] let mut body = BodyExt::boxed(http_body_util::Empty::::new()); let mut req_builder = Request::builder(); $( test!(@client_request; req_builder, body, addr, $c_req_prop: $c_req_val); )* - let req = req_builder + let mut req = req_builder .body(body) .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(); @@ -188,7 +357,7 @@ macro_rules! test { assert_eq!(s(&buf[..n]), expected); inc.write_all($server_reply.as_ref()).expect("write_all"); - let _ = tx.send(Ok::<_, hyper::Error>(())); + let _ = tx.send(Ok::<_, Error>(())); }).expect("thread spawn"); let rx = rx.expect("thread panicked"); @@ -197,10 +366,10 @@ macro_rules! test { // Always check that HttpConnector has set the "extra" info... let extra = resp .extensions_mut() - .remove::<::hyper::client::connect::HttpInfo>() + .remove::() .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 }) @@ -1174,7 +1343,7 @@ mod dispatch_impl { use super::support; use hyper::body::HttpBody; - use hyper::client::connect::{Connected, Connection, HttpConnector}; + use hyper::client::connect::{Connected, Connection}; use hyper::Client; #[test] @@ -1186,10 +1355,7 @@ mod dispatch_impl { let addr = server.local_addr().unwrap(); let rt = support::runtime(); let (closes_tx, closes) = mpsc::channel(10); - let client = Client::builder().build(DebugConnector::with_http_and_closes( - HttpConnector::new(), - closes_tx, - )); + let client = Client::builder().build(DebugConnector::with_closes(closes_tx)); let (tx1, rx1) = oneshot::channel(); @@ -1259,10 +1425,7 @@ mod dispatch_impl { }); let res = { - let client = Client::builder().build(DebugConnector::with_http_and_closes( - HttpConnector::new(), - closes_tx, - )); + let client = Client::builder().build(DebugConnector::with_closes(closes_tx)); let req = Request::builder() .uri(&*format!("http://{}/a", addr)) @@ -1322,10 +1485,7 @@ mod dispatch_impl { support::runtime().block_on(client_drop_rx.into_future()) }); - let client = Client::builder().build(DebugConnector::with_http_and_closes( - HttpConnector::new(), - closes_tx, - )); + let client = Client::builder().build(DebugConnector::with_closes(closes_tx)); let req = Request::builder() .uri(&*format!("http://{}/a", addr)) @@ -1385,10 +1545,7 @@ mod dispatch_impl { }); let res = { - let client = Client::builder().build(DebugConnector::with_http_and_closes( - HttpConnector::new(), - closes_tx, - )); + let client = Client::builder().build(DebugConnector::with_closes(closes_tx)); let req = Request::builder() .uri(&*format!("http://{}/a", addr)) @@ -1438,10 +1595,7 @@ mod dispatch_impl { let rx = rx1.expect("thread panicked"); let res = { - let client = Client::builder().build(DebugConnector::with_http_and_closes( - HttpConnector::new(), - closes_tx, - )); + let client = Client::builder().build(DebugConnector::with_closes(closes_tx)); let req = Request::builder() .uri(&*format!("http://{}/a", addr)) @@ -1490,9 +1644,9 @@ mod dispatch_impl { let _ = rx2.recv(); }); - let client = Client::builder().pool_max_idle_per_host(0).build( - DebugConnector::with_http_and_closes(HttpConnector::new(), closes_tx), - ); + let client = Client::builder() + .pool_max_idle_per_host(0) + .build(DebugConnector::with_closes(closes_tx)); let req = Request::builder() .uri(&*format!("http://{}/a", addr)) @@ -1536,10 +1690,7 @@ mod dispatch_impl { let _ = tx1.send(()); }); - let client = Client::builder().build(DebugConnector::with_http_and_closes( - HttpConnector::new(), - closes_tx, - )); + let client = Client::builder().build(DebugConnector::with_closes(closes_tx)); let req = Request::builder() .uri(&*format!("http://{}/a", addr)) @@ -2085,7 +2236,6 @@ mod dispatch_impl { #[derive(Clone)] struct DebugConnector { - http: HttpConnector, closes: mpsc::Sender<()>, connects: Arc, is_proxy: bool, @@ -2094,14 +2244,12 @@ mod dispatch_impl { impl DebugConnector { fn new() -> DebugConnector { - let http = HttpConnector::new(); 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 { - http, closes, connects: Arc::new(AtomicUsize::new(0)), is_proxy: false, @@ -2117,12 +2265,11 @@ mod dispatch_impl { impl hyper::service::Service for DebugConnector { type Response = DebugStream; - type Error = >::Error; + type Error = std::io::Error; type Future = Pin> + Send>>; - fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { - // don't forget to check inner service is ready :) - hyper::service::Service::::poll_ready(&mut self.http, cx) + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { + Poll::Ready(Ok(())) } fn call(&mut self, dst: Uri) -> Self::Future { @@ -2130,12 +2277,20 @@ mod dispatch_impl { let closes = self.closes.clone(); let is_proxy = self.is_proxy; let is_alpn_h2 = self.alpn_h2; - Box::pin(self.http.call(dst).map_ok(move |tcp| DebugStream { - tcp, - on_drop: closes, - is_alpn_h2, - is_proxy, - })) + + Box::pin(async move { + let host = dst.host().expect("no host in uri"); + let port = dst.port_u16().expect("no port in uri"); + + 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 { 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 { connected.negotiated_h2() @@ -2744,27 +2899,45 @@ mod conn { #[tokio::test] async fn http2_detect_conn_eof() { use futures_util::future; - use hyper::service::{make_service_fn, service_fn}; - use hyper::{Response, Server}; let _ = pretty_env_logger::try_init(); - let server = Server::bind(&([127, 0, 0, 1], 0).into()) - .http2_only(true) - .serve(make_service_fn(|_| async move { - Ok::<_, hyper::Error>(service_fn(|_req| { - future::ok::<_, hyper::Error>(Response::new(Body::empty())) - })) - })); - let addr = server.local_addr(); - let (shdn_tx, shdn_rx) = oneshot::channel(); + let addr = SocketAddr::from(([127, 0, 0, 1], 0)); + let listener = TkTcpListener::bind(addr).await.unwrap(); + + let addr = listener.local_addr().unwrap(); + let (shdn_tx, mut shdn_rx) = tokio::sync::watch::channel(false); tokio::task::spawn(async move { - server - .with_graceful_shutdown(async move { - let _ = shdn_rx.await; - }) - .await - .expect("server") + use hyper::server::conn::Http; + use hyper::service::service_fn; + + loop { + tokio::select! { + res = listener.accept() => { + let (stream, _) = res.unwrap(); + + let service = service_fn(|_:Request| future::ok::, 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"); @@ -2796,7 +2969,7 @@ mod conn { .expect("client poll ready after"); // Trigger the server shutdown... - let _ = shdn_tx.send(()); + let _ = shdn_tx.send(true); // Allow time for graceful shutdown roundtrips... tokio::time::sleep(Duration::from_millis(100)).await; diff --git a/tests/server.rs b/tests/server.rs index 0e98b40f..40e01b1d 100644 --- a/tests/server.rs +++ b/tests/server.rs @@ -21,15 +21,14 @@ use h2::client::SendRequest; use h2::{RecvStream, SendStream}; use http::header::{HeaderName, HeaderValue}; use http_body_util::{combinators::BoxBody, BodyExt, StreamBody}; -use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; -use tokio::net::{TcpListener, TcpStream as TkTcpStream}; +use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; +use tokio::io::{AsyncReadExt, AsyncWriteExt}; +use tokio::net::{TcpListener as TkTcpListener, TcpListener, TcpStream as TkTcpStream}; -use hyper::body::HttpBody as _; -use hyper::client::Client; +use hyper::body::HttpBody; use hyper::server::conn::Http; -use hyper::server::Server; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Request, Response, StatusCode, Version}; +use hyper::service::service_fn; +use hyper::{Body, Method, Request, Response, StatusCode, Uri, Version}; mod support; @@ -320,15 +319,11 @@ mod response_body_lengths { #[tokio::test] async fn http2_auto_response_with_known_length() { - use http_body::Body; - let server = serve(); let addr_str = format!("http://{}", server.addr()); server.reply().body("Hello, World!"); - let client = Client::builder() - .http2_only(true) - .build_http::(); + let client = TestClient::new().http2_only(); let uri = addr_str .parse::() .expect("server addr should parse"); @@ -340,8 +335,6 @@ mod response_body_lengths { #[tokio::test] async fn http2_auto_response_with_conflicting_lengths() { - use http_body::Body; - let server = serve(); let addr_str = format!("http://{}", server.addr()); server @@ -349,9 +342,7 @@ mod response_body_lengths { .header("content-length", "10") .body("Hello, World!"); - let client = Client::builder() - .http2_only(true) - .build_http::(); + let client = TestClient::new().http2_only(); let uri = addr_str .parse::() .expect("server addr should parse"); @@ -363,15 +354,11 @@ mod response_body_lengths { #[tokio::test] async fn http2_implicit_empty_size_hint() { - use http_body::Body; - let server = serve(); let addr_str = format!("http://{}", server.addr()); server.reply(); - let client = Client::builder() - .http2_only(true) - .build_http::(); + let client = TestClient::new().http2_only(); let uri = addr_str .parse::() .expect("server addr should parse"); @@ -1480,8 +1467,6 @@ async fn header_read_timeout_slow_writes_multiple_requests() { #[tokio::test] async fn upgrades() { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); @@ -1539,8 +1524,6 @@ async fn upgrades() { #[tokio::test] async fn http_connect() { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); @@ -1675,15 +1658,19 @@ async fn upgrades_ignored() { future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty())) }); - let (socket, _) = listener.accept().await.unwrap(); - Http::new() - .serve_connection(socket, svc) - .with_upgrades() - .await - .expect("server task"); + loop { + let (socket, _) = listener.accept().await.unwrap(); + tokio::task::spawn(async move { + Http::new() + .serve_connection(socket, svc) + .with_upgrades() + .await + .expect("server task"); + }); + } }); - let client = hyper::Client::new(); + let client = TestClient::new(); let url = format!("http://{}/", addr); let make_req = || { @@ -1705,8 +1692,6 @@ async fn upgrades_ignored() { #[tokio::test] async fn http_connect_new() { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); @@ -1771,8 +1756,6 @@ async fn http_connect_new() { #[tokio::test] async fn h2_connect() { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); @@ -1843,7 +1826,6 @@ async fn h2_connect() { async fn h2_connect_multiplex() { use futures_util::stream::FuturesUnordered; use futures_util::StreamExt; - use tokio::io::{AsyncReadExt, AsyncWriteExt}; let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); @@ -1954,8 +1936,6 @@ async fn h2_connect_multiplex() { #[tokio::test] async fn h2_connect_large_body() { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); @@ -2031,8 +2011,6 @@ async fn h2_connect_large_body() { #[tokio::test] async fn h2_connect_empty_frames() { - use tokio::io::{AsyncReadExt, AsyncWriteExt}; - let _ = pretty_env_logger::try_init(); let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); let addr = listener.local_addr().unwrap(); @@ -2225,8 +2203,8 @@ fn http1_response_with_http2_version() { server.reply().version(hyper::Version::HTTP_2); + let client = TestClient::new(); rt.block_on({ - let client = Client::new(); let uri = addr_str.parse().expect("server addr should parse"); client.get(uri) }) @@ -2240,10 +2218,8 @@ fn try_h2() { let rt = support::runtime(); + let client = TestClient::new().http2_only(); rt.block_on({ - let client = Client::builder() - .http2_only(true) - .build_http::(); let uri = addr_str.parse().expect("server addr should parse"); client.get(uri).map_ok(|_| ()).map_err(|_e| ()) @@ -2260,10 +2236,8 @@ fn http1_only() { let rt = support::runtime(); + let client = TestClient::new().http2_only(); rt.block_on({ - let client = Client::builder() - .http2_only(true) - .build_http::(); let uri = addr_str.parse().expect("server addr should parse"); 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"); dbg!("start"); - let err = dbg!(Client::builder() - .http2_only(true) - .build_http::() + let err = dbg!(TestClient::new() + .http2_only() .get(uri) .await .expect_err("client.get")); @@ -2314,9 +2287,8 @@ fn http2_body_user_error_sends_reset_reason() { let err: hyper::Error = rt .block_on(async move { - let client = Client::builder() - .http2_only(true) - .build_http::(); + let client = TestClient::new().http2_only(); + let uri = addr_str.parse().expect("server addr should parse"); 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 server = hyper::Server::bind(&([127, 0, 0, 1], 0).into()) - .http2_only(true) - .serve(make_service_fn(|_| async move { - Ok::<_, BoxError>(Http2ReadyErrorSvc) - })); + let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) + .await + .unwrap(); - let addr_str = format!("http://{}", server.local_addr()); + let addr_str = format!("http://{}", listener.local_addr().unwrap()); tokio::task::spawn(async move { - server.await.expect("server"); + loop { + tokio::select! { + res = listener.accept() => { + let (stream, _) = res.unwrap(); + + tokio::task::spawn(async move { + let mut http = Http::new(); + http.http2_only(true); + + let service = Http2ReadyErrorSvc; + http.serve_connection(stream, service).await.unwrap(); + }); + } + } + } }); let uri = addr_str.parse().expect("server addr should parse"); - let err = dbg!(Client::builder() - .http2_only(true) - .build_http::() + let err = dbg!(TestClient::new() + .http2_only() .get(uri) .await .expect_err("client.get should fail")); @@ -2948,9 +2931,9 @@ impl ServeOptions { let (addr_tx, addr_rx) = mpsc::channel(); let (msg_tx, msg_rx) = mpsc::channel(); let (reply_tx, reply_rx) = spmc::channel(); - let (shutdown_tx, shutdown_rx) = oneshot::channel(); + let (shutdown_tx, mut shutdown_rx) = oneshot::channel(); - let addr = ([127, 0, 0, 1], 0).into(); + let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); let thread_name = format!( "test-server-{}", @@ -2961,36 +2944,46 @@ impl ServeOptions { let thread = thread::Builder::new() .name(thread_name) .spawn(move || { - support::runtime() - .block_on(async move { - let service = make_service_fn(|_| { - let msg_tx = msg_tx.clone(); - let reply_rx = reply_rx.clone(); - future::ok::<_, BoxError>(TestService { - tx: msg_tx, - reply: reply_rx, - }) - }); + support::runtime().block_on(async move { + let listener = TkTcpListener::bind(addr).await.unwrap(); - let builder = Server::bind(&addr); + addr_tx + .send(listener.local_addr().unwrap()) + .expect("server addr tx"); - #[cfg(feature = "http1")] - let builder = builder - .http1_only(_options.http1_only) - .http1_keepalive(_options.keep_alive) - .http1_pipeline_flush(_options.pipeline); + loop { + let msg_tx = msg_tx.clone(); + let reply_rx = reply_rx.clone(); - let server = builder.serve(service); + tokio::select! { + res = listener.accept() => { + let (stream, _) = res.unwrap(); - addr_tx.send(server.local_addr()).expect("server addr tx"); + tokio::task::spawn(async move { + let mut http = Http::new(); - server - .with_graceful_shutdown(async { - let _ = shutdown_rx.await; - }) - .await - }) - .expect("serve()"); + #[cfg(feature = "http1")] + let http = http + .http1_only(_options.http1_only) + .http1_keep_alive(_options.keep_alive) + .pipeline_flush(_options.pipeline); + + let msg_tx = msg_tx.clone(); + let reply_rx = reply_rx.clone(); + let service = TestService { + tx: msg_tx, + reply: reply_rx, + }; + + http.serve_connection(stream, service).await.unwrap(); + }); + } + _ = &mut shutdown_rx => { + break; + } + } + } + }) }) .expect("thread spawn"); @@ -3119,3 +3112,49 @@ impl Drop for Dropped { self.0.store(true, Ordering::SeqCst); } } + +struct TestClient { + http2_only: bool, +} + +impl TestClient { + fn new() -> Self { + Self { http2_only: false } + } + + fn http2_only(mut self) -> Self { + self.http2_only = true; + self + } + + async fn get(&self, uri: Uri) -> Result, hyper::Error> { + self.request( + Request::builder() + .uri(uri) + .method(Method::GET) + .body(Body::empty()) + .unwrap(), + ) + .await + } + + async fn request(&self, req: Request) -> Result, 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 + } +} diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 6b3c8f44..781e71ba 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -6,9 +6,12 @@ use std::sync::{ Arc, Mutex, }; -use hyper::client::HttpConnector; -use hyper::service::{make_service_fn, service_fn}; -use hyper::{Body, Client, Request, Response, Server, Version}; +use hyper::client::conn::Builder; +use hyper::server::conn::Http; +use tokio::net::{TcpListener, TcpStream}; + +use hyper::service::service_fn; +use hyper::{Body, Request, Response, Version}; pub use futures_util::{ future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _, @@ -326,16 +329,20 @@ async fn async_test(cfg: __TestConfig) { Version::HTTP_11 }; - let connector = HttpConnector::new(); - let client = Client::builder() - .http2_only(cfg.client_version == 2) - .build::<_, Body>(connector); + let http2_only = cfg.server_version == 2; let serve_handles = Arc::new(Mutex::new(cfg.server_msgs)); + let listener = TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) + .await + .unwrap(); + + let mut addr = listener.local_addr().unwrap(); + let expected_connections = cfg.connections; - let mut cnt = 0; - let new_service = make_service_fn(move |_| { + tokio::task::spawn(async move { + let mut cnt = 0; + cnt += 1; assert!( cnt <= expected_connections, @@ -344,98 +351,108 @@ async fn async_test(cfg: __TestConfig) { cnt ); - // Move a clone into the service_fn - let serve_handles = serve_handles.clone(); - future::ok::<_, hyper::Error>(service_fn(move |req: Request| { - let (sreq, sres) = serve_handles.lock().unwrap().remove(0); + loop { + let (stream, _) = listener.accept().await.expect("server error"); - assert_eq!(req.uri().path(), sreq.uri, "client path"); - assert_eq!(req.method(), &sreq.method, "client method"); - assert_eq!(req.version(), version, "client version"); - for func in &sreq.headers { - func(&req.headers()); - } - let sbody = sreq.body; - hyper::body::to_bytes(req).map_ok(move |body| { - assert_eq!(body.as_ref(), sbody.as_slice(), "client body"); + // Move a clone into the service_fn + let serve_handles = serve_handles.clone(); + let service = service_fn(move |req: Request| { + let (sreq, sres) = serve_handles.lock().unwrap().remove(0); - let mut res = Response::builder() - .status(sres.status) - .body(Body::from(sres.body)) - .expect("Response::build"); - *res.headers_mut() = sres.headers; - res - }) - })) + assert_eq!(req.uri().path(), sreq.uri, "client path"); + assert_eq!(req.method(), &sreq.method, "client method"); + assert_eq!(req.version(), version, "client version"); + for func in &sreq.headers { + func(&req.headers()); + } + let sbody = sreq.body; + hyper::body::to_bytes(req).map_ok(move |body| { + assert_eq!(body.as_ref(), sbody.as_slice(), "client body"); + + let mut res = Response::builder() + .status(sres.status) + .body(Body::from(sres.body)) + .expect("Response::build"); + *res.headers_mut() = sres.headers; + res + }) + }); + + tokio::task::spawn(async move { + Http::new() + .http2_only(http2_only) + .serve_connection(stream, service) + .await + .expect("server error"); + }); + } }); - let server = hyper::Server::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) - .http2_only(cfg.server_version == 2) - .serve(new_service); - - let mut addr = server.local_addr(); - - tokio::task::spawn(server.map(|result| { - result.expect("server error"); - })); - if cfg.proxy { let (proxy_addr, proxy) = naive_proxy(ProxyConfig { connections: cfg.connections, dst: addr, version: cfg.server_version, - }); + }) + .await; tokio::task::spawn(proxy); addr = proxy_addr; } - let make_request = Arc::new( - move |client: &Client, creq: __CReq, cres: __CRes| { - let uri = format!("http://{}{}", addr, creq.uri); - let mut req = Request::builder() - .method(creq.method) - .uri(uri) - //.headers(creq.headers) - .body(creq.body.into()) - .expect("Request::build"); - *req.headers_mut() = creq.headers; - let cstatus = cres.status; - let cheaders = cres.headers; - let cbody = cres.body; + let make_request = Arc::new(move |creq: __CReq, cres: __CRes| { + let uri = format!("http://{}{}", addr, creq.uri); + let mut req = Request::builder() + .method(creq.method) + .uri(uri) + //.headers(creq.headers) + .body(creq.body.into()) + .expect("Request::build"); + *req.headers_mut() = creq.headers; + let cstatus = cres.status; + let cheaders = cres.headers; + let cbody = cres.body; - client - .request(req) - .and_then(move |res| { - assert_eq!(res.status(), cstatus, "server status"); - assert_eq!(res.version(), version, "server version"); - for func in &cheaders { - func(&res.headers()); - } - hyper::body::to_bytes(res) - }) - .map_ok(move |body| { - assert_eq!(body.as_ref(), cbody.as_slice(), "server body"); - }) - .map(|res| res.expect("client error")) - }, - ); + async move { + let stream = TcpStream::connect(addr).await.unwrap(); + + let (mut sender, conn) = hyper::client::conn::Builder::new() + .http2_only(http2_only) + .handshake::(stream) + .await + .unwrap(); + + tokio::task::spawn(async move { + if let Err(err) = conn.await { + panic!("{:?}", err); + } + }); + + let res = sender.send_request(req).await.unwrap(); + + assert_eq!(res.status(), cstatus, "server status"); + assert_eq!(res.version(), version, "server version"); + for func in &cheaders { + func(&res.headers()); + } + + let body = hyper::body::to_bytes(res).await.unwrap(); + + assert_eq!(body.as_ref(), cbody.as_slice(), "server body"); + } + }); let client_futures: Pin + Send>> = if cfg.parallel { let mut client_futures = vec![]; for (creq, cres) in cfg.client_msgs { - client_futures.push(make_request(&client, creq, cres)); + client_futures.push(make_request(creq, cres)); } - drop(client); Box::pin(future::join_all(client_futures).map(|_| ())) } else { - let mut client_futures: Pin> + Send>> = - Box::pin(future::ready(client)); + let mut client_futures: Pin + Send>> = + Box::pin(future::ready(())); for (creq, cres) in cfg.client_msgs { let mk_request = make_request.clone(); - client_futures = Box::pin(client_futures.then(move |client| { - let fut = mk_request(&client, creq, cres); - fut.map(move |()| client) - })); + client_futures = Box::pin(client_futures.then(move |_| mk_request(creq, cres))); } Box::pin(client_futures.map(|_| ())) }; @@ -449,27 +466,75 @@ struct ProxyConfig { version: usize, } -fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future) { - let client = Client::builder() - .http2_only(cfg.version == 2) - .build_http::(); - +async fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future) { let dst_addr = cfg.dst; let max_connections = cfg.connections; let counter = AtomicUsize::new(0); + let http2_only = cfg.version == 2; - let srv = Server::bind(&([127, 0, 0, 1], 0).into()).serve(make_service_fn(move |_| { - let prev = counter.fetch_add(1, Ordering::Relaxed); - assert!(max_connections > prev, "proxy max connections"); - let client = client.clone(); - future::ok::<_, hyper::Error>(service_fn(move |mut req| { - let uri = format!("http://{}{}", dst_addr, req.uri().path()) - .parse() - .expect("proxy new uri parse"); - *req.uri_mut() = uri; - client.request(req) - })) - })); - let proxy_addr = srv.local_addr(); - (proxy_addr, srv.map(|res| res.expect("proxy error"))) + let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) + .await + .unwrap(); + + let proxy_addr = listener.local_addr().unwrap(); + + let fut = async move { + tokio::task::spawn(async move { + let prev = counter.fetch_add(1, Ordering::Relaxed); + assert!(max_connections > prev, "proxy max connections"); + + loop { + let (stream, _) = listener.accept().await.unwrap(); + + let service = service_fn(move |mut req| { + async move { + let uri = format!("http://{}{}", dst_addr, req.uri().path()) + .parse() + .expect("proxy new uri parse"); + *req.uri_mut() = uri; + + // Make the client request + let uri = req.uri().host().expect("uri has no host"); + let port = req.uri().port_u16().expect("uri has no port"); + + let stream = TcpStream::connect(format!("{}:{}", uri, port)) + .await + .unwrap(); + + let mut builder = Builder::new(); + builder.http2_only(http2_only); + let (mut sender, conn) = builder.handshake(stream).await.unwrap(); + + tokio::task::spawn(async move { + if let Err(err) = conn.await { + panic!("{:?}", err); + } + }); + + let resp = sender.send_request(req).await?; + + let (mut parts, body) = resp.into_parts(); + + // Remove the Connection header for HTTP/1.1 proxy connections. + if !http2_only { + parts.headers.remove("Connection"); + } + + let mut builder = Response::builder().status(parts.status); + *builder.headers_mut().unwrap() = parts.headers; + + Result::, hyper::Error>::Ok(builder.body(body).unwrap()) + } + }); + + Http::new() + .http2_only(http2_only) + .serve_connection(stream, service) + .await + .unwrap(); + } + }); + }; + + (proxy_addr, fut) }