Compare commits
	
		
			41 Commits
		
	
	
		
			v0.14.18-p
			...
			5e20688398
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | 5e20688398 | ||
|  | bb3af17ce1 | ||
|  | 889fa2d872 | ||
|  | cd32454403 | ||
|  | c558647762 | ||
|  | 3c7bef3b6f | ||
|  | 491b076bca | ||
|  | ca99e23e27 | ||
|  | 0c8ee93d7f | ||
|  | d4b5bd4ee6 | ||
|  | 509672aada | ||
|  | 09e35668e5 | ||
|  | 3660443108 | ||
|  | ce72f73464 | ||
|  | a563404033 | ||
|  | 5fa113ebff | ||
|  | e9cab49e6e | ||
|  | 2c7344a65b | ||
|  | b2052a433f | ||
|  | f12d4d4aa8 | ||
|  | 4545c3ef19 | ||
|  | f8e2a83194 | ||
|  | a929df843e | ||
|  | 3a755a632d | ||
|  | 4678be9e81 | ||
|  | 775fac114b | ||
|  | a32658c1ae | ||
|  | 67b73138f1 | ||
|  | faf24c6ad8 | ||
|  | 6a35c175f2 | ||
|  | 89598dfcfe | ||
|  | 78de8914ea | ||
|  | e1138d716d | ||
|  | 8834d5a2a7 | ||
|  | ffbf610b16 | ||
|  | d2c945e8ed | ||
|  | 311ba2b97e | ||
|  | 1d895b8dfc | ||
|  | e3ee1de32d | ||
|  | dd08d9c3e5 | ||
|  | 0fec1c8737 | 
							
								
								
									
										24
									
								
								.github/workflows/CI.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/CI.yml
									
									
									
									
										vendored
									
									
								
							| @@ -16,6 +16,7 @@ jobs: | ||||
|       - style | ||||
|       - test | ||||
|       - msrv | ||||
|       - miri | ||||
|       - features | ||||
|       - ffi | ||||
|       - ffi-header | ||||
| @@ -100,7 +101,7 @@ jobs: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         rust: | ||||
|           - 1.49 # keep in sync with MSRV.md dev doc | ||||
|           - 1.56 # keep in sync with MSRV.md dev doc | ||||
|  | ||||
|         os: | ||||
|           - ubuntu-latest | ||||
| @@ -124,6 +125,27 @@ jobs: | ||||
|           command: check | ||||
|           args: --features full | ||||
|  | ||||
|   miri: | ||||
|     name: Test with Miri | ||||
|     needs: [style] | ||||
|     runs-on: ubuntu-latest | ||||
|  | ||||
|     steps: | ||||
|       - name: Checkout | ||||
|         uses: actions/checkout@v1 | ||||
|  | ||||
|       - name: Install Rust | ||||
|         uses: actions-rs/toolchain@v1 | ||||
|         with: | ||||
|           profile: minimal | ||||
|           toolchain: nightly | ||||
|           components: miri | ||||
|           override: true | ||||
|  | ||||
|       - name: Test | ||||
|         # Can't enable tcp feature since Miri does not support the tokio runtime | ||||
|         run: MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test --features http1,http2,client,server,nightly | ||||
|  | ||||
|   features: | ||||
|     name: features | ||||
|     needs: [style] | ||||
|   | ||||
							
								
								
									
										6
									
								
								.github/workflows/bench.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/bench.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,9 +11,9 @@ jobs: | ||||
|     strategy: | ||||
|       matrix: | ||||
|         bench: | ||||
|           - connect | ||||
|           - end_to_end | ||||
|           - pipeline | ||||
|           #- connect | ||||
|           #- end_to_end | ||||
|           #- pipeline | ||||
|     steps: | ||||
|       - uses: actions/checkout@v2 | ||||
|  | ||||
|   | ||||
							
								
								
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,3 +1,26 @@ | ||||
| ### v0.14.19 (2022-05-27) | ||||
|  | ||||
|  | ||||
| #### Bug Fixes | ||||
|  | ||||
| * **http1:** fix preserving header case without enabling ffi (#2820) ([6a35c175](https://github.com/hyperium/hyper/commit/6a35c175f2b416851518b5831c2c7827d6dbd822)) | ||||
| * **server:** don't add implicit content-length to HEAD responses (#2836) ([67b73138](https://github.com/hyperium/hyper/commit/67b73138f110979f3c77ef7b56588f018837e592)) | ||||
|  | ||||
|  | ||||
| #### Features | ||||
|  | ||||
| * **server:** | ||||
|   * add `Connection::http2_max_header_list_size` option (#2828) ([a32658c1](https://github.com/hyperium/hyper/commit/a32658c1ae7f1261fa234a767df963be4fc63521), closes [#2826](https://github.com/hyperium/hyper/issues/2826)) | ||||
|   * add `AddrStream::local_addr()` (#2816) ([ffbf610b](https://github.com/hyperium/hyper/commit/ffbf610b1631cabfacb20886270e3c137fa93800), closes [#2773](https://github.com/hyperium/hyper/issues/2773)) | ||||
|  | ||||
|  | ||||
| #### Breaking Changes | ||||
|  | ||||
| * **ffi (unstable):** | ||||
|   * `hyper_clientconn_options_new` no longer sets the `http1_preserve_header_case` connection option by default. | ||||
|     Users should now call `hyper_clientconn_options_set_preserve_header_case` if they desire that functionality. ([78de8914](https://github.com/hyperium/hyper/commit/78de8914eadeab4b9a2c71a82c77b2ce33fe6c74)) | ||||
|  | ||||
|  | ||||
| ### v0.14.18 (2022-03-22) | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -5,6 +5,7 @@ You want to contribute? You're awesome! Don't know where to start? Check the [li | ||||
| [easy tag]: https://github.com/hyperium/hyper/issues?q=label%3AE-easy+is%3Aopen | ||||
|  | ||||
|  | ||||
| ## Pull Requests | ||||
| ## [Pull Requests](./docs/PULL_REQUESTS.md) | ||||
|  | ||||
| - [Submitting a Pull Request](./docs/PULL_REQUESTS.md#submitting-a-pull-request) | ||||
| - [Commit Guidelines](./docs/COMMITS.md) | ||||
|   | ||||
							
								
								
									
										24
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | ||||
| [package] | ||||
| name = "hyper" | ||||
| version = "0.14.18" | ||||
| version = "1.0.0-dev.0" | ||||
| description = "A fast and correct HTTP library." | ||||
| readme = "README.md" | ||||
| homepage = "https://hyper.rs" | ||||
| @@ -12,6 +12,8 @@ keywords = ["http", "hyper", "hyperium"] | ||||
| categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"] | ||||
| edition = "2018" | ||||
|  | ||||
| publish = false # no accidents while in dev | ||||
|  | ||||
| include = [ | ||||
|   "Cargo.toml", | ||||
|   "LICENSE", | ||||
| @@ -25,7 +27,8 @@ futures-core = { version = "0.3", default-features = false } | ||||
| futures-channel = "0.3" | ||||
| futures-util = { version = "0.3", default-features = false } | ||||
| http = "0.2" | ||||
| http-body = "0.4" | ||||
| http-body = { git = "https://github.com/hyperium/http-body", branch = "master" } | ||||
| http-body-util = { git = "https://github.com/hyperium/http-body", branch = "master" } | ||||
| httpdate = "1.0" | ||||
| httparse = "1.6" | ||||
| h2 = { version = "0.3.9", optional = true } | ||||
| @@ -61,7 +64,7 @@ tokio = { version = "1", features = [ | ||||
|     "test-util", | ||||
| ] } | ||||
| tokio-test = "0.4" | ||||
| tokio-util = { version = "0.6", features = ["codec"] } | ||||
| tokio-util = { version = "0.7", features = ["codec"] } | ||||
| tower = { version = "0.4", features = ["make", "util"] } | ||||
| url = "2.2" | ||||
|  | ||||
| @@ -78,7 +81,6 @@ full = [ | ||||
|     "http1", | ||||
|     "http2", | ||||
|     "server", | ||||
|     "stream", | ||||
|     "runtime", | ||||
| ] | ||||
|  | ||||
| @@ -90,17 +92,8 @@ http2 = ["h2"] | ||||
| client = [] | ||||
| server = [] | ||||
|  | ||||
| # `impl Stream` for things | ||||
| stream = [] | ||||
|  | ||||
| # Tokio support | ||||
| runtime = [ | ||||
|     "tcp", | ||||
|     "tokio/rt", | ||||
|     "tokio/time", | ||||
| ] | ||||
| tcp = [ | ||||
|     "socket2", | ||||
|     "tokio/net", | ||||
|     "tokio/rt", | ||||
|     "tokio/time", | ||||
| @@ -188,11 +181,6 @@ name = "state" | ||||
| path = "examples/state.rs" | ||||
| required-features = ["full"] | ||||
|  | ||||
| [[example]] | ||||
| name = "tower_client" | ||||
| path = "examples/tower_client.rs" | ||||
| required-features = ["full"] | ||||
|  | ||||
| [[example]] | ||||
| name = "tower_server" | ||||
| path = "examples/tower_server.rs" | ||||
|   | ||||
| @@ -8,6 +8,10 @@ | ||||
|  | ||||
| A **fast** and **correct** HTTP implementation for Rust. | ||||
|  | ||||
| > **Note**: hyper's [master](https://github.com/hyperium/hyper) branch is | ||||
| > currently preparing breaking changes. For the most recently *released* code, | ||||
| > look to the [0.14.x branch](https://github.com/hyperium/hyper/tree/0.14.x). | ||||
|  | ||||
| - HTTP/1 and HTTP/2 | ||||
| - Asynchronous design | ||||
| - Leading in performance | ||||
|   | ||||
| @@ -6,7 +6,7 @@ extern crate test; | ||||
| use bytes::Buf; | ||||
| use futures_util::stream; | ||||
| use futures_util::StreamExt; | ||||
| use hyper::body::Body; | ||||
| use http_body_util::StreamBody; | ||||
|  | ||||
| macro_rules! bench_stream { | ||||
|     ($bencher:ident, bytes: $bytes:expr, count: $count:expr, $total_ident:ident, $body_pat:pat, $block:expr) => {{ | ||||
| @@ -20,9 +20,10 @@ macro_rules! bench_stream { | ||||
|  | ||||
|         $bencher.iter(|| { | ||||
|             rt.block_on(async { | ||||
|                 let $body_pat = Body::wrap_stream( | ||||
|                 let $body_pat = StreamBody::new( | ||||
|                     stream::iter(__s.iter()).map(|&s| Ok::<_, std::convert::Infallible>(s)), | ||||
|                 ); | ||||
|  | ||||
|                 $block; | ||||
|             }); | ||||
|         }); | ||||
|   | ||||
| @@ -3,35 +3,38 @@ | ||||
|  | ||||
| extern crate test; | ||||
|  | ||||
| use http::Uri; | ||||
| use hyper::client::connect::HttpConnector; | ||||
| use hyper::service::Service; | ||||
| use std::net::SocketAddr; | ||||
| use tokio::net::TcpListener; | ||||
| // TODO: Reimplement http_connector bench using hyper::client::conn | ||||
| // (instead of removed HttpConnector). | ||||
|  | ||||
| #[bench] | ||||
| fn http_connector(b: &mut test::Bencher) { | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let rt = tokio::runtime::Builder::new_current_thread() | ||||
|         .enable_all() | ||||
|         .build() | ||||
|         .expect("rt build"); | ||||
|     let listener = rt | ||||
|         .block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0)))) | ||||
|         .expect("bind"); | ||||
|     let addr = listener.local_addr().expect("local_addr"); | ||||
|     let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse"); | ||||
|     let mut connector = HttpConnector::new(); | ||||
| // use http::Uri; | ||||
| // use hyper::client::connect::HttpConnector; | ||||
| // use hyper::service::Service; | ||||
| // use std::net::SocketAddr; | ||||
| // use tokio::net::TcpListener; | ||||
|  | ||||
|     rt.spawn(async move { | ||||
|         loop { | ||||
|             let _ = listener.accept().await; | ||||
|         } | ||||
|     }); | ||||
| // #[bench] | ||||
| // fn http_connector(b: &mut test::Bencher) { | ||||
| //     let _ = pretty_env_logger::try_init(); | ||||
| //     let rt = tokio::runtime::Builder::new_current_thread() | ||||
| //         .enable_all() | ||||
| //         .build() | ||||
| //         .expect("rt build"); | ||||
| //     let listener = rt | ||||
| //         .block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0)))) | ||||
| //         .expect("bind"); | ||||
| //     let addr = listener.local_addr().expect("local_addr"); | ||||
| //     let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse"); | ||||
| //     let mut connector = HttpConnector::new(); | ||||
|  | ||||
|     b.iter(|| { | ||||
|         rt.block_on(async { | ||||
|             connector.call(dst.clone()).await.expect("connect"); | ||||
|         }); | ||||
|     }); | ||||
| } | ||||
| //     rt.spawn(async move { | ||||
| //         loop { | ||||
| //             let _ = listener.accept().await; | ||||
| //         } | ||||
| //     }); | ||||
|  | ||||
| //     b.iter(|| { | ||||
| //         rt.block_on(async { | ||||
| //             connector.call(dst.clone()).await.expect("connect"); | ||||
| //         }); | ||||
| //     }); | ||||
| // } | ||||
|   | ||||
| @@ -3,380 +3,383 @@ | ||||
|  | ||||
| extern crate test; | ||||
|  | ||||
| use std::net::SocketAddr; | ||||
| // TODO: Reimplement Opts::bench using hyper::server::conn and hyper::client::conn | ||||
| // (instead of Server and HttpClient). | ||||
|  | ||||
| use futures_util::future::join_all; | ||||
| // use std::net::SocketAddr; | ||||
|  | ||||
| use hyper::client::HttpConnector; | ||||
| use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server}; | ||||
| // use futures_util::future::join_all; | ||||
|  | ||||
| // HTTP1 | ||||
| // use hyper::client::HttpConnector; | ||||
| // use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server}; | ||||
|  | ||||
| #[bench] | ||||
| fn http1_consecutive_x1_empty(b: &mut test::Bencher) { | ||||
|     opts().bench(b) | ||||
| } | ||||
| // // HTTP1 | ||||
|  | ||||
| #[bench] | ||||
| fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { | ||||
|     opts() | ||||
|         .method(Method::POST) | ||||
|         .request_body(&[b's'; 10]) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_consecutive_x1_empty(b: &mut test::Bencher) { | ||||
| //     opts().bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 100]; | ||||
|     opts() | ||||
|         .method(Method::POST) | ||||
|         .request_body(body) | ||||
|         .response_body(body) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { | ||||
| //     opts() | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(&[b's'; 10]) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
|     opts() | ||||
|         .method(Method::POST) | ||||
|         .request_body(body) | ||||
|         .response_body(body) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 100]; | ||||
| //     opts() | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(body) | ||||
| //         .response_body(body) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_parallel_x10_empty(b: &mut test::Bencher) { | ||||
|     opts().parallel(10).bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
| //     opts() | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(body) | ||||
| //         .response_body(body) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
|     opts() | ||||
|         .parallel(10) | ||||
|         .method(Method::POST) | ||||
|         .request_body(body) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_parallel_x10_empty(b: &mut test::Bencher) { | ||||
| //     opts().parallel(10).bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 10]; | ||||
|     opts() | ||||
|         .parallel(10) | ||||
|         .method(Method::POST) | ||||
|         .request_chunks(body, 100) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
| //     opts() | ||||
| //         .parallel(10) | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(body) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 1]; | ||||
|     opts().parallel(10).response_body(body).bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 10]; | ||||
| //     opts() | ||||
| //         .parallel(10) | ||||
| //         .method(Method::POST) | ||||
| //         .request_chunks(body, 100) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
|     opts().parallel(10).response_body(body).bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 1]; | ||||
| //     opts().parallel(10).response_body(body).bench(b) | ||||
| // } | ||||
|  | ||||
| // HTTP2 | ||||
| // #[bench] | ||||
| // fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
| //     opts().parallel(10).response_body(body).bench(b) | ||||
| // } | ||||
|  | ||||
| const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1; | ||||
| // // HTTP2 | ||||
|  | ||||
| #[bench] | ||||
| fn http2_consecutive_x1_empty(b: &mut test::Bencher) { | ||||
|     opts().http2().bench(b) | ||||
| } | ||||
| // const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1; | ||||
|  | ||||
| #[bench] | ||||
| fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) { | ||||
|     opts() | ||||
|         .http2() | ||||
|         .method(Method::POST) | ||||
|         .request_body(&[b's'; 10]) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_consecutive_x1_empty(b: &mut test::Bencher) { | ||||
| //     opts().http2().bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 100]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .method(Method::POST) | ||||
|         .request_body(body) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) { | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(&[b's'; 10]) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_empty(b: &mut test::Bencher) { | ||||
|     opts().http2().parallel(10).bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 100]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(body) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .parallel(10) | ||||
|         .method(Method::POST) | ||||
|         .request_body(body) | ||||
|         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
|         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_empty(b: &mut test::Bencher) { | ||||
| //     opts().http2().parallel(10).bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 10]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .parallel(10) | ||||
|         .method(Method::POST) | ||||
|         .request_chunks(body, 100) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .parallel(10) | ||||
| //         .method(Method::POST) | ||||
| //         .request_body(body) | ||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 10]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .parallel(10) | ||||
|         .method(Method::POST) | ||||
|         .request_chunks(body, 100) | ||||
|         .http2_adaptive_window() | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 10]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .parallel(10) | ||||
| //         .method(Method::POST) | ||||
| //         .request_chunks(body, 100) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 10]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .parallel(10) | ||||
|         .method(Method::POST) | ||||
|         .request_chunks(body, 100) | ||||
|         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
|         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 10]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .parallel(10) | ||||
| //         .method(Method::POST) | ||||
| //         .request_chunks(body, 100) | ||||
| //         .http2_adaptive_window() | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 1]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .parallel(10) | ||||
|         .response_body(body) | ||||
|         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
|         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 10]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .parallel(10) | ||||
| //         .method(Method::POST) | ||||
| //         .request_chunks(body, 100) | ||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| #[bench] | ||||
| fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) { | ||||
|     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
|     opts() | ||||
|         .http2() | ||||
|         .parallel(10) | ||||
|         .response_body(body) | ||||
|         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
|         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
|         .bench(b) | ||||
| } | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 1]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .parallel(10) | ||||
| //         .response_body(body) | ||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| // ==== Benchmark Options ===== | ||||
| // #[bench] | ||||
| // fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) { | ||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; | ||||
| //     opts() | ||||
| //         .http2() | ||||
| //         .parallel(10) | ||||
| //         .response_body(body) | ||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||
| //         .bench(b) | ||||
| // } | ||||
|  | ||||
| struct Opts { | ||||
|     http2: bool, | ||||
|     http2_stream_window: Option<u32>, | ||||
|     http2_conn_window: Option<u32>, | ||||
|     http2_adaptive_window: bool, | ||||
|     parallel_cnt: u32, | ||||
|     request_method: Method, | ||||
|     request_body: Option<&'static [u8]>, | ||||
|     request_chunks: usize, | ||||
|     response_body: &'static [u8], | ||||
| } | ||||
| // // ==== Benchmark Options ===== | ||||
|  | ||||
| fn opts() -> Opts { | ||||
|     Opts { | ||||
|         http2: false, | ||||
|         http2_stream_window: None, | ||||
|         http2_conn_window: None, | ||||
|         http2_adaptive_window: false, | ||||
|         parallel_cnt: 1, | ||||
|         request_method: Method::GET, | ||||
|         request_body: None, | ||||
|         request_chunks: 0, | ||||
|         response_body: b"", | ||||
|     } | ||||
| } | ||||
| // struct Opts { | ||||
| //     http2: bool, | ||||
| //     http2_stream_window: Option<u32>, | ||||
| //     http2_conn_window: Option<u32>, | ||||
| //     http2_adaptive_window: bool, | ||||
| //     parallel_cnt: u32, | ||||
| //     request_method: Method, | ||||
| //     request_body: Option<&'static [u8]>, | ||||
| //     request_chunks: usize, | ||||
| //     response_body: &'static [u8], | ||||
| // } | ||||
|  | ||||
| impl Opts { | ||||
|     fn http2(mut self) -> Self { | ||||
|         self.http2 = true; | ||||
|         self | ||||
|     } | ||||
| // fn opts() -> Opts { | ||||
| //     Opts { | ||||
| //         http2: false, | ||||
| //         http2_stream_window: None, | ||||
| //         http2_conn_window: None, | ||||
| //         http2_adaptive_window: false, | ||||
| //         parallel_cnt: 1, | ||||
| //         request_method: Method::GET, | ||||
| //         request_body: None, | ||||
| //         request_chunks: 0, | ||||
| //         response_body: b"", | ||||
| //     } | ||||
| // } | ||||
|  | ||||
|     fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
|         assert!(!self.http2_adaptive_window); | ||||
|         self.http2_stream_window = sz.into(); | ||||
|         self | ||||
|     } | ||||
| // impl Opts { | ||||
| //     fn http2(mut self) -> Self { | ||||
| //         self.http2 = true; | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
|         assert!(!self.http2_adaptive_window); | ||||
|         self.http2_conn_window = sz.into(); | ||||
|         self | ||||
|     } | ||||
| //     fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
| //         assert!(!self.http2_adaptive_window); | ||||
| //         self.http2_stream_window = sz.into(); | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn http2_adaptive_window(mut self) -> Self { | ||||
|         assert!(self.http2_stream_window.is_none()); | ||||
|         assert!(self.http2_conn_window.is_none()); | ||||
|         self.http2_adaptive_window = true; | ||||
|         self | ||||
|     } | ||||
| //     fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
| //         assert!(!self.http2_adaptive_window); | ||||
| //         self.http2_conn_window = sz.into(); | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn method(mut self, m: Method) -> Self { | ||||
|         self.request_method = m; | ||||
|         self | ||||
|     } | ||||
| //     fn http2_adaptive_window(mut self) -> Self { | ||||
| //         assert!(self.http2_stream_window.is_none()); | ||||
| //         assert!(self.http2_conn_window.is_none()); | ||||
| //         self.http2_adaptive_window = true; | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn request_body(mut self, body: &'static [u8]) -> Self { | ||||
|         self.request_body = Some(body); | ||||
|         self | ||||
|     } | ||||
| //     fn method(mut self, m: Method) -> Self { | ||||
| //         self.request_method = m; | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self { | ||||
|         assert!(cnt > 0); | ||||
|         self.request_body = Some(chunk); | ||||
|         self.request_chunks = cnt; | ||||
|         self | ||||
|     } | ||||
| //     fn request_body(mut self, body: &'static [u8]) -> Self { | ||||
| //         self.request_body = Some(body); | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn response_body(mut self, body: &'static [u8]) -> Self { | ||||
|         self.response_body = body; | ||||
|         self | ||||
|     } | ||||
| //     fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self { | ||||
| //         assert!(cnt > 0); | ||||
| //         self.request_body = Some(chunk); | ||||
| //         self.request_chunks = cnt; | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn parallel(mut self, cnt: u32) -> Self { | ||||
|         assert!(cnt > 0, "parallel count must be larger than 0"); | ||||
|         self.parallel_cnt = cnt; | ||||
|         self | ||||
|     } | ||||
| //     fn response_body(mut self, body: &'static [u8]) -> Self { | ||||
| //         self.response_body = body; | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|     fn bench(self, b: &mut test::Bencher) { | ||||
|         use std::sync::Arc; | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         // Create a runtime of current thread. | ||||
|         let rt = Arc::new( | ||||
|             tokio::runtime::Builder::new_current_thread() | ||||
|                 .enable_all() | ||||
|                 .build() | ||||
|                 .expect("rt build"), | ||||
|         ); | ||||
|         let exec = rt.clone(); | ||||
| //     fn parallel(mut self, cnt: u32) -> Self { | ||||
| //         assert!(cnt > 0, "parallel count must be larger than 0"); | ||||
| //         self.parallel_cnt = cnt; | ||||
| //         self | ||||
| //     } | ||||
|  | ||||
|         let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64; | ||||
|         let req_len = if self.request_chunks > 0 { | ||||
|             req_len * self.request_chunks as u64 | ||||
|         } else { | ||||
|             req_len | ||||
|         }; | ||||
|         let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64; | ||||
|         b.bytes = bytes_per_iter; | ||||
| //     fn bench(self, b: &mut test::Bencher) { | ||||
| //         use std::sync::Arc; | ||||
| //         let _ = pretty_env_logger::try_init(); | ||||
| //         // Create a runtime of current thread. | ||||
| //         let rt = Arc::new( | ||||
| //             tokio::runtime::Builder::new_current_thread() | ||||
| //                 .enable_all() | ||||
| //                 .build() | ||||
| //                 .expect("rt build"), | ||||
| //         ); | ||||
| //         let exec = rt.clone(); | ||||
|  | ||||
|         let addr = spawn_server(&rt, &self); | ||||
| //         let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64; | ||||
| //         let req_len = if self.request_chunks > 0 { | ||||
| //             req_len * self.request_chunks as u64 | ||||
| //         } else { | ||||
| //             req_len | ||||
| //         }; | ||||
| //         let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64; | ||||
| //         b.bytes = bytes_per_iter; | ||||
|  | ||||
|         let connector = HttpConnector::new(); | ||||
|         let client = hyper::Client::builder() | ||||
|             .http2_only(self.http2) | ||||
|             .http2_initial_stream_window_size(self.http2_stream_window) | ||||
|             .http2_initial_connection_window_size(self.http2_conn_window) | ||||
|             .http2_adaptive_window(self.http2_adaptive_window) | ||||
|             .build::<_, Body>(connector); | ||||
| //         let addr = spawn_server(&rt, &self); | ||||
|  | ||||
|         let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap(); | ||||
| //         let connector = HttpConnector::new(); | ||||
| //         let client = hyper::Client::builder() | ||||
| //             .http2_only(self.http2) | ||||
| //             .http2_initial_stream_window_size(self.http2_stream_window) | ||||
| //             .http2_initial_connection_window_size(self.http2_conn_window) | ||||
| //             .http2_adaptive_window(self.http2_adaptive_window) | ||||
| //             .build::<_, Body>(connector); | ||||
|  | ||||
|         let make_request = || { | ||||
|             let chunk_cnt = self.request_chunks; | ||||
|             let body = if chunk_cnt > 0 { | ||||
|                 let (mut tx, body) = Body::channel(); | ||||
|                 let chunk = self | ||||
|                     .request_body | ||||
|                     .expect("request_chunks means request_body"); | ||||
|                 exec.spawn(async move { | ||||
|                     for _ in 0..chunk_cnt { | ||||
|                         tx.send_data(chunk.into()).await.expect("send_data"); | ||||
|                     } | ||||
|                 }); | ||||
|                 body | ||||
|             } else { | ||||
|                 self.request_body | ||||
|                     .map(Body::from) | ||||
|                     .unwrap_or_else(Body::empty) | ||||
|             }; | ||||
|             let mut req = Request::new(body); | ||||
|             *req.method_mut() = self.request_method.clone(); | ||||
|             *req.uri_mut() = url.clone(); | ||||
|             req | ||||
|         }; | ||||
| //         let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap(); | ||||
|  | ||||
|         let send_request = |req: Request<Body>| { | ||||
|             let fut = client.request(req); | ||||
|             async { | ||||
|                 let res = fut.await.expect("client wait"); | ||||
|                 let mut body = res.into_body(); | ||||
|                 while let Some(_chunk) = body.data().await {} | ||||
|             } | ||||
|         }; | ||||
| //         let make_request = || { | ||||
| //             let chunk_cnt = self.request_chunks; | ||||
| //             let body = if chunk_cnt > 0 { | ||||
| //                 let (mut tx, body) = Body::channel(); | ||||
| //                 let chunk = self | ||||
| //                     .request_body | ||||
| //                     .expect("request_chunks means request_body"); | ||||
| //                 exec.spawn(async move { | ||||
| //                     for _ in 0..chunk_cnt { | ||||
| //                         tx.send_data(chunk.into()).await.expect("send_data"); | ||||
| //                     } | ||||
| //                 }); | ||||
| //                 body | ||||
| //             } else { | ||||
| //                 self.request_body | ||||
| //                     .map(Body::from) | ||||
| //                     .unwrap_or_else(Body::empty) | ||||
| //             }; | ||||
| //             let mut req = Request::new(body); | ||||
| //             *req.method_mut() = self.request_method.clone(); | ||||
| //             *req.uri_mut() = url.clone(); | ||||
| //             req | ||||
| //         }; | ||||
|  | ||||
|         if self.parallel_cnt == 1 { | ||||
|             b.iter(|| { | ||||
|                 let req = make_request(); | ||||
|                 rt.block_on(send_request(req)); | ||||
|             }); | ||||
|         } else { | ||||
|             b.iter(|| { | ||||
|                 let futs = (0..self.parallel_cnt).map(|_| { | ||||
|                     let req = make_request(); | ||||
|                     send_request(req) | ||||
|                 }); | ||||
|                 // Await all spawned futures becoming completed. | ||||
|                 rt.block_on(join_all(futs)); | ||||
|             }); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| //         let send_request = |req: Request<Body>| { | ||||
| //             let fut = client.request(req); | ||||
| //             async { | ||||
| //                 let res = fut.await.expect("client wait"); | ||||
| //                 let mut body = res.into_body(); | ||||
| //                 while let Some(_chunk) = body.data().await {} | ||||
| //             } | ||||
| //         }; | ||||
|  | ||||
| fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { | ||||
|     use hyper::service::{make_service_fn, service_fn}; | ||||
|     let addr = "127.0.0.1:0".parse().unwrap(); | ||||
| //         if self.parallel_cnt == 1 { | ||||
| //             b.iter(|| { | ||||
| //                 let req = make_request(); | ||||
| //                 rt.block_on(send_request(req)); | ||||
| //             }); | ||||
| //         } else { | ||||
| //             b.iter(|| { | ||||
| //                 let futs = (0..self.parallel_cnt).map(|_| { | ||||
| //                     let req = make_request(); | ||||
| //                     send_request(req) | ||||
| //                 }); | ||||
| //                 // Await all spawned futures becoming completed. | ||||
| //                 rt.block_on(join_all(futs)); | ||||
| //             }); | ||||
| //         } | ||||
| //     } | ||||
| // } | ||||
|  | ||||
|     let body = opts.response_body; | ||||
|     let srv = rt.block_on(async move { | ||||
|         Server::bind(&addr) | ||||
|             .http2_only(opts.http2) | ||||
|             .http2_initial_stream_window_size(opts.http2_stream_window) | ||||
|             .http2_initial_connection_window_size(opts.http2_conn_window) | ||||
|             .http2_adaptive_window(opts.http2_adaptive_window) | ||||
|             .serve(make_service_fn(move |_| async move { | ||||
|                 Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move { | ||||
|                     let mut req_body = req.into_body(); | ||||
|                     while let Some(_chunk) = req_body.data().await {} | ||||
|                     Ok::<_, hyper::Error>(Response::new(Body::from(body))) | ||||
|                 })) | ||||
|             })) | ||||
|     }); | ||||
|     let addr = srv.local_addr(); | ||||
|     rt.spawn(async { | ||||
|         if let Err(err) = srv.await { | ||||
|             panic!("server error: {}", err); | ||||
|         } | ||||
|     }); | ||||
|     addr | ||||
| } | ||||
| // fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { | ||||
| //     use hyper::service::{make_service_fn, service_fn}; | ||||
| //     let addr = "127.0.0.1:0".parse().unwrap(); | ||||
|  | ||||
| //     let body = opts.response_body; | ||||
| //     let srv = rt.block_on(async move { | ||||
| //         Server::bind(&addr) | ||||
| //             .http2_only(opts.http2) | ||||
| //             .http2_initial_stream_window_size(opts.http2_stream_window) | ||||
| //             .http2_initial_connection_window_size(opts.http2_conn_window) | ||||
| //             .http2_adaptive_window(opts.http2_adaptive_window) | ||||
| //             .serve(make_service_fn(move |_| async move { | ||||
| //                 Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move { | ||||
| //                     let mut req_body = req.into_body(); | ||||
| //                     while let Some(_chunk) = req_body.data().await {} | ||||
| //                     Ok::<_, hyper::Error>(Response::new(Body::from(body))) | ||||
| //                 })) | ||||
| //             })) | ||||
| //     }); | ||||
| //     let addr = srv.local_addr(); | ||||
| //     rt.spawn(async { | ||||
| //         if let Err(err) = srv.await { | ||||
| //             panic!("server error: {}", err); | ||||
| //         } | ||||
| //     }); | ||||
| //     addr | ||||
| // } | ||||
|   | ||||
| @@ -4,14 +4,16 @@ | ||||
| extern crate test; | ||||
|  | ||||
| use std::io::{Read, Write}; | ||||
| use std::net::TcpStream; | ||||
| use std::net::{SocketAddr, TcpStream}; | ||||
| use std::sync::mpsc; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use tokio::net::TcpListener; | ||||
| use tokio::sync::oneshot; | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Response, Server}; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Response}; | ||||
|  | ||||
| const PIPELINED_REQUESTS: usize = 16; | ||||
|  | ||||
| @@ -23,35 +25,34 @@ fn hello_world_16(b: &mut test::Bencher) { | ||||
|     let addr = { | ||||
|         let (addr_tx, addr_rx) = mpsc::channel(); | ||||
|         std::thread::spawn(move || { | ||||
|             let addr = "127.0.0.1:0".parse().unwrap(); | ||||
|  | ||||
|             let make_svc = make_service_fn(|_| async { | ||||
|                 Ok::<_, hyper::Error>(service_fn(|_| async { | ||||
|                     Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!"))) | ||||
|                 })) | ||||
|             }); | ||||
|  | ||||
|             let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); | ||||
|             let rt = tokio::runtime::Builder::new_current_thread() | ||||
|                 .enable_all() | ||||
|                 .build() | ||||
|                 .expect("rt build"); | ||||
|             let srv = rt.block_on(async move { | ||||
|                 Server::bind(&addr) | ||||
|                     .http1_pipeline_flush(true) | ||||
|                     .serve(make_svc) | ||||
|             }); | ||||
|  | ||||
|             addr_tx.send(srv.local_addr()).unwrap(); | ||||
|             let listener = rt.block_on(TcpListener::bind(addr)).unwrap(); | ||||
|             let addr = listener.local_addr().unwrap(); | ||||
|  | ||||
|             let graceful = srv.with_graceful_shutdown(async { | ||||
|                 until_rx.await.ok(); | ||||
|             }); | ||||
|             rt.spawn(async move { | ||||
|                 loop { | ||||
|                     let (stream, _addr) = listener.accept().await.expect("accept"); | ||||
|  | ||||
|             rt.block_on(async { | ||||
|                 if let Err(e) = graceful.await { | ||||
|                     panic!("server error: {}", e); | ||||
|                     Http::new() | ||||
|                         .pipeline_flush(true) | ||||
|                         .serve_connection( | ||||
|                             stream, | ||||
|                             service_fn(|_| async { | ||||
|                                 Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!"))) | ||||
|                             }), | ||||
|                         ) | ||||
|                         .await | ||||
|                         .unwrap(); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             addr_tx.send(addr).unwrap(); | ||||
|             rt.block_on(until_rx).ok(); | ||||
|         }); | ||||
|  | ||||
|         addr_rx.recv().unwrap() | ||||
|   | ||||
| @@ -4,53 +4,59 @@ | ||||
| extern crate test; | ||||
|  | ||||
| use std::io::{Read, Write}; | ||||
| use std::net::{TcpListener, TcpStream}; | ||||
| use std::net::{SocketAddr, TcpListener, TcpStream}; | ||||
| use std::sync::mpsc; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use futures_util::{stream, StreamExt}; | ||||
| use http_body_util::{BodyExt, StreamBody}; | ||||
| use tokio::sync::oneshot; | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Response, Server}; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::Response; | ||||
|  | ||||
| macro_rules! bench_server { | ||||
|     ($b:ident, $header:expr, $body:expr) => {{ | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|         let (_until_tx, until_rx) = oneshot::channel::<()>(); | ||||
|  | ||||
|         let addr = { | ||||
|             let (addr_tx, addr_rx) = mpsc::channel(); | ||||
|             std::thread::spawn(move || { | ||||
|                 let addr = "127.0.0.1:0".parse().unwrap(); | ||||
|                 let make_svc = make_service_fn(|_| async { | ||||
|                     Ok::<_, hyper::Error>(service_fn(|_| async { | ||||
|                         Ok::<_, hyper::Error>( | ||||
|                             Response::builder() | ||||
|                                 .header($header.0, $header.1) | ||||
|                                 .header("content-type", "text/plain") | ||||
|                                 .body($body()) | ||||
|                                 .unwrap(), | ||||
|                         ) | ||||
|                     })) | ||||
|                 }); | ||||
|  | ||||
|                 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); | ||||
|                 let rt = tokio::runtime::Builder::new_current_thread() | ||||
|                     .enable_all() | ||||
|                     .build() | ||||
|                     .expect("rt build"); | ||||
|  | ||||
|                 let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) }); | ||||
|                 let listener = rt.block_on(tokio::net::TcpListener::bind(addr)).unwrap(); | ||||
|                 let addr = listener.local_addr().unwrap(); | ||||
|  | ||||
|                 addr_tx.send(srv.local_addr()).unwrap(); | ||||
|                 rt.spawn(async move { | ||||
|                     loop { | ||||
|                         let (stream, _) = listener.accept().await.expect("accept"); | ||||
|  | ||||
|                 let graceful = srv.with_graceful_shutdown(async { | ||||
|                     until_rx.await.ok(); | ||||
|                 }); | ||||
|                 rt.block_on(async move { | ||||
|                     if let Err(e) = graceful.await { | ||||
|                         panic!("server error: {}", e); | ||||
|                         Http::new() | ||||
|                             .serve_connection( | ||||
|                                 stream, | ||||
|                                 service_fn(|_| async { | ||||
|                                     Ok::<_, hyper::Error>( | ||||
|                                         Response::builder() | ||||
|                                             .header($header.0, $header.1) | ||||
|                                             .header("content-type", "text/plain") | ||||
|                                             .body($body()) | ||||
|                                             .unwrap(), | ||||
|                                     ) | ||||
|                                 }), | ||||
|                             ) | ||||
|                             .await | ||||
|                             .unwrap(); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 addr_tx.send(addr).unwrap(); | ||||
|                 rt.block_on(until_rx).ok(); | ||||
|             }); | ||||
|  | ||||
|             addr_rx.recv().unwrap() | ||||
| @@ -99,9 +105,11 @@ fn throughput_fixedsize_large_payload(b: &mut test::Bencher) { | ||||
|  | ||||
| #[bench] | ||||
| fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { | ||||
|     bench_server!(b, ("content-length", "1000000"), || { | ||||
|     bench_server!(b, ("content-length", "1000000"), move || { | ||||
|         static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; | ||||
|         Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) | ||||
|         BodyExt::boxed(StreamBody::new( | ||||
|             stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)), | ||||
|         )) | ||||
|     }) | ||||
| } | ||||
|  | ||||
| @@ -123,7 +131,9 @@ fn throughput_chunked_large_payload(b: &mut test::Bencher) { | ||||
| fn throughput_chunked_many_chunks(b: &mut test::Bencher) { | ||||
|     bench_server!(b, ("transfer-encoding", "chunked"), || { | ||||
|         static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; | ||||
|         Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) | ||||
|         BodyExt::boxed(StreamBody::new( | ||||
|             stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)), | ||||
|         )) | ||||
|     }) | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -24,44 +24,42 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b | ||||
|     struct conn_data *conn = (struct conn_data *)userdata; | ||||
|     ssize_t ret = read(conn->fd, buf, buf_len); | ||||
|  | ||||
|     if (ret < 0) { | ||||
|         int err = errno; | ||||
|         if (err == EAGAIN) { | ||||
|             // would block, register interest | ||||
|             if (conn->read_waker != NULL) { | ||||
|                 hyper_waker_free(conn->read_waker); | ||||
|             } | ||||
|             conn->read_waker = hyper_context_waker(ctx); | ||||
|             return HYPER_IO_PENDING; | ||||
|         } else { | ||||
|             // kaboom | ||||
|             return HYPER_IO_ERROR; | ||||
|         } | ||||
|     } else { | ||||
|     if (ret >= 0) { | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     if (errno != EAGAIN) { | ||||
|         // kaboom | ||||
|         return HYPER_IO_ERROR; | ||||
|     } | ||||
|  | ||||
|     // would block, register interest | ||||
|     if (conn->read_waker != NULL) { | ||||
|         hyper_waker_free(conn->read_waker); | ||||
|     } | ||||
|     conn->read_waker = hyper_context_waker(ctx); | ||||
|     return HYPER_IO_PENDING; | ||||
| } | ||||
|  | ||||
| static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) { | ||||
|     struct conn_data *conn = (struct conn_data *)userdata; | ||||
|     ssize_t ret = write(conn->fd, buf, buf_len); | ||||
|  | ||||
|     if (ret < 0) { | ||||
|         int err = errno; | ||||
|         if (err == EAGAIN) { | ||||
|             // would block, register interest | ||||
|             if (conn->write_waker != NULL) { | ||||
|                 hyper_waker_free(conn->write_waker); | ||||
|             } | ||||
|             conn->write_waker = hyper_context_waker(ctx); | ||||
|             return HYPER_IO_PENDING; | ||||
|         } else { | ||||
|             // kaboom | ||||
|             return HYPER_IO_ERROR; | ||||
|         } | ||||
|     } else { | ||||
|     if (ret >= 0) { | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     if (errno != EAGAIN) { | ||||
|         // kaboom | ||||
|         return HYPER_IO_ERROR; | ||||
|     } | ||||
|  | ||||
|     // would block, register interest | ||||
|     if (conn->write_waker != NULL) { | ||||
|         hyper_waker_free(conn->write_waker); | ||||
|     } | ||||
|     conn->write_waker = hyper_context_waker(ctx); | ||||
|     return HYPER_IO_PENDING; | ||||
| } | ||||
|  | ||||
| static void free_conn_data(struct conn_data *conn) { | ||||
| @@ -98,9 +96,9 @@ static int connect_to(const char *host, const char *port) { | ||||
|  | ||||
|         if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { | ||||
|             break; | ||||
|         } else { | ||||
|             close(sfd); | ||||
|         } | ||||
|  | ||||
|         close(sfd); | ||||
|     } | ||||
|  | ||||
|     freeaddrinfo(result); | ||||
| @@ -142,17 +140,17 @@ typedef enum { | ||||
| #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) | ||||
|  | ||||
| int main(int argc, char *argv[]) { | ||||
|         const char *host = argc > 1 ? argv[1] : "httpbin.org"; | ||||
|         const char *port = argc > 2 ? argv[2] : "80"; | ||||
|         const char *path = argc > 3 ? argv[3] : "/"; | ||||
|         printf("connecting to port %s on %s...\n", port, host); | ||||
|     const char *host = argc > 1 ? argv[1] : "httpbin.org"; | ||||
|     const char *port = argc > 2 ? argv[2] : "80"; | ||||
|     const char *path = argc > 3 ? argv[3] : "/"; | ||||
|     printf("connecting to port %s on %s...\n", port, host); | ||||
|  | ||||
|         int fd = connect_to(host, port); | ||||
|     int fd = connect_to(host, port); | ||||
|     if (fd < 0) { | ||||
|         return 1; | ||||
|     } | ||||
|         printf("connected to %s, now get %s\n", host, path); | ||||
|  | ||||
|     printf("connected to %s, now get %s\n", host, path); | ||||
|     if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) { | ||||
|         printf("failed to set socket to non-blocking\n"); | ||||
|         return 1; | ||||
| @@ -168,7 +166,6 @@ int main(int argc, char *argv[]) { | ||||
|     conn->read_waker = NULL; | ||||
|     conn->write_waker = NULL; | ||||
|  | ||||
|  | ||||
|     // Hookup the IO | ||||
|     hyper_io *io = hyper_io_new(); | ||||
|     hyper_io_set_userdata(io, (void *)conn); | ||||
| @@ -315,15 +312,16 @@ int main(int argc, char *argv[]) { | ||||
|         if (sel_ret < 0) { | ||||
|             printf("select() error\n"); | ||||
|             return 1; | ||||
|         } else { | ||||
|             if (FD_ISSET(conn->fd, &fds_read)) { | ||||
|                 hyper_waker_wake(conn->read_waker); | ||||
|                 conn->read_waker = NULL; | ||||
|             } | ||||
|             if (FD_ISSET(conn->fd, &fds_write)) { | ||||
|                 hyper_waker_wake(conn->write_waker); | ||||
|                 conn->write_waker = NULL; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (FD_ISSET(conn->fd, &fds_read)) { | ||||
|             hyper_waker_wake(conn->read_waker); | ||||
|             conn->read_waker = NULL; | ||||
|         } | ||||
|  | ||||
|         if (FD_ISSET(conn->fd, &fds_write)) { | ||||
|             hyper_waker_wake(conn->write_waker); | ||||
|             conn->write_waker = NULL; | ||||
|         } | ||||
|  | ||||
|     } | ||||
|   | ||||
| @@ -24,44 +24,42 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b | ||||
|     struct conn_data *conn = (struct conn_data *)userdata; | ||||
|     ssize_t ret = read(conn->fd, buf, buf_len); | ||||
|  | ||||
|     if (ret < 0) { | ||||
|         int err = errno; | ||||
|         if (err == EAGAIN) { | ||||
|             // would block, register interest | ||||
|             if (conn->read_waker != NULL) { | ||||
|                 hyper_waker_free(conn->read_waker); | ||||
|             } | ||||
|             conn->read_waker = hyper_context_waker(ctx); | ||||
|             return HYPER_IO_PENDING; | ||||
|         } else { | ||||
|             // kaboom | ||||
|             return HYPER_IO_ERROR; | ||||
|         } | ||||
|     } else { | ||||
|     if (ret >= 0) { | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     if (errno != EAGAIN) { | ||||
|         // kaboom | ||||
|         return HYPER_IO_ERROR; | ||||
|     } | ||||
|  | ||||
|     // would block, register interest | ||||
|     if (conn->read_waker != NULL) { | ||||
|         hyper_waker_free(conn->read_waker); | ||||
|     } | ||||
|     conn->read_waker = hyper_context_waker(ctx); | ||||
|     return HYPER_IO_PENDING; | ||||
| } | ||||
|  | ||||
| static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) { | ||||
|     struct conn_data *conn = (struct conn_data *)userdata; | ||||
|     ssize_t ret = write(conn->fd, buf, buf_len); | ||||
|  | ||||
|     if (ret < 0) { | ||||
|         int err = errno; | ||||
|         if (err == EAGAIN) { | ||||
|             // would block, register interest | ||||
|             if (conn->write_waker != NULL) { | ||||
|                 hyper_waker_free(conn->write_waker); | ||||
|             } | ||||
|             conn->write_waker = hyper_context_waker(ctx); | ||||
|             return HYPER_IO_PENDING; | ||||
|         } else { | ||||
|             // kaboom | ||||
|             return HYPER_IO_ERROR; | ||||
|         } | ||||
|     } else { | ||||
|     if (ret >= 0) { | ||||
|         return ret; | ||||
|     } | ||||
|  | ||||
|     if (errno != EAGAIN) { | ||||
|         // kaboom | ||||
|         return HYPER_IO_ERROR; | ||||
|     } | ||||
|  | ||||
|     // would block, register interest | ||||
|     if (conn->write_waker != NULL) { | ||||
|         hyper_waker_free(conn->write_waker); | ||||
|     } | ||||
|     conn->write_waker = hyper_context_waker(ctx); | ||||
|     return HYPER_IO_PENDING; | ||||
| } | ||||
|  | ||||
| static void free_conn_data(struct conn_data *conn) { | ||||
| @@ -98,9 +96,9 @@ static int connect_to(const char *host, const char *port) { | ||||
|  | ||||
|         if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { | ||||
|             break; | ||||
|         } else { | ||||
|             close(sfd); | ||||
|         } | ||||
|  | ||||
|         close(sfd); | ||||
|     } | ||||
|  | ||||
|     freeaddrinfo(result); | ||||
| @@ -126,17 +124,20 @@ static int poll_req_upload(void *userdata, | ||||
|     struct upload_body* upload = userdata; | ||||
|  | ||||
|     ssize_t res = read(upload->fd, upload->buf, upload->len); | ||||
|     if (res < 0) { | ||||
|         printf("error reading upload file: %d", errno); | ||||
|         return HYPER_POLL_ERROR; | ||||
|     } else if (res == 0) { | ||||
|         // All done! | ||||
|         *chunk = NULL; | ||||
|         return HYPER_POLL_READY; | ||||
|     } else { | ||||
|     if (res > 0) { | ||||
|         *chunk = hyper_buf_copy(upload->buf, res); | ||||
|         return HYPER_POLL_READY; | ||||
|     } | ||||
|  | ||||
|     if (res == 0) { | ||||
|         // All done! | ||||
|         *chunk = NULL; | ||||
|         return HYPER_POLL_READY; | ||||
|     } | ||||
|  | ||||
|     // Oh no! | ||||
|     printf("error reading upload file: %d", errno); | ||||
|     return HYPER_POLL_ERROR; | ||||
| } | ||||
|  | ||||
| static int print_each_header(void *userdata, | ||||
| @@ -348,20 +349,20 @@ int main(int argc, char *argv[]) { | ||||
|                     hyper_executor_push(exec, body_data); | ||||
|  | ||||
|                     break; | ||||
|                 } else { | ||||
|                     assert(task_type == HYPER_TASK_EMPTY); | ||||
|                     hyper_task_free(task); | ||||
|                     hyper_body_free(resp_body); | ||||
|  | ||||
|                     printf("\n -- Done! -- \n"); | ||||
|  | ||||
|                     // Cleaning up before exiting | ||||
|                     hyper_executor_free(exec); | ||||
|                     free_conn_data(conn); | ||||
|                     free(upload.buf); | ||||
|  | ||||
|                     return 0; | ||||
|                 } | ||||
|  | ||||
|                 assert(task_type == HYPER_TASK_EMPTY); | ||||
|                 hyper_task_free(task); | ||||
|                 hyper_body_free(resp_body); | ||||
|  | ||||
|                 printf("\n -- Done! -- \n"); | ||||
|  | ||||
|                 // Cleaning up before exiting | ||||
|                 hyper_executor_free(exec); | ||||
|                 free_conn_data(conn); | ||||
|                 free(upload.buf); | ||||
|  | ||||
|                 return 0; | ||||
|             case EXAMPLE_NOT_SET: | ||||
|                 // A background task for hyper completed... | ||||
|                 hyper_task_free(task); | ||||
| @@ -387,17 +388,17 @@ int main(int argc, char *argv[]) { | ||||
|         if (sel_ret < 0) { | ||||
|             printf("select() error\n"); | ||||
|             return 1; | ||||
|         } else { | ||||
|             if (FD_ISSET(conn->fd, &fds_read)) { | ||||
|                 hyper_waker_wake(conn->read_waker); | ||||
|                 conn->read_waker = NULL; | ||||
|             } | ||||
|             if (FD_ISSET(conn->fd, &fds_write)) { | ||||
|                 hyper_waker_wake(conn->write_waker); | ||||
|                 conn->write_waker = NULL; | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if (FD_ISSET(conn->fd, &fds_read)) { | ||||
|             hyper_waker_wake(conn->read_waker); | ||||
|             conn->read_waker = NULL; | ||||
|         } | ||||
|  | ||||
|         if (FD_ISSET(conn->fd, &fds_write)) { | ||||
|             hyper_waker_wake(conn->write_waker); | ||||
|             conn->write_waker = NULL; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|  | ||||
|   | ||||
| @@ -355,6 +355,22 @@ void hyper_clientconn_free(struct hyper_clientconn *conn); | ||||
|  */ | ||||
| struct hyper_clientconn_options *hyper_clientconn_options_new(void); | ||||
|  | ||||
| /* | ||||
|  Set the whether or not header case is preserved. | ||||
|  | ||||
|  Pass `0` to allow lowercase normalization (default), `1` to retain original case. | ||||
|  */ | ||||
| void hyper_clientconn_options_set_preserve_header_case(struct hyper_clientconn_options *opts, | ||||
|                                                        int enabled); | ||||
|  | ||||
| /* | ||||
|  Set the whether or not header order is preserved. | ||||
|  | ||||
|  Pass `0` to allow reordering (default), `1` to retain original ordering. | ||||
|  */ | ||||
| void hyper_clientconn_options_set_preserve_header_order(struct hyper_clientconn_options *opts, | ||||
|                                                         int enabled); | ||||
|  | ||||
| /* | ||||
|  Free a `hyper_clientconn_options *`. | ||||
|  */ | ||||
| @@ -386,6 +402,16 @@ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options * | ||||
| enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts, | ||||
|                                                      int enabled); | ||||
|  | ||||
| /* | ||||
|  Set whether HTTP/1 connections will accept obsolete line folding for header values. | ||||
|  Newline codepoints (\r and \n) will be transformed to spaces when parsing. | ||||
|  | ||||
|  Pass `0` to disable, `1` to enable. | ||||
|  | ||||
|  */ | ||||
| enum hyper_code hyper_clientconn_options_http1_allow_multiline_headers(struct hyper_clientconn_options *opts, | ||||
|                                                                        int enabled); | ||||
|  | ||||
| /* | ||||
|  Frees a `hyper_error`. | ||||
|  */ | ||||
|   | ||||
| @@ -4,7 +4,7 @@ | ||||
|  | ||||
| - Don't be mean. | ||||
| - Insulting anyone is prohibited. | ||||
| - Harrassment of any kind is prohibited. | ||||
| - Harassment of any kind is prohibited. | ||||
| - If another person feels uncomfortable with your remarks, stop it. | ||||
| - If a moderator deems your comment or conduct as inappropriate, stop it. | ||||
| - Disagreeing is fine, but keep it to technical arguments. Never attack the person. | ||||
|   | ||||
							
								
								
									
										111
									
								
								docs/GOVERNANCE.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								docs/GOVERNANCE.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | ||||
| # Governance | ||||
|  | ||||
| ## Making decisions | ||||
|  | ||||
| There's two main pieces to the way decisions are made in hyper: | ||||
|  | ||||
| 1. A decision-making framework | ||||
| 2. The people who apply it | ||||
|  | ||||
| The people are described [lower down in this document](#roles). | ||||
|  | ||||
| ### Decision-making framework | ||||
|  | ||||
| We start with the users. The project wouldn't exist without them, and it exists | ||||
| in order to enable users to do amazing things with HTTP. We listen to our | ||||
| users. Some actively contribute their thoughts, but many others we must seek | ||||
| out to learn about their usage, joys, and headaches. Those insights allow our | ||||
| experts to determine the best solutions for the users. | ||||
|  | ||||
| We then define a set of [TENETS](./TENETS.md), which are guiding principles | ||||
| that can be used to measure aspects of individual decisions. It should be | ||||
| possible to identify one or more tenets that apply to why a decision is made. | ||||
| And the set helps us balance which priorities are more important for our users. | ||||
|  | ||||
| We combine the usecases with the tenets to come up with a [VISION](./VISION.md) | ||||
| that provides a longer-term plan of what hyper _should_ look like. | ||||
|  | ||||
| Finally, we define a [ROADMAP](./ROADMAP.md) that describes what the | ||||
| short-term, tactical changes to bring hyper closer in line with the vision. | ||||
|  | ||||
| ## Roles | ||||
|  | ||||
| These are the roles people can fill when participating in the project. A list | ||||
| of the people in each role is available in [MAINTAINERS](./MAINTAINERS.md). | ||||
|  | ||||
| ### Contributor | ||||
|  | ||||
| A contributor is anyone who contributes their time to provide value for the | ||||
| project. This could be in the form of code, documentation, filing issues, | ||||
| discussing designs, or helping others on the issue tracker or in chat. | ||||
|  | ||||
| All contributors MUST follow the [Code of Conduct][coc]. | ||||
|  | ||||
| 👋  **New here?** [This could be you!][contrib] | ||||
|  | ||||
|  | ||||
| ### Triager | ||||
|  | ||||
| Triagers assess issues on the issue tracker. They help make sure the work is | ||||
| well organized, and are critical for making new issue reporters feeling | ||||
| welcome. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| - Adhere to the [Code of Conduct][coc] | ||||
| - Follow the [triager's guide][triage-guide] | ||||
|  | ||||
| Privileges: | ||||
|  | ||||
| - Can edit, label, and close issues | ||||
| - Member of the organization | ||||
| - Can be assigned issues and pull requests | ||||
|  | ||||
| How to become: | ||||
|  | ||||
| - Make a few [contributions][contrib] to the project, to show you can follow | ||||
|   the [Code of Conduct][coc]. | ||||
| - Self-nominate by making a pull request adding yourself to the | ||||
|   [list](./MAINTAINERS.md#triagers). | ||||
|  | ||||
|  | ||||
| ### Collaborator | ||||
|  | ||||
| Collaborators are contributors who have been helping out in a consistent basis. | ||||
|  | ||||
| Responsibilities: | ||||
|  | ||||
| - Be exemplars of the [Code of Conduct][coc] | ||||
| - Internalize the [VISION](./VISION.md) | ||||
| - Reviewing pull requests from other contributors | ||||
| - Provide feedback on proposed features and design documents | ||||
| - [Welcome new contributors][triage-guide] | ||||
| - Answer questions in issues and/or chat | ||||
| - Mentor contributors to become members | ||||
|  | ||||
| Privileges: | ||||
|  | ||||
| - Can review and merge pull requests | ||||
| - Can trigger re-runs of CI, and approve CI for first-time contributors | ||||
| - Can assign issues and pull requests to other organization members | ||||
|  | ||||
| How to become: | ||||
|  | ||||
| - Work at fulfilling the above responsibilities. | ||||
| - Any collaborator may nominate a contributor who has been around for some time | ||||
|   and is already filling the responsibilities. | ||||
| - Another collaborator must second the nomination. | ||||
| - If there are no objections, a maintainer will welcome the new collaborator. | ||||
|  | ||||
| Don't be afraid to ask a collaborator for what you could work on to reach this | ||||
| goal! | ||||
|  | ||||
| ### Maintainer | ||||
|  | ||||
| Maintainers are the project admins. Besides being a collaborator, they take care | ||||
| house-keeping duties, help lead the direction, and have the final authority when | ||||
| required. | ||||
|  | ||||
| [coc]: ./CODE_OF_CONDUCT.md | ||||
| [contrib]: ../CONTRIBUTING.md | ||||
| [triage-guide]: ./ISSUES.md#triaging | ||||
| @@ -2,6 +2,68 @@ | ||||
|  | ||||
| The [issue tracker][issues] for hyper is where we track all features, bugs, and discuss proposals. | ||||
|  | ||||
| ## Triaging | ||||
|  | ||||
| Once an issue has been opened, it is normal for there to be discussion | ||||
| around it. Some contributors may have differing opinions about the issue, | ||||
| including whether the behavior being seen is a bug or a feature. This | ||||
| discussion is part of the process and should be kept focused, helpful, and | ||||
| professional. | ||||
|  | ||||
| The objective of helping with triaging issues is to help reduce the issue | ||||
| backlog and keep the issue tracker healthy, while enabling newcomers another | ||||
| meaningful way to get engaged and contribute. | ||||
|  | ||||
| ### Acknowledge | ||||
|  | ||||
| Acknowledge the human. This is meant actively, such as giving a welcome, or | ||||
| thanks for a detailed report, or any other greeting that makes the person feel | ||||
| that their contribution (issues are contributions!) is valued. It also is meant | ||||
| to be internalized, and be sure to always [treat the person kindly][COC] | ||||
| throughout the rest of the steps of triaging. | ||||
|  | ||||
| ### Ask for more info | ||||
|  | ||||
| Frequently, we need more information than was originally provided to fully | ||||
| evaluate an issue. | ||||
|  | ||||
| If it is a bug report, ask follow up questions that help us get a [minimum | ||||
| reproducible example][MRE]. This may take several round-trip questions. Once | ||||
| all the details are gathered, it may be helpful to edit the original issue text | ||||
| to include them all. | ||||
|  | ||||
| ### Categorize | ||||
|  | ||||
| Once enough information has been gathered, the issue should be categorized | ||||
| with [labels][#labels]. Ideally, most issues should be labelled with an area, | ||||
| effort, and severity. An issue _can_ have multiple areas, pick what fits. There | ||||
| should be only one severity, and the descriptions of each should help to pick | ||||
| the right one. The hardest label to select is "effort". If after reading the | ||||
| descriptions of each effort level, you're still unsure, you can ping a | ||||
| maintainer to pick one. | ||||
|  | ||||
| ### Adjust the title | ||||
|  | ||||
| An optional step when triaging is to adjust the title once more information is | ||||
| known. Sometimes an issue starts as a question, and through discussion, it | ||||
| turns out to be a feature request, or a bug report. In those cases, the title | ||||
| should be changed from a question, and the title should be a succinct action to | ||||
| be taken. For example, a question about an non-existent configuration option | ||||
| may be reworded to "Add option to Client to do Zed". | ||||
|  | ||||
| ### Mentoring | ||||
|  | ||||
| The last part of triaging is to try to make the issue a learning experience. | ||||
| After a discussion with the reporter, it would be good to ask if they are now | ||||
| interested in submitting the change described in the issue. | ||||
|  | ||||
| Otherwise, it would be best to leave the issue with a series of steps for | ||||
| anyone else to try to write the change. That could be pointing out that a | ||||
| design proposal is needed, addressing certain points. Or, if the required | ||||
| changes are mostly know, a list of links to modules and functions where code | ||||
| needs to be changed, and to what. That way we mentor newcomers to become | ||||
| successful contributors of new [pull requests][PRs]. | ||||
|  | ||||
| ## Labels | ||||
|  | ||||
| Issues are organized with a set of labels. Most labels follow a system of being prefixed by a "type". | ||||
| @@ -47,3 +109,5 @@ The severity marks how _severe_ the issue is. Note this isn't "importance" or "p | ||||
| - **S-refactor**: improve internal code to help readability and maintenance. | ||||
|  | ||||
| [issues]: https://github.com/hyperium/hyper/issues | ||||
| [COC]: ./CODE_OF_CONDUCT.md | ||||
| [PRs]: ./PULL_REQUESTS.md | ||||
|   | ||||
							
								
								
									
										30
									
								
								docs/MAINTAINERS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								docs/MAINTAINERS.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,30 @@ | ||||
| # The People | ||||
|  | ||||
| To see what these roles do, and how to become one, look at [GOVERNANCE](./GOVERNANCED.md). | ||||
|  | ||||
| ## Triagers | ||||
|  | ||||
| ## Collaborators | ||||
|  | ||||
| - Sean McArthur (@seanmonstar) | ||||
| - Steven Fackler (@sfackler) | ||||
| - Oliver Gould (@olix0r) | ||||
| - Eliza Weisman (@hawkw) | ||||
| - Lucio Franco (@LucioFranco) | ||||
| - Anthony Ramine (@nox) | ||||
| - David Pedersen (@davidpdrsn) | ||||
| - Adam Foltzer (@acfoltzer) | ||||
|  | ||||
| <details> | ||||
| <summary>Emeriti</summary> | ||||
|  | ||||
| ### Collaborator emeriti | ||||
|  | ||||
| - Jonathan Reem (@reem) | ||||
| - Carl Lerche (@carllerche) | ||||
|  | ||||
| </details> | ||||
|  | ||||
| ## Maintainers | ||||
|  | ||||
| - Sean McArthur (@seanmonstar) | ||||
| @@ -6,4 +6,4 @@ hyper. It is possible that an older compiler can work, but that is not | ||||
| guaranteed. We try to increase the MSRV responsibly, only when a significant | ||||
| new feature is needed. | ||||
|  | ||||
| The current MSRV is: **1.49**. | ||||
| The current MSRV is: **1.56**. | ||||
|   | ||||
							
								
								
									
										49
									
								
								docs/PULL_REQUESTS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										49
									
								
								docs/PULL_REQUESTS.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,49 @@ | ||||
| # Pull Requests | ||||
|  | ||||
| Pull requests are the way to submit changes to the hyper repository. | ||||
|  | ||||
| ## Submitting a Pull Request | ||||
|  | ||||
| In most cases, it a good idea to discuss a potential change in an | ||||
| [issue](ISSUES.md). This will allow other contributors to provide guidance and | ||||
| feedback _before_ significant code work is done, and can increase the | ||||
| likelihood of getting the pull request merged. | ||||
|  | ||||
| ### Tests | ||||
|  | ||||
| If the change being proposed alters code (as opposed to only documentation for | ||||
| example), it is either adding new functionality to hyper or it is fixing | ||||
| existing, broken functionality. In both of these cases, the pull request should | ||||
| include one or more tests to ensure that hyper does not regress in the future. | ||||
|  | ||||
| ### Commits | ||||
|  | ||||
| Once code, tests, and documentation have been written, a commit needs to be | ||||
| made. Following the [commit guidelines](COMMITS.md) will help with the review | ||||
| process by making your change easier to understand, and makes it easier for | ||||
| hyper to produce a valuable changelog with each release. | ||||
|  | ||||
| However, if your message doesn't perfectly match the guidelines, **do not | ||||
| worry!** The person that eventually merges can easily fixup the message at that | ||||
| time. | ||||
|  | ||||
| ### Opening the Pull Request | ||||
|  | ||||
| From within GitHub, open a new pull request from your personal branch. | ||||
|  | ||||
| Once opened, pull requests are usually reviewed within a few days. | ||||
|  | ||||
| ### Discuss and Update | ||||
|  | ||||
| You will probably get feedback or requests for changes to your Pull Request. | ||||
| This is a big part of the submission process so don't be discouraged! Some | ||||
| contributors may sign off on the Pull Request right away, others may have more | ||||
| detailed comments or feedback. This is a necessary part of the process in order | ||||
| to evaluate whether the changes are correct and necessary. | ||||
|  | ||||
| Any community member can review a PR and you might get conflicting feedback. | ||||
| Keep an eye out for comments from code owners to provide guidance on | ||||
| conflicting feedback. | ||||
|  | ||||
| You don't need to close the PR and create a new one to address feedback. You | ||||
| may simply push new commits to the existing branch. | ||||
							
								
								
									
										404
									
								
								docs/ROADMAP.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										404
									
								
								docs/ROADMAP.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,404 @@ | ||||
| # hyper 1.0 Roadmap | ||||
|  | ||||
| ## Goal | ||||
|  | ||||
| Align current hyper to the [hyper VISION][VISION]. | ||||
|  | ||||
| The VISION outlines a decision-making framework, use-cases, and general shape | ||||
| of hyper. This roadmap describes the currently known problems with hyper, and | ||||
| then shows what changes are needed to make hyper 1.0 look more like what is in | ||||
| the VISION. | ||||
|  | ||||
| ## Known Issues | ||||
|  | ||||
|  | ||||
| > **Note**: These known issues are as of hyper v0.14.x. After v1.0 is released, | ||||
| ideally these issues will have been solved. Keeping this history may be helpful | ||||
| to Future Us, though. | ||||
|  | ||||
| ### Higher-level Client and Server problems | ||||
|  | ||||
| Both the higher-level `Client` and `Server` types have stability concerns. | ||||
|  | ||||
| For the `hyper::Server`: | ||||
|  | ||||
| - The `Accept` trait is complex, and too easy to get wrong. If used with TLS, a slow TLS handshake | ||||
|   can affect all other new connections waiting for it to finish. | ||||
| - The `MakeService<&IO>` is confusing. The bounds are an assault on the eyes. | ||||
| - The `MakeService` API doesn't allow to easily annotate the HTTP connection with `tracing`. | ||||
| - Graceful shutdown doesn't give enough control. | ||||
|  | ||||
|  | ||||
| It's more common for people to simply use `hyper::server::conn` at this point, | ||||
| than to bother with the `hyper::Server`. | ||||
|  | ||||
| While the `hyper::Client` is much easier to use, problems still exist: | ||||
|  | ||||
| - The whole `Connect` design isn't stable. | ||||
|   - ALPN and proxies can provide surprising extra configuration of connections. | ||||
|   - Some `Connect` implementations may wish to view the path, in addition to the scheme, host, and port. | ||||
|   - Wants `runtime` feature | ||||
| - The Pool could be made more general or composable. At the same time, more customization is | ||||
|   desired, and it's not clear | ||||
| how to expose it yet. | ||||
|  | ||||
|  | ||||
| ### Runtime woes | ||||
|  | ||||
| hyper has been able to support different runtimes, but it has sometimes awkward | ||||
| default support for Tokio. | ||||
|  | ||||
| - The `runtime` cargo-feature isn't additive | ||||
| - Built-in Tokio support can be confusing | ||||
| - Executors and Timers | ||||
|   - The `runtime` feature currently enables a few options that require a timer, such as timeouts and | ||||
|     keepalive intervals. It implicitly relies on Tokio's timer context. This can be quite confusing. | ||||
| - IO traits | ||||
|   - Should we publicly depend on Tokio's traits? | ||||
|   - `futures-io`? | ||||
|     - Definitely nope. | ||||
|     - Not stable. (0.3?) | ||||
|     - No uninitialized memory. | ||||
|   - Eventual `std` traits? | ||||
|     - They've been in design for years. | ||||
|     - We cannot base our schedule on them. | ||||
|     - When they are stable, we can: | ||||
|       - Provide a bridge in `hyper-util`. | ||||
|       - Consider a 2.0 of hyper. | ||||
|   - Define our own traits, provide util wrappers? | ||||
|  | ||||
| ### Forwards-compatibility | ||||
|  | ||||
| There's a concern about forwards-compatibility. We want to be able to add | ||||
| support for new HTTP features without needing a new major version. While most | ||||
| of `http` and `hyper` are prepared for that, there's two potential problems. | ||||
|  | ||||
| - New frames on an HTTP stream (body) | ||||
|    - Receiving a new frame type would require a new trait method | ||||
|      - There's no way to implement a "receive unknown frame" that hyper doesn't know about. | ||||
|    - Sending an unknown frame type would be even harder. | ||||
|      - Besides being able to pass an "unknown" type through the trait, the user would need to be | ||||
|        able to describe how that frame is encoded in HTTP/2/3. | ||||
| - New HTTP versions | ||||
|   - HTTP/3 will require a new transport abstraction. It's not as simple as just using some | ||||
|     `impl AsyncRead + AsyncWrite`. While HTTP/2 bundled the concept of stream creation internally, | ||||
|     and thus could be managed wholly on top of a read-write transport, HTTP/3 is different. Stream | ||||
|     creation is shifted to the QUIC protocol, and HTTP/3 needs to be able to use that directly. | ||||
|   - This means the existing `Connection` types for both client and server will not be able to | ||||
|     accept a QUIC transport so we can add HTTP/3 support. | ||||
|  | ||||
| ### Errors | ||||
|  | ||||
| It's not easy to match for specific errors. | ||||
|  | ||||
| The `Error::source()` can leak an internal dependency. For example, a | ||||
| `hyper::Error` may wrap an `h2::Error`. Users can downcast the source at | ||||
| runtime, and hyper internally changing the version of its `h2` dependency can | ||||
| cause runtime breakage for users. | ||||
|  | ||||
| Formatting errors is in conflict with the current expected norm. The | ||||
| `fmt::Display` implementation for `hyper::Error` currently prints its own | ||||
| message, and then prints the message of any wrapped source error. The Errors | ||||
| Working Group currently recommends that errors only print their own message | ||||
| (link?). This conflict means that error "reporters", which crawl a source chain | ||||
| and print each error, has a lot of duplicated information. | ||||
|  | ||||
| ``` | ||||
| error fetching website: error trying to connect: tcp connect error: Connection refused (os error 61) | ||||
| tcp connect error: Connection refused (os error 61) | ||||
| Connection refused (os error 61) | ||||
| ``` | ||||
|  | ||||
| While there is a good reason for why hyper's `Error` types do this, at the very | ||||
| least, it _is_ unfortunate. | ||||
|  | ||||
| ### You call hyper, or hyper calls you? | ||||
|  | ||||
| > Note: this problem space, of who calls whom, will be explored more deeply in | ||||
| > a future article. | ||||
|  | ||||
| At times, it's been wondered whether hyper should call user code, or if user | ||||
| code should call hyper. For instance, should a `Service` be called with a | ||||
| request when the connection receives one, or should the user always poll for | ||||
| the next request. | ||||
|  | ||||
| There's a similar question around sending a message body. Should hyper ask the | ||||
| body for more data to write, or should the user call a `write` method directly? | ||||
|  | ||||
| These both get at a root topic about [write | ||||
| observability](https://github.com/hyperium/hyper/issues/2181). How do you know | ||||
| when a response, or when body data, has been written successfully? This is | ||||
| desirable for metrics, or for triggering other side-effects.  | ||||
|  | ||||
| The `Service` trait also has some other frequently mentioned issues. Does | ||||
| `poll_ready` pull its complexity weight for servers? What about returning | ||||
| errors, what does that mean? Ideally users would turn all errors into | ||||
| appropriate `http::Response`s. But in HTTP/2 and beyond, stream errors are | ||||
| different from HTTP Server Error responses. Could the `Service::Error` type do | ||||
| more to encourage best practices? | ||||
|  | ||||
| ## Design | ||||
|  | ||||
| The goal is to get hyper closer to the [VISION][], using that to determine the | ||||
| best way to solve the known issues above. The main thrust of the proposed | ||||
| changes are to make hyper more **Flexible** and stable. | ||||
|  | ||||
| In order to keep hyper **Understandable**, however, the proposed changes *must* | ||||
| be accompanied by providing utilities that solve the common usage patterns, | ||||
| documentation explaining how to use the more flexible pieces, and guides on how | ||||
| to reach for the `hyper-util`ity belt. | ||||
|  | ||||
| The majority of the changes are smaller and can be contained to the *Public | ||||
| API* section, since they usually only apply to a single module or type. But the | ||||
| biggest changes are explained in detail here. | ||||
|  | ||||
| ### Split per HTTP version | ||||
|  | ||||
| The existing `Connection` types, both for the client and server, abstract over | ||||
| HTTP version by requiring a generic `AsyncRead + AsyncWrite` transport type. | ||||
| But as we figure out HTTP/3, that needs to change. So to prepare now, the | ||||
| `Connection` types will be split up. | ||||
|  | ||||
| For example, there will now be `hyper::server::conn::http1::Connection` and | ||||
| `hyper::server::conn::http2::Connection` types. | ||||
|  | ||||
| These specific types will still have a very similar looking API that, as the | ||||
| VISION describes, provides **Correct** connection management as it pertains to | ||||
| HTTP. | ||||
|  | ||||
| There will be still be a type to wrap the different versions. It will no longer | ||||
| be generic over the transport type, to prepare for being able to wrap HTTP/3 | ||||
| connections. Exactly how it will wrap, either by using internal trait objects, | ||||
| or an `enum Either` style, or using a `trait Connection` that each type | ||||
| implements, is something to be determined. It's likely that this "auto" type | ||||
| will start in `hyper-util`. | ||||
|  | ||||
| ### Focus on the `Connection` level | ||||
|  | ||||
| As mentioned in the *Known Issues*, the higher-level `Client` and `Server` have | ||||
| stability and complexity problems. Therefore, for hyper 1.0, the main API will | ||||
| focus on the "lower-level" connection types. The `Client` and `Server` helpers | ||||
| will be moved to `hyper-util`. | ||||
|  | ||||
| ## Public API | ||||
|  | ||||
| ### body | ||||
|  | ||||
| The `Body` struct is removed. Its internal "variants" are [separated into | ||||
| distinct types](https://github.com/hyperium/hyper/issues/2345), and can start | ||||
| in either `hyper-util` or `http-body-util`. | ||||
|  | ||||
| The exported trait `HttpBody` is renamed to `Body`. | ||||
|  | ||||
| A single `Body` implementation in `hyper` is the one provided by receiving | ||||
| client responses and server requests. It has the name `Streaming`. | ||||
|  | ||||
| > **Unresolved**: Other names can be considered during implementation. Another | ||||
| > option is to not publicly name the implementation, but return `Response<impl | ||||
| Body>`s. | ||||
|  | ||||
| The `Body` trait will be experimented on to see about making it possible to | ||||
| return more frame types beyonds just data and trailers. | ||||
|  | ||||
| > **Unresolved**: What exactly this looks like will only be known after | ||||
| > experimentation. | ||||
|  | ||||
| ### client | ||||
|  | ||||
| The high-level `hyper::Client` will be removed, along with the | ||||
| `hyper::client::connect` module. They will be explored more in `hyper-util`. | ||||
|  | ||||
| As described in *Design*, the `client::conn` module will gain `http1` and | ||||
| `http2` sub-modules, providing per-version `SendRequest`, `Connection`, and | ||||
| `Builder` structs. An `auto` version can be explored in `hyper-util`. | ||||
|  | ||||
| ### error | ||||
|  | ||||
| The `hyper::Error` struct remains in place. | ||||
|  | ||||
| All errors returned from `Error::source()` are made opaque. They are wrapped an | ||||
| internal `Opaque` newtype that still allows printing, but prevents downcasting | ||||
| to the internal dependency. | ||||
|  | ||||
| A new `hyper::error::Code` struct is defined. It is an opaque struct, with | ||||
| associated constants defining various code variants. | ||||
|  | ||||
| > Alternative: define a non-exhaustive enum. It's not clear that this is | ||||
| > definitely better, though. Keeping it an opaque struct means we can add | ||||
| > secondary parts to the code in the future, or add bit flags, or similar | ||||
| > extensions. | ||||
|  | ||||
| The purpose of `Code` is to provide an abstraction over the kind of error that | ||||
| is encountered. The `Code` could be some behavior noticed inside hyper, such as | ||||
| an incomplete HTTP message. Or it can be "translated" from the underlying | ||||
| protocol, if it defines protocol level errors. For example, an | ||||
| `h2::Reason::CANCEL`. | ||||
|  | ||||
| ### rt | ||||
|  | ||||
| The `Executor` trait stays in here. | ||||
|  | ||||
| Define a new trait `Timer`, which describes a way for users to provide a source | ||||
| of sleeping/timeout futures. Similar to `Executor`, a new generic is added to | ||||
| connection builders to provide a `Timer`. | ||||
|  | ||||
| ### server | ||||
|  | ||||
| The higher-level `hyper::Server` struct, its related `Builder`, and the | ||||
| `Accept` trait are all removed. | ||||
|  | ||||
| The `AddrStream` struct will be completely removed, as it provides no value but | ||||
| causes binary bloat. | ||||
|  | ||||
| Similar to `client`, and as describe in the *Design*, the `conn` modules will | ||||
| be expanded to support `http1` and `http2` submodules. An `auto` version can be | ||||
| explored in `hyper-util`. | ||||
|  | ||||
| ### service | ||||
|  | ||||
| A vendored and simplified `Service` trait will be explored. | ||||
|  | ||||
| The error type for `Service`s used for a server will explore having the return | ||||
| type changed from any error to one that can become a `hyper::error::Code`. | ||||
|  | ||||
| > **Unresolved**: Both of the above points are not set in stone. We will | ||||
| > explore and decide if they are the best outcome during development. | ||||
|  | ||||
| The `MakeService` pieces will be removed. | ||||
|  | ||||
| ### Cargo Features | ||||
|  | ||||
| Remove the `stream` feature. The `Stream` trait is not stable, and we cannot | ||||
| depend on an unstable API. | ||||
|  | ||||
| Remove the `tcp` and `runtime` features. The automatic executor and timer parts | ||||
| are handled by providing implementations of `Executor` and `Timer`. The | ||||
| `connect` and `Accept` parts are also moving to `hyper-util`. | ||||
|  | ||||
| ### Public Dependencies | ||||
|  | ||||
| - `http` | ||||
| - `http-body` | ||||
| - `bytes` | ||||
|  | ||||
| Cannot be public while "unstable": | ||||
|  | ||||
| - `tracing` | ||||
|  | ||||
| ## `hyper-util` | ||||
|  | ||||
|  | ||||
| ### body | ||||
|  | ||||
| A channel implementation of `Body` that has an API to know when the data has | ||||
| been successfully written is provided in `hyper_util::body::channel`. | ||||
|  | ||||
| ### client | ||||
|  | ||||
| A `Pool` struct that implements `Service` is provided. It fills a similar role | ||||
| as the previous `hyper::Client`. | ||||
|  | ||||
| > **Note**: The `Pool` might be something that goes into the `tower` crate | ||||
| > instead. Or it might stay here as a slightly more specialized racing-connect | ||||
| > pool. We'll find out as we go. | ||||
|  | ||||
| A `connect` submodule that mostly mirrors the existing `hyper::client::connect` | ||||
| module is moved here. Connectors can be used as a source to provide `Service`s | ||||
| used by the `Pool`. | ||||
|  | ||||
| ### rt | ||||
|  | ||||
| We can provide Tokio-backed implementations of `Executor` and `Timer`. | ||||
|  | ||||
| ### server | ||||
|  | ||||
| A `GracefulShutdown` helper is provided, to allow for similar style of graceful | ||||
| shutdown as the previous `hyper::Server` did, but with better control. | ||||
|  | ||||
| # Appendix | ||||
|  | ||||
| ## Unresolved Questions | ||||
|  | ||||
| There are some parts of the proposal which are not fully resolved. They are | ||||
| mentioned in Design and API sections above, but also collected here for easy | ||||
| finding. While they all have _plans_, they are more exploratory parts of the | ||||
| API, and thus they have a higher possibility of changing as we implement them. | ||||
|  | ||||
| The goal is to have these questions resolved and removed from the document by | ||||
| the time there is a [Release Candidate][timeline]. | ||||
|  | ||||
| ### Should there be `hyper::io` traits? | ||||
|  | ||||
| Depending on `tokio` just for `AsyncRead` and `AsyncWrite` is convenient, but | ||||
| can be confusing for users integrating hyper with other runtimes. It also ties | ||||
| our version directly to Tokio. We can consider having vendored traits, and | ||||
| providing Tokio wrappers in `hyper-util`. | ||||
|  | ||||
| ### Should returned body types be `impl Body`? | ||||
|  | ||||
| ### How could the `Body` trait prepare for unknown frames? | ||||
|  | ||||
| We will experiment with this, and keep track of those experiments in a | ||||
| dedicated issue. It might be possible to use something like this: | ||||
|  | ||||
| ```rust | ||||
| pub trait Body { | ||||
|     type Data; | ||||
|     fn poll_frame(..) -> Result<Option<Frame<Self::Data>>>; | ||||
| } | ||||
|  | ||||
| pub struct Frame<T>(Kind<T>); | ||||
|  | ||||
| enum Kind<T> { | ||||
|    Data(T), | ||||
|    Trailers(HeaderMap), | ||||
|    Unknown(Box<dyn FrameThingy>), | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### Should there be a simplified `hyper::Service` trait, or should hyper depend on `tower-service`? | ||||
|  | ||||
| - There's still a few uncertain decisions around tower, such as if it should be | ||||
|   changed to `async fn call`, and if `poll_ready` is the best way to handle | ||||
|   backpressure. | ||||
| - It's not clear that the backpressure is something needed at the `Server` | ||||
|   boundary, thus meaning we should remove `poll_ready` from hyper. | ||||
| - It's not 100% clear if we should keep the service pattern, or use a | ||||
|   pull-based API. This will be explored in a future blog post. | ||||
|  | ||||
| ## FAQ | ||||
|  | ||||
| ### Why did you pick _that_ name? Why not this other better name? | ||||
|  | ||||
| Naming is hard. We certainly should solve it, but discussion for particular | ||||
| names for structs and traits should be scoped to the specific issues. This | ||||
| document is to define the shape of the library API. | ||||
|  | ||||
| ### Should I publicly depend on `hyper-util`? | ||||
|  | ||||
| The `hyper-util` crate will not reach 1.0 when `hyper` does. Some types and | ||||
| traits are being moved to `hyper-util`. As with any pre-1.0 crate, you _can_ | ||||
| publicly depend on it, but it is explicitly less stable. | ||||
|  | ||||
| In most cases, it's recommended to not publicly expose your dependency on | ||||
| `hyper-util`. If you depend on a trait, such as used by the moved higher-level | ||||
| `Client` or `Server`, it may be better for your users to define your own | ||||
| abstraction, and then make an internal adapter. | ||||
|  | ||||
| ### Isn't this making hyper harder? | ||||
|  | ||||
| We are making hyper more **flexible**. As noted in the [VISION][], most use | ||||
| cases of hyper require it to be flexible. That _can_ mean that the exposed API | ||||
| is lower level, and that it feels more complicated. It should still be | ||||
| **understandable**. | ||||
|  | ||||
| But the hyper 1.0 effort is more than just the single `hyper` crate. Many | ||||
| useful helpers will be migrated to a `hyper-util` crate, and likely improved in | ||||
| the process. The [timeline][] also points out that we will have a significant | ||||
| documentation push. While the flexible pieces will be in hyper to compose how | ||||
| they need, we will also write guides for the [hyper.rs][] showing people how to | ||||
| accomplish the most common tasks. | ||||
|  | ||||
| [timeline]: https://seanmonstar.com/post/676912131372875776/hyper-10-timeline | ||||
| [VISION]: https://github.com/hyperium/hyper/pull/2772 | ||||
| [hyper.rs]: https://hyper.rs | ||||
							
								
								
									
										100
									
								
								docs/TENETS.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										100
									
								
								docs/TENETS.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,100 @@ | ||||
| # Charter | ||||
|  | ||||
| > hyper is a protective and efficient HTTP library for all. | ||||
|  | ||||
| # Tenets | ||||
|  | ||||
| Tenets are guiding principles. They guide how decisions are made for the whole | ||||
| project. Ideally, we do all of them all the time. In some cases, though, we may | ||||
| be forced to decide between slightly penalizing one goal or another. In that | ||||
| case, we tend to support those goals that come earlier in the list over those | ||||
| that come later (but every case is different). | ||||
|  | ||||
| ## 0. Open | ||||
|  | ||||
| hyper is open source, always. The success of hyper depends on the health of the | ||||
| community building and using it. All contributions are in the open. We don't | ||||
| maintain private versions, and don't include features that aren't useful to | ||||
| others. | ||||
|  | ||||
| [We prioritize kindness][CONDUCT], compassion and empathy towards all | ||||
| contributors. Technical skill is not a substitute for human decency. | ||||
|  | ||||
| [CONDUCT]: https://github.com/hyperium/hyper/blob/master/docs/CODE_OF_CONDUCT.md | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| It's not usually hard for an open source library to stay open and also meet its | ||||
| other priorities. Here's some instances where being **Open** would be more | ||||
| important than **Correct** or **Fast**: | ||||
|  | ||||
| - Say an individual were to bring forward a contribution that makes hyper more | ||||
|   correct, or faster, perhaps fixing some serious bug. But in doing so, they | ||||
|   also insulted people, harassed other contributors or users, or shamed | ||||
|   everyone for the previous code. They felt their contribution was "invaluable". | ||||
|   We would not accept such a contribution, instead banning the user and | ||||
|   rewriting the code amongst the kind collaborators of the project. | ||||
|  | ||||
| - Say someone brings a contribution that adds a new feature useful for | ||||
|   performance or correctness, but their work accomplishes this by integrating | ||||
|   hyper with a proprietary library. We would not accept such a contribution, | ||||
|   because we don't want such a feature limited only to those users willing to | ||||
|   compromise openness, and we don't want to bifurcate the ecosystem between those | ||||
|   who make that compromise and those who don't. | ||||
|  | ||||
| ## 1. Correct | ||||
|  | ||||
| hyper is a memory safe and precise implementation of the HTTP specification. | ||||
| Memory safety is vital in a core Internet technology. Following the HTTP | ||||
| specifications correctly protects users. It makes the software durable to the | ||||
| “real world”. Where feasible, hyper enforces correct usage. | ||||
|  | ||||
| This is more than just "don't write bugs". hyper actively protects the user. | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| - Even though we follow the **HTTP/\*** specs, hyper doesn't blindly implement | ||||
|   everything without considering if it is safe to do so. | ||||
|  | ||||
| ## 2. Fast | ||||
|  | ||||
| A fast experience delights users. A faster network library means a faster | ||||
| application, resulting in delighting our users’ users. Whether with one request, | ||||
| or millions. | ||||
|  | ||||
| Being _fast_ means we improve throughput, drive down CPU usage, and improve | ||||
| sustainability. | ||||
|  | ||||
| Fast _enough_. We don't sacrifice sanity for speed. | ||||
|  | ||||
| ## 3. HTTP/* | ||||
|  | ||||
| hyper is specifically focused on HTTP. Supporting new HTTP versions is in scope, | ||||
| but supporting separate protocols is not. | ||||
|  | ||||
| This also defines what the abstraction layer is: the API is designed around | ||||
| sending and receiving HTTP messages. | ||||
|  | ||||
| ## 4. Flexible | ||||
|  | ||||
| hyper enables as many usecases as possible. It has no opinion on application | ||||
| structure, and makes few assumptions about its environment. This includes being | ||||
| portable to different operating systems. | ||||
|  | ||||
| ### Examples | ||||
|  | ||||
| - While we choose safer defaults to be **Correct**, hyper includes options to | ||||
|   _allow_ different behavior, when the user requires them. | ||||
| - Providing choice usually makes things more complex, so being **Flexible** does | ||||
|   mean it's less _easy_. That can sometimes conflict with simplest way of making | ||||
|   hyper **Understandable**. | ||||
|  | ||||
| ## 5. Understandable | ||||
|  | ||||
| hyper is [no more complicated than it has to | ||||
| be](https://en.wikipedia.org/wiki/Occam%27s_razor). HTTP is not simple. It may | ||||
| not be as "easy" as 1-line to do everything, but it shouldn't be "hard" to find | ||||
| the answers. | ||||
|  | ||||
| From logical and misuse-resistant APIs, to stellar documentation, to transparent | ||||
| metrics. | ||||
							
								
								
									
										230
									
								
								docs/VISION.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										230
									
								
								docs/VISION.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,230 @@ | ||||
| # hyper Vision | ||||
|  | ||||
| ## Purpose | ||||
|  | ||||
| This is an overview of what the shape of hyper looks like, but also somewhat | ||||
| zoomed out, so that the _vision_ can survive while the exact minute details | ||||
| might shift and change over time. | ||||
|  | ||||
| ### Charter | ||||
|  | ||||
| > hyper is a protective and efficient HTTP library for all. | ||||
|  | ||||
| ### Tenets | ||||
|  | ||||
| Tenets are guiding principles. They guide how decisions are made for the whole | ||||
| project. Ideally, we do all of them all the time. In some cases, though, we may | ||||
| be forced to decide between slightly penalizing one goal or another. In that | ||||
| case, we tend to support those goals that come earlier in the list over those | ||||
| that come later (but every case is different). | ||||
|  | ||||
| 0. Open | ||||
| 1. Correct | ||||
| 2. Fast | ||||
| 3. HTTP/\* | ||||
| 4. Flexible | ||||
| 5. Understandable | ||||
|  | ||||
| There's a lot more detail about each in [TENETS](./TENETS.md). | ||||
|  | ||||
| ## Use Cases | ||||
|  | ||||
| Who are the *users* of hyper? How would they use hyper? | ||||
|  | ||||
| ### Low-Level Client Library (curl, reqwest, aws-sdk) | ||||
|  | ||||
| These client libraries care that hyper is **Flexible**, since they are | ||||
| expressing their own opinion on how a more-featured HTTP client should act. | ||||
| This includes opinions on connection establishment, management, pooling, HTTP | ||||
| version options, and even runtimes. | ||||
|  | ||||
| curl's main reason for using hyper is that it is **Safe**. | ||||
|  | ||||
| ### Web Server Frameworks (deno, axum) | ||||
|  | ||||
| These are using hyper's server feature to expose a different, higher-level API | ||||
| to users. Besides the obvious requirements, these require that hyper is | ||||
| **Fast**. Servers are costly, handling more requests faster is important to | ||||
| them. | ||||
|  | ||||
| That hyper is **Flexible** is also important, in that it needs to be flexible | ||||
| enough for them to build a server framework, and allow them to express their | ||||
| own opinions about API to their users. | ||||
|  | ||||
| ### Services and Proxies (linkerd, cloudflare, fastly) | ||||
|  | ||||
| These are using hyper directly, likely both the client and server, in order to | ||||
| build efficient and powerful services, applications, and tools for their end | ||||
| users. They care greatly that hyper is **Correct**, since web traffic can | ||||
| stretch the limits of what is valid HTTP, and exercise less-common parts of the | ||||
| specifications. | ||||
|  | ||||
| They also require hyper to be **Fast**, for similar reasons that the web server | ||||
| frameworks do. | ||||
|  | ||||
| ### New Rust Web Developers | ||||
|  | ||||
| These are developers who are either new to Rust, or new to web servers, and | ||||
| have reached for hyper to start with. | ||||
|  | ||||
| It's likely that these users don't have strong opinions about how an HTTP | ||||
| server or client should work, just that it _should_ handle all the things they | ||||
| normally assume it would. For these users, it would be best to quickly help | ||||
| them compare their own expectations with hyper's capabilities, and may | ||||
| suggest reaching for higher-level, _easier_ libraries instead. | ||||
|  | ||||
| Those that stick around after that recommendation are users that wish both to | ||||
| learn at a lower level, and to pick and choose what batteries they plug in to | ||||
| hyper as they move along. While they do care about the other tenets, that hyper | ||||
| is **Understandable** is of extra importance to them. | ||||
|  | ||||
| ## The Library | ||||
|  | ||||
| So with all that context in mind, what does hyper, the library, actually look | ||||
| like? This doesn't highlight what _is_ and _isn't_ present. What currently | ||||
| needs to change to reach this vision is left to individual version roadmaps. | ||||
|  | ||||
| ### Layers | ||||
|  | ||||
| In all cases, a user brings their own runtime and IO to work with hyper. The IO | ||||
| is provided to hyper, and hyper acts on top of it. hyper returns `Future`s that | ||||
| the user then decides how to poll, likely involving their runtime options. | ||||
|  | ||||
|  | ||||
|  | ||||
|  | ||||
| #### Protocol Codecs | ||||
|  | ||||
| hyper has dedicated codecs for the major HTTP versions. Each is internally | ||||
| designed to be **Correct** and **Fast** when it comes to encoding and decoding. | ||||
|  | ||||
| The individual codecs may be implemented as sub-crates, with a less-stable | ||||
| promise, to support the **Flexible** needs of some users who wish to build | ||||
| their own connection management, or customize encoding and decoding beyond what | ||||
| is officially supported. | ||||
|  | ||||
| #### Connection State Management | ||||
|  | ||||
| A **Correct** implementation includes more than just enforcing certain | ||||
| characters when encoding and decoding. Order of frames, and flags in certain | ||||
| frames can affect the state of the connection. Some examples of things enforced | ||||
| at this layer: | ||||
|  | ||||
| - If a message has a `content-length`, enforce only that many bytes are read or | ||||
|   written. | ||||
| - Reading a `Response` before a `Request` is even written implies a mismatched | ||||
|   reply that should be interpreted as an error. | ||||
| - The presence of some headers, such as `Connection: close`, or the absence of | ||||
|   others, such as `content-length` and `transfer-encoding`, can mean that the | ||||
|   connection should terminate after the current message. | ||||
| - HTTP/2 and HTTP/3 may send connection-level frames that don't pertain to any | ||||
|   specific transaction, and must be read and handled regardless of if a user is | ||||
|   currently checking for a message. | ||||
|  | ||||
| #### HTTP Role and Version Abstraction | ||||
|  | ||||
| This is the public API layer. Methods exposed are around sending and receiving | ||||
| `http::Request`s and `http::Response`s, not around framing specifics of the | ||||
| different versions. These are built around a client or server `Connection` | ||||
| interface. | ||||
|  | ||||
| By exposing this layer publicly, we take care of the **Correct** tenet, by not | ||||
| forcing the user to send the specific frames themselves. The API should be | ||||
| designed in a way that a user cannot easily (if at all) create an _incorrect_ | ||||
| HTTP connection. | ||||
|  | ||||
| Motivated by the **Flexible** tenet, there _are_ version-specific options that | ||||
| can be configured at this level, and version-specific functionality can usually | ||||
| be handled via `http::Extensions`. | ||||
|  | ||||
| ### Not quite stable, but utile (useful) | ||||
|  | ||||
| Beyond what is directly in the hyper crate, there are useful (utile) parts that | ||||
| may not meet hyper's stability promise. Developing, experimenting, and exposing | ||||
| those parts is the purpose of the `hyper-util` crate. That crate does not have | ||||
| the same stability level as hyper. However, the goal is that things that other | ||||
| libraries might want to expose as a public dependency do not live in | ||||
| `hyper-util` forever, but rather stabilize and get promoted into `hyper`. | ||||
|  | ||||
| Exactly what gets put into `hyper-util` presently is kept in the roadmap | ||||
| documents. | ||||
|  | ||||
| ### Stability Promise | ||||
|  | ||||
| What even is hyper's stability promise? Does it mean we are "done"? No. Will we | ||||
| ever make breaking changes again? Probably. We'll still follow the [semantic | ||||
| versioning](https://semver.org). | ||||
|  | ||||
| Prior to 1.0, hyper has already only done breaking changes once a year. So 1 | ||||
| year isn't much of a promise. We'll have significant more use and understanding | ||||
| after a few years, and that could prompt some redesign. | ||||
|  | ||||
| As of this writing, we'll promise that _major_ versions of hyper are stable for | ||||
| 3 years. New features will come out in _minor_ versions frequently. If it is | ||||
| determined necessary to make breaking changes to the API, we'll save them for | ||||
| after the 3 years. | ||||
|  | ||||
| hyper also establishes a Minimum Supported Rust Version (MSRV). hyper will | ||||
| support Rust versions at least 6 months old. If a new Rust version is released | ||||
| with a feature hyper wishes to use, we won't do so until at least 6 months | ||||
| afterwards. hyper will only ever require a new Rust version as a _minor_ | ||||
| release (1.x), not as a patch (1.x.y). | ||||
|  | ||||
| ## Security | ||||
|  | ||||
| The security of hyper is a large part of what makes hyper _protective_. We make | ||||
| hyper secure via the combined efforts of being **Correct**, focusing on | ||||
| **HTTP/\***, and making it all **Understandable**. | ||||
|  | ||||
| ### Memory Safety | ||||
|  | ||||
| Being **Correct** requires that hyper be memory-safe. Using the Rust language | ||||
| gets us most of the way there. But there is the ability to write `unsafe` | ||||
| Rust. Does being **Correct** mean that we can _never_ write `unsafe` code | ||||
| anywhere? Even if it helps make hyper **Fast**? We can, carefully. | ||||
|  | ||||
| How do we balance the two, so that hyper is secure? | ||||
|  | ||||
| hyper prefers not to have large modules of intertwined `unsafe` code. hyper | ||||
| does allow small `unsafe` blocks, no more than a few lines, where it's easier | ||||
| to verify that the `unsafe` code was written **Correctly**. | ||||
|  | ||||
| ### Meticulous Testing | ||||
|  | ||||
| hyper's test suite grows and grows. There's a lot that needs to be right. | ||||
| Parsers, encoders, state machines. When easily isolated, those pieces have | ||||
| internal unit tests. But hyper also keeps a large list of growing integration | ||||
| tests that make sure all the parts are **Correct**. | ||||
|  | ||||
| Making writing new tests easy is a high priority. Investing in the testing | ||||
| infrastructure is a proven way to make sure hyper stays **Correct** and secure. | ||||
|  | ||||
| ### Constant Fuzzing | ||||
|  | ||||
| One thing is to know specific cases to test for. But we can't know all the | ||||
| inputs or states that *might* cause a bug. That's why hyper has rounds of | ||||
| fuzzing built into its CI. It's also why hyper signs up for and uses resources | ||||
| to provide *constant*, around-the-clock fuzzing, always looking for something | ||||
| that hyper should be hardened against. | ||||
|  | ||||
| ### Security Process | ||||
|  | ||||
| hyper has an outlined | ||||
| [SECURITY](https://github.com/hyperium/hyper/blob/master/SECURITY.md) process, | ||||
| so we can safely report and fix issues. | ||||
|  | ||||
| ## Non-goals | ||||
|  | ||||
| After writing this up, it is easier to articulate what sorts of things many | ||||
| might associate with an HTTP library, but which are explicitly *not* for hyper. | ||||
| These are all things that definitely **out of scope**. | ||||
|  | ||||
| - TLS: We learned early that bundling TLS directly in hyper [has | ||||
|   problems](https://github.com/hyperium/hyper/issues/985). People also have | ||||
|   very strong opinions about which TLS implementation to use. The design of | ||||
|   hyper allows users to bring their own TLS. | ||||
| - Routing | ||||
| - Cookies | ||||
| - Not-HTTP: WebSockets, or other protocols that are built next to HTTP. It | ||||
|   should be possible to _use_ hyper to upgrade, but the actual next-protocol | ||||
|   should be handled by a different library. | ||||
							
								
								
									
										4
									
								
								docs/vision-arch.svg
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										4
									
								
								docs/vision-arch.svg
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| After Width: | Height: | Size: 9.7 KiB | 
| @@ -2,8 +2,9 @@ | ||||
| #![warn(rust_2018_idioms)] | ||||
| use std::env; | ||||
|  | ||||
| use hyper::{body::HttpBody as _, Client}; | ||||
| use hyper::{body::HttpBody as _, Body, Request}; | ||||
| use tokio::io::{self, AsyncWriteExt as _}; | ||||
| use tokio::net::TcpStream; | ||||
|  | ||||
| // A simple type alias so as to DRY. | ||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | ||||
| @@ -33,9 +34,20 @@ async fn main() -> Result<()> { | ||||
| } | ||||
|  | ||||
| async fn fetch_url(url: hyper::Uri) -> Result<()> { | ||||
|     let client = Client::new(); | ||||
|     let host = url.host().expect("uri has no host"); | ||||
|     let port = url.port_u16().unwrap_or(80); | ||||
|     let addr = format!("{}:{}", host, port); | ||||
|     let stream = TcpStream::connect(addr).await?; | ||||
|  | ||||
|     let mut res = client.get(url).await?; | ||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; | ||||
|     tokio::task::spawn(async move { | ||||
|         if let Err(err) = conn.await { | ||||
|             println!("Connection failed: {:?}", err); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let req = Request::builder().uri(url).body(Body::empty()).unwrap(); | ||||
|     let mut res = sender.send_request(req).await?; | ||||
|  | ||||
|     println!("Response: {}", res.status()); | ||||
|     println!("Headers: {:#?}\n", res.headers()); | ||||
|   | ||||
| @@ -1,9 +1,10 @@ | ||||
| #![deny(warnings)] | ||||
| #![warn(rust_2018_idioms)] | ||||
|  | ||||
| use hyper::body::Buf; | ||||
| use hyper::Client; | ||||
| use hyper::Body; | ||||
| use hyper::{body::Buf, Request}; | ||||
| use serde::Deserialize; | ||||
| use tokio::net::TcpStream; | ||||
|  | ||||
| // A simple type alias so as to DRY. | ||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | ||||
| @@ -22,10 +23,22 @@ async fn main() -> Result<()> { | ||||
| } | ||||
|  | ||||
| async fn fetch_json(url: hyper::Uri) -> Result<Vec<User>> { | ||||
|     let client = Client::new(); | ||||
|     let host = url.host().expect("uri has no host"); | ||||
|     let port = url.port_u16().unwrap_or(80); | ||||
|     let addr = format!("{}:{}", host, port); | ||||
|  | ||||
|     let stream = TcpStream::connect(addr).await?; | ||||
|  | ||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; | ||||
|     tokio::task::spawn(async move { | ||||
|         if let Err(err) = conn.await { | ||||
|             println!("Connection failed: {:?}", err); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // Fetch the url... | ||||
|     let res = client.get(url).await?; | ||||
|     let req = Request::builder().uri(url).body(Body::empty()).unwrap(); | ||||
|     let res = sender.send_request(req).await?; | ||||
|  | ||||
|     // asynchronously aggregate the chunks of the body | ||||
|     let body = hyper::body::aggregate(res).await?; | ||||
|   | ||||
| @@ -1,8 +1,11 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use futures_util::TryStreamExt; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Method, Request, Response, Server, StatusCode}; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Method, Request, Response, StatusCode}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| /// This is our service handler. It receives a Request, routes on its | ||||
| /// path, and returns a Future of a Response. | ||||
| @@ -16,16 +19,17 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||
|         // Simply echo the body back to the client. | ||||
|         (&Method::POST, "/echo") => Ok(Response::new(req.into_body())), | ||||
|  | ||||
|         // TODO: Fix this, broken in PR #2896 | ||||
|         // Convert to uppercase before sending back to client using a stream. | ||||
|         (&Method::POST, "/echo/uppercase") => { | ||||
|             let chunk_stream = req.into_body().map_ok(|chunk| { | ||||
|                 chunk | ||||
|                     .iter() | ||||
|                     .map(|byte| byte.to_ascii_uppercase()) | ||||
|                     .collect::<Vec<u8>>() | ||||
|             }); | ||||
|             Ok(Response::new(Body::wrap_stream(chunk_stream))) | ||||
|         } | ||||
|         // (&Method::POST, "/echo/uppercase") => { | ||||
|         // let chunk_stream = req.into_body().map_ok(|chunk| { | ||||
|         //     chunk | ||||
|         //         .iter() | ||||
|         //         .map(|byte| byte.to_ascii_uppercase()) | ||||
|         //         .collect::<Vec<u8>>() | ||||
|         // }); | ||||
|         // Ok(Response::new(Body::wrap_stream(chunk_stream))) | ||||
|         // } | ||||
|  | ||||
|         // Reverse the entire body before sending back to the client. | ||||
|         // | ||||
| @@ -51,15 +55,17 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|     let addr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     let service = make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(echo)) }); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(service); | ||||
|     let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||||
|  | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|     server.await?; | ||||
|  | ||||
|     Ok(()) | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new().serve_connection(stream, service_fn(echo)).await { | ||||
|                 println!("Error serving connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,51 +1,63 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Client, Error, Server}; | ||||
| use hyper::{server::conn::Http, service::service_fn}; | ||||
| use std::net::SocketAddr; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let in_addr = ([127, 0, 0, 1], 3001).into(); | ||||
|     let in_addr: SocketAddr = ([127, 0, 0, 1], 3001).into(); | ||||
|     let out_addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     let client_main = Client::new(); | ||||
|  | ||||
|     let out_addr_clone = out_addr.clone(); | ||||
|  | ||||
|     // The closure inside `make_service_fn` is run for each connection, | ||||
|     // creating a 'service' to handle requests for that specific connection. | ||||
|     let make_service = make_service_fn(move |_| { | ||||
|         let client = client_main.clone(); | ||||
|  | ||||
|         async move { | ||||
|             // This is the `Service` that will handle the connection. | ||||
|             // `service_fn` is a helper to convert a function that | ||||
|             // returns a Response into a `Service`. | ||||
|             Ok::<_, Error>(service_fn(move |mut req| { | ||||
|                 let uri_string = format!( | ||||
|                     "http://{}{}", | ||||
|                     out_addr_clone, | ||||
|                     req.uri() | ||||
|                         .path_and_query() | ||||
|                         .map(|x| x.as_str()) | ||||
|                         .unwrap_or("/") | ||||
|                 ); | ||||
|                 let uri = uri_string.parse().unwrap(); | ||||
|                 *req.uri_mut() = uri; | ||||
|                 client.request(req) | ||||
|             })) | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let server = Server::bind(&in_addr).serve(make_service); | ||||
|     let listener = TcpListener::bind(in_addr).await?; | ||||
|  | ||||
|     println!("Listening on http://{}", in_addr); | ||||
|     println!("Proxying on http://{}", out_addr); | ||||
|  | ||||
|     if let Err(e) = server.await { | ||||
|         eprintln!("server error: {}", e); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|         // This is the `Service` that will handle the connection. | ||||
|         // `service_fn` is a helper to convert a function that | ||||
|         // returns a Response into a `Service`. | ||||
|         let service = service_fn(move |mut req| { | ||||
|             let uri_string = format!( | ||||
|                 "http://{}{}", | ||||
|                 out_addr_clone, | ||||
|                 req.uri() | ||||
|                     .path_and_query() | ||||
|                     .map(|x| x.as_str()) | ||||
|                     .unwrap_or("/") | ||||
|             ); | ||||
|             let uri = uri_string.parse().unwrap(); | ||||
|             *req.uri_mut() = uri; | ||||
|  | ||||
|             let host = req.uri().host().expect("uri has no host"); | ||||
|             let port = req.uri().port_u16().unwrap_or(80); | ||||
|             let addr = format!("{}:{}", host, port); | ||||
|  | ||||
|             async move { | ||||
|                 let client_stream = TcpStream::connect(addr).await.unwrap(); | ||||
|  | ||||
|                 let (mut sender, conn) = hyper::client::conn::handshake(client_stream).await?; | ||||
|                 tokio::task::spawn(async move { | ||||
|                     if let Err(err) = conn.await { | ||||
|                         println!("Connection failed: {:?}", err); | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 sender.send_request(req).await | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new().serve_connection(stream, service).await { | ||||
|                 println!("Failed to servce connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,9 +1,12 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use std::convert::Infallible; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Request, Response, Server}; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Request, Response}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> { | ||||
|     Ok(Response::new(Body::from("Hello World!"))) | ||||
| @@ -13,22 +16,20 @@ async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> { | ||||
| pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     // For every connection, we must make a `Service` to handle all | ||||
|     // incoming HTTP requests on said connection. | ||||
|     let make_svc = make_service_fn(|_conn| { | ||||
|         // This is the `Service` that will handle the connection. | ||||
|         // `service_fn` is a helper to convert a function that | ||||
|         // returns a Response into a `Service`. | ||||
|         async { Ok::<_, Infallible>(service_fn(hello)) } | ||||
|     }); | ||||
|  | ||||
|     let addr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(make_svc); | ||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|     server.await?; | ||||
|  | ||||
|     Ok(()) | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new() | ||||
|                 .serve_connection(stream, service_fn(hello)) | ||||
|                 .await | ||||
|             { | ||||
|                 println!("Error serving connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,15 +1,14 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use std::convert::Infallible; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::client::conn::Builder; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::upgrade::Upgraded; | ||||
| use hyper::{Body, Client, Method, Request, Response, Server}; | ||||
| use hyper::{Body, Method, Request, Response}; | ||||
|  | ||||
| use tokio::net::TcpStream; | ||||
|  | ||||
| type HttpClient = Client<hyper::client::HttpConnector>; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
|  | ||||
| // To try this example: | ||||
| // 1. cargo run --example http_proxy | ||||
| @@ -19,32 +18,29 @@ type HttpClient = Client<hyper::client::HttpConnector>; | ||||
| // 3. send requests | ||||
| //    $ curl -i https://www.some_domain.com/ | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); | ||||
|  | ||||
|     let client = Client::builder() | ||||
|         .http1_title_case_headers(true) | ||||
|         .http1_preserve_header_case(true) | ||||
|         .build_http(); | ||||
|  | ||||
|     let make_service = make_service_fn(move |_| { | ||||
|         let client = client.clone(); | ||||
|         async move { Ok::<_, Infallible>(service_fn(move |req| proxy(client.clone(), req))) } | ||||
|     }); | ||||
|  | ||||
|     let server = Server::bind(&addr) | ||||
|         .http1_preserve_header_case(true) | ||||
|         .http1_title_case_headers(true) | ||||
|         .serve(make_service); | ||||
|  | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|  | ||||
|     if let Err(e) = server.await { | ||||
|         eprintln!("server error: {}", e); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new() | ||||
|                 .http1_preserve_header_case(true) | ||||
|                 .http1_title_case_headers(true) | ||||
|                 .serve_connection(stream, service_fn(proxy)) | ||||
|                 .await | ||||
|             { | ||||
|                 println!("Failed to serve connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||
| async fn proxy(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||
|     println!("req: {:?}", req); | ||||
|  | ||||
|     if Method::CONNECT == req.method() { | ||||
| @@ -82,7 +78,24 @@ async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>, | ||||
|             Ok(resp) | ||||
|         } | ||||
|     } else { | ||||
|         client.request(req).await | ||||
|         let host = req.uri().host().expect("uri has no host"); | ||||
|         let port = req.uri().port_u16().unwrap_or(80); | ||||
|         let addr = format!("{}:{}", host, port); | ||||
|  | ||||
|         let stream = TcpStream::connect(addr).await.unwrap(); | ||||
|  | ||||
|         let (mut sender, conn) = Builder::new() | ||||
|             .http1_preserve_header_case(true) | ||||
|             .http1_title_case_headers(true) | ||||
|             .handshake(stream) | ||||
|             .await?; | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = conn.await { | ||||
|                 println!("Connection failed: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|  | ||||
|         sender.send_request(req).await | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,9 +1,13 @@ | ||||
| #![deny(warnings)] | ||||
| #![warn(rust_2018_idioms)] | ||||
|  | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use futures_util::future::join; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Request, Response, Server}; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Request, Response}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| static INDEX1: &[u8] = b"The 1st service!"; | ||||
| static INDEX2: &[u8] = b"The 2nd service!"; | ||||
| @@ -20,16 +24,40 @@ async fn index2(_: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let addr1 = ([127, 0, 0, 1], 1337).into(); | ||||
|     let addr2 = ([127, 0, 0, 1], 1338).into(); | ||||
|     let addr1: SocketAddr = ([127, 0, 0, 1], 1337).into(); | ||||
|     let addr2: SocketAddr = ([127, 0, 0, 1], 1338).into(); | ||||
|  | ||||
|     let srv1 = Server::bind(&addr1).serve(make_service_fn(|_| async { | ||||
|         Ok::<_, hyper::Error>(service_fn(index1)) | ||||
|     })); | ||||
|     let srv1 = async move { | ||||
|         let listener = TcpListener::bind(addr1).await.unwrap(); | ||||
|         loop { | ||||
|             let (stream, _) = listener.accept().await.unwrap(); | ||||
|  | ||||
|     let srv2 = Server::bind(&addr2).serve(make_service_fn(|_| async { | ||||
|         Ok::<_, hyper::Error>(service_fn(index2)) | ||||
|     })); | ||||
|             tokio::task::spawn(async move { | ||||
|                 if let Err(err) = Http::new() | ||||
|                     .serve_connection(stream, service_fn(index1)) | ||||
|                     .await | ||||
|                 { | ||||
|                     println!("Error serving connection: {:?}", err); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     let srv2 = async move { | ||||
|         let listener = TcpListener::bind(addr2).await.unwrap(); | ||||
|         loop { | ||||
|             let (stream, _) = listener.accept().await.unwrap(); | ||||
|  | ||||
|             tokio::task::spawn(async move { | ||||
|                 if let Err(err) = Http::new() | ||||
|                     .serve_connection(stream, service_fn(index2)) | ||||
|                     .await | ||||
|                 { | ||||
|                     println!("Error serving connection: {:?}", err); | ||||
|                 } | ||||
|             }); | ||||
|         } | ||||
|     }; | ||||
|  | ||||
|     println!("Listening on http://{} and http://{}", addr1, addr2); | ||||
|  | ||||
|   | ||||
| @@ -1,10 +1,13 @@ | ||||
| // #![deny(warnings)]  // FIXME: https://github.com/rust-lang/rust/issues/62411 | ||||
| #![warn(rust_2018_idioms)] | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Method, Request, Response, Server, StatusCode}; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Method, Request, Response, StatusCode}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| use std::collections::HashMap; | ||||
| use std::net::SocketAddr; | ||||
| use url::form_urlencoded; | ||||
|  | ||||
| static INDEX: &[u8] = b"<html><body><form action=\"post\" method=\"post\">Name: <input type=\"text\" name=\"name\"><br>Number: <input type=\"text\" name=\"number\"><br><input type=\"submit\"></body></html>"; | ||||
| @@ -102,15 +105,20 @@ async fn param_example(req: Request<Body>) -> Result<Response<Body>, hyper::Erro | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let addr = ([127, 0, 0, 1], 1337).into(); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(make_service_fn(|_| async { | ||||
|         Ok::<_, hyper::Error>(service_fn(param_example)) | ||||
|     })); | ||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 1337).into(); | ||||
|  | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|     server.await?; | ||||
|  | ||||
|     Ok(()) | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new() | ||||
|                 .serve_connection(stream, service_fn(param_example)) | ||||
|                 .await | ||||
|             { | ||||
|                 println!("Error serving connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,30 +1,36 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use tokio::fs::File; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use tokio_util::codec::{BytesCodec, FramedRead}; | ||||
| use hyper::server::conn::Http; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Method, Request, Response, Result, Server, StatusCode}; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Method, Request, Response, Result, StatusCode}; | ||||
|  | ||||
| static INDEX: &str = "examples/send_file_index.html"; | ||||
| static NOTFOUND: &[u8] = b"Not Found"; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
| async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let addr = "127.0.0.1:1337".parse().unwrap(); | ||||
|  | ||||
|     let make_service = | ||||
|         make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(response_examples)) }); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(make_service); | ||||
|     let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); | ||||
|  | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|  | ||||
|     if let Err(e) = server.await { | ||||
|         eprintln!("server error: {}", e); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new() | ||||
|                 .serve_connection(stream, service_fn(response_examples)) | ||||
|                 .await | ||||
|             { | ||||
|                 println!("Failed to serve connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -48,11 +54,8 @@ fn not_found() -> Response<Body> { | ||||
| } | ||||
|  | ||||
| async fn simple_file_send(filename: &str) -> Result<Response<Body>> { | ||||
|     // Serve a file by asynchronously reading it by chunks using tokio-util crate. | ||||
|  | ||||
|     if let Ok(file) = File::open(filename).await { | ||||
|         let stream = FramedRead::new(file, BytesCodec::new()); | ||||
|         let body = Body::wrap_stream(stream); | ||||
|     if let Ok(contents) = tokio::fs::read(filename).await { | ||||
|         let body = contents.into(); | ||||
|         return Ok(Response::new(body)); | ||||
|     } | ||||
|  | ||||
|   | ||||
| @@ -1,7 +1,10 @@ | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::Service; | ||||
| use hyper::{Body, Request, Response, Server}; | ||||
| use hyper::{Body, Request, Response}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| use std::future::Future; | ||||
| use std::net::SocketAddr; | ||||
| use std::pin::Pin; | ||||
| use std::task::{Context, Poll}; | ||||
|  | ||||
| @@ -9,13 +12,23 @@ type Counter = i32; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||
|     let addr = ([127, 0, 0, 1], 3000).into(); | ||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(MakeSvc { counter: 81818 }); | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|  | ||||
|     server.await?; | ||||
|     Ok(()) | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new() | ||||
|                 .serve_connection(stream, Svc { counter: 81818 }) | ||||
|                 .await | ||||
|             { | ||||
|                 println!("Failed to serve connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct Svc { | ||||
| @@ -54,23 +67,3 @@ impl Service<Request<Body>> for Svc { | ||||
|         Box::pin(async { res }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct MakeSvc { | ||||
|     counter: Counter, | ||||
| } | ||||
|  | ||||
| impl<T> Service<T> for MakeSvc { | ||||
|     type Response = Svc; | ||||
|     type Error = hyper::Error; | ||||
|     type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; | ||||
|  | ||||
|     fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, _: T) -> Self::Future { | ||||
|         let counter = self.counter.clone(); | ||||
|         let fut = async move { Ok(Svc { counter }) }; | ||||
|         Box::pin(fut) | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,13 +1,15 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use hyper::server::conn::Http; | ||||
| use std::cell::Cell; | ||||
| use std::net::SocketAddr; | ||||
| use std::rc::Rc; | ||||
| use tokio::sync::oneshot; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| use hyper::body::{Bytes, HttpBody}; | ||||
| use hyper::header::{HeaderMap, HeaderValue}; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Error, Response, Server}; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Error, Response}; | ||||
| use std::marker::PhantomData; | ||||
| use std::pin::Pin; | ||||
| use std::task::{Context, Poll}; | ||||
| @@ -46,7 +48,7 @@ impl HttpBody for Body { | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn main() { | ||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     // Configure a runtime that runs everything on the current thread | ||||
| @@ -57,43 +59,39 @@ fn main() { | ||||
|  | ||||
|     // Combine it with a `LocalSet,  which means it can spawn !Send futures... | ||||
|     let local = tokio::task::LocalSet::new(); | ||||
|     local.block_on(&rt, run()); | ||||
|     local.block_on(&rt, run()) | ||||
| } | ||||
|  | ||||
| async fn run() { | ||||
|     let addr = ([127, 0, 0, 1], 3000).into(); | ||||
| async fn run() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     // Using a !Send request counter is fine on 1 thread... | ||||
|     let counter = Rc::new(Cell::new(0)); | ||||
|  | ||||
|     let make_service = make_service_fn(move |_| { | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|         // For each connection, clone the counter to use in our service... | ||||
|         let cnt = counter.clone(); | ||||
|  | ||||
|         async move { | ||||
|             Ok::<_, Error>(service_fn(move |_| { | ||||
|                 let prev = cnt.get(); | ||||
|                 cnt.set(prev + 1); | ||||
|                 let value = cnt.get(); | ||||
|                 async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) } | ||||
|             })) | ||||
|         } | ||||
|     }); | ||||
|         let service = service_fn(move |_| { | ||||
|             let prev = cnt.get(); | ||||
|             cnt.set(prev + 1); | ||||
|             let value = cnt.get(); | ||||
|             async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) } | ||||
|         }); | ||||
|  | ||||
|     let server = Server::bind(&addr).executor(LocalExec).serve(make_service); | ||||
|  | ||||
|     // Just shows that with_graceful_shutdown compiles with !Send, | ||||
|     // !Sync HttpBody. | ||||
|     let (_tx, rx) = oneshot::channel::<()>(); | ||||
|     let server = server.with_graceful_shutdown(async move { | ||||
|         rx.await.ok(); | ||||
|     }); | ||||
|  | ||||
|     println!("Listening on http://{}", addr); | ||||
|  | ||||
|     // The server would block on current thread to await !Send futures. | ||||
|     if let Err(e) = server.await { | ||||
|         eprintln!("server error: {}", e); | ||||
|         tokio::task::spawn_local(async move { | ||||
|             if let Err(err) = Http::new() | ||||
|                 .with_executor(LocalExec) | ||||
|                 .serve_connection(stream, service) | ||||
|                 .await | ||||
|             { | ||||
|                 println!("Error serving connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -1,52 +1,46 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use std::net::SocketAddr; | ||||
| use std::sync::{ | ||||
|     atomic::{AtomicUsize, Ordering}, | ||||
|     Arc, | ||||
| }; | ||||
|  | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Error, Response, Server}; | ||||
| use hyper::{server::conn::Http, service::service_fn}; | ||||
| use hyper::{Body, Error, Response}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() { | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let addr = ([127, 0, 0, 1], 3000).into(); | ||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); | ||||
|  | ||||
|     // For the most basic of state, we just share a counter, that increments | ||||
|     // with each request, and we send its value back in the response. | ||||
|     let counter = Arc::new(AtomicUsize::new(0)); | ||||
|  | ||||
|     // The closure inside `make_service_fn` is run for each connection, | ||||
|     // creating a 'service' to handle requests for that specific connection. | ||||
|     let make_service = make_service_fn(move |_| { | ||||
|         // While the state was moved into the make_service closure, | ||||
|         // we need to clone it here because this closure is called | ||||
|         // once for every connection. | ||||
|         // | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|         // Each connection could send multiple requests, so | ||||
|         // the `Service` needs a clone to handle later requests. | ||||
|         let counter = counter.clone(); | ||||
|  | ||||
|         async move { | ||||
|             // This is the `Service` that will handle the connection. | ||||
|             // `service_fn` is a helper to convert a function that | ||||
|             // returns a Response into a `Service`. | ||||
|             Ok::<_, Error>(service_fn(move |_req| { | ||||
|                 // Get the current count, and also increment by 1, in a single | ||||
|                 // atomic operation. | ||||
|                 let count = counter.fetch_add(1, Ordering::AcqRel); | ||||
|                 async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) } | ||||
|             })) | ||||
|         // This is the `Service` that will handle the connection. | ||||
|         // `service_fn` is a helper to convert a function that | ||||
|         // returns a Response into a `Service`. | ||||
|         let service = service_fn(move |_req| { | ||||
|             // Get the current count, and also increment by 1, in a single | ||||
|             // atomic operation. | ||||
|             let count = counter.fetch_add(1, Ordering::AcqRel); | ||||
|             async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) } | ||||
|         }); | ||||
|  | ||||
|         if let Err(err) = Http::new().serve_connection(stream, service).await { | ||||
|             println!("Error serving connection: {:?}", err); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(make_service); | ||||
|  | ||||
|     println!("Listening on http://{}", addr); | ||||
|  | ||||
|     if let Err(e) = server.await { | ||||
|         eprintln!("server error: {}", e); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -1,27 +0,0 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use hyper::client::conn::Builder; | ||||
| use hyper::client::connect::HttpConnector; | ||||
| use hyper::client::service::Connect; | ||||
| use hyper::service::Service; | ||||
| use hyper::{Body, Request}; | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let mut mk_svc = Connect::new(HttpConnector::new(), Builder::new()); | ||||
|  | ||||
|     let uri = "http://127.0.0.1:8080".parse::<http::Uri>()?; | ||||
|  | ||||
|     let mut svc = mk_svc.call(uri.clone()).await?; | ||||
|  | ||||
|     let body = Body::empty(); | ||||
|  | ||||
|     let req = Request::get(uri).body(body)?; | ||||
|     let res = svc.call(req).await?; | ||||
|  | ||||
|     println!("RESPONSE={:?}", res); | ||||
|  | ||||
|     Ok(()) | ||||
| } | ||||
| @@ -1,10 +1,13 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use std::net::SocketAddr; | ||||
| use std::task::{Context, Poll}; | ||||
|  | ||||
| use futures_util::future; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::Service; | ||||
| use hyper::{Body, Request, Response, Server}; | ||||
| use hyper::{Body, Request, Response}; | ||||
| use tokio::net::TcpListener; | ||||
|  | ||||
| const ROOT: &str = "/"; | ||||
|  | ||||
| @@ -36,33 +39,22 @@ impl Service<Request<Body>> for Svc { | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub struct MakeSvc; | ||||
|  | ||||
| impl<T> Service<T> for MakeSvc { | ||||
|     type Response = Svc; | ||||
|     type Error = std::io::Error; | ||||
|     type Future = future::Ready<Result<Self::Response, Self::Error>>; | ||||
|  | ||||
|     fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||
|         Ok(()).into() | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, _: T) -> Self::Future { | ||||
|         future::ok(Svc) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[tokio::main] | ||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let addr = "127.0.0.1:1337".parse().unwrap(); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(MakeSvc); | ||||
|     let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); | ||||
|  | ||||
|     let listener = TcpListener::bind(addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|  | ||||
|     server.await?; | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|     Ok(()) | ||||
|         tokio::task::spawn(async move { | ||||
|             if let Err(err) = Http::new().serve_connection(stream, Svc).await { | ||||
|                 println!("Failed to serve connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -3,13 +3,15 @@ | ||||
| // Note: `hyper::upgrade` docs link to this upgrade. | ||||
| use std::str; | ||||
|  | ||||
| use hyper::server::conn::Http; | ||||
| use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
| use tokio::sync::oneshot; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
| use tokio::sync::watch; | ||||
|  | ||||
| use hyper::header::{HeaderValue, UPGRADE}; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::upgrade::Upgraded; | ||||
| use hyper::{Body, Client, Request, Response, Server, StatusCode}; | ||||
| use hyper::{Body, Request, Response, StatusCode}; | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| // A simple type alias so as to DRY. | ||||
| @@ -92,7 +94,17 @@ async fn client_upgrade_request(addr: SocketAddr) -> Result<()> { | ||||
|         .body(Body::empty()) | ||||
|         .unwrap(); | ||||
|  | ||||
|     let res = Client::new().request(req).await?; | ||||
|     let stream = TcpStream::connect(addr).await?; | ||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; | ||||
|  | ||||
|     tokio::task::spawn(async move { | ||||
|         if let Err(err) = conn.await { | ||||
|             println!("Connection failed: {:?}", err); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let res = sender.send_request(req).await?; | ||||
|  | ||||
|     if res.status() != StatusCode::SWITCHING_PROTOCOLS { | ||||
|         panic!("Our server didn't upgrade: {}", res.status()); | ||||
|     } | ||||
| @@ -114,28 +126,52 @@ async fn main() { | ||||
|     // For this example, we just make a server and our own client to talk to | ||||
|     // it, so the exact port isn't important. Instead, let the OS give us an | ||||
|     // unused port. | ||||
|     let addr = ([127, 0, 0, 1], 0).into(); | ||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); | ||||
|  | ||||
|     let make_service = | ||||
|         make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(server_upgrade)) }); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(make_service); | ||||
|     let listener = TcpListener::bind(addr).await.expect("failed to bind"); | ||||
|  | ||||
|     // We need the assigned address for the client to send it messages. | ||||
|     let addr = server.local_addr(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
|  | ||||
|     // For this example, a oneshot is used to signal that after 1 request, | ||||
|     // the server should be shutdown. | ||||
|     let (tx, rx) = oneshot::channel::<()>(); | ||||
|     let server = server.with_graceful_shutdown(async move { | ||||
|         rx.await.ok(); | ||||
|     }); | ||||
|     let (tx, mut rx) = watch::channel(false); | ||||
|  | ||||
|     // Spawn server on the default executor, | ||||
|     // which is usually a thread-pool from tokio default runtime. | ||||
|     tokio::task::spawn(async move { | ||||
|         if let Err(e) = server.await { | ||||
|             eprintln!("server error: {}", e); | ||||
|         loop { | ||||
|             tokio::select! { | ||||
|                 res = listener.accept() => { | ||||
|                     let (stream, _) = res.expect("Failed to accept"); | ||||
|  | ||||
|                     let mut rx = rx.clone(); | ||||
|                     tokio::task::spawn(async move { | ||||
|                         let conn = Http::new().serve_connection(stream, service_fn(server_upgrade)); | ||||
|  | ||||
|                         // Don't forget to enable upgrades on the connection. | ||||
|                         let mut conn = conn.with_upgrades(); | ||||
|  | ||||
|                         let mut conn = Pin::new(&mut conn); | ||||
|  | ||||
|                         tokio::select! { | ||||
|                             res = &mut conn => { | ||||
|                                 if let Err(err) = res { | ||||
|                                     println!("Error serving connection: {:?}", err); | ||||
|                                     return; | ||||
|                                 } | ||||
|                             } | ||||
|                             // Continue polling the connection after enabling graceful shutdown. | ||||
|                             _ = rx.changed() => { | ||||
|                                 conn.graceful_shutdown(); | ||||
|                             } | ||||
|                         } | ||||
|                     }); | ||||
|                 } | ||||
|                 _ = rx.changed() => { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|  | ||||
| @@ -147,5 +183,5 @@ async fn main() { | ||||
|  | ||||
|     // Complete the oneshot so that the server stops | ||||
|     // listening and the process can close down. | ||||
|     let _ = tx.send(()); | ||||
|     let _ = tx.send(true); | ||||
| } | ||||
|   | ||||
| @@ -1,10 +1,12 @@ | ||||
| #![deny(warnings)] | ||||
|  | ||||
| use std::net::SocketAddr; | ||||
|  | ||||
| use bytes::Buf; | ||||
| use futures_util::{stream, StreamExt}; | ||||
| use hyper::client::HttpConnector; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{header, Body, Client, Method, Request, Response, Server, StatusCode}; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{header, Body, Method, Request, Response, StatusCode}; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
|  | ||||
| type GenericError = Box<dyn std::error::Error + Send + Sync>; | ||||
| type Result<T> = std::result::Result<T, GenericError>; | ||||
| @@ -15,7 +17,7 @@ static NOTFOUND: &[u8] = b"Not Found"; | ||||
| static POST_DATA: &str = r#"{"original": "data"}"#; | ||||
| static URL: &str = "http://127.0.0.1:1337/json_api"; | ||||
|  | ||||
| async fn client_request_response(client: &Client<HttpConnector>) -> Result<Response<Body>> { | ||||
| async fn client_request_response() -> Result<Response<Body>> { | ||||
|     let req = Request::builder() | ||||
|         .method(Method::POST) | ||||
|         .uri(URL) | ||||
| @@ -23,19 +25,23 @@ async fn client_request_response(client: &Client<HttpConnector>) -> Result<Respo | ||||
|         .body(POST_DATA.into()) | ||||
|         .unwrap(); | ||||
|  | ||||
|     let web_res = client.request(req).await?; | ||||
|     // Compare the JSON we sent (before) with what we received (after): | ||||
|     let before = stream::once(async { | ||||
|         Ok(format!( | ||||
|             "<b>POST request body</b>: {}<br><b>Response</b>: ", | ||||
|             POST_DATA, | ||||
|         ) | ||||
|         .into()) | ||||
|     }); | ||||
|     let after = web_res.into_body(); | ||||
|     let body = Body::wrap_stream(before.chain(after)); | ||||
|     let host = req.uri().host().expect("uri has no host"); | ||||
|     let port = req.uri().port_u16().expect("uri has no port"); | ||||
|     let stream = TcpStream::connect(format!("{}:{}", host, port)).await?; | ||||
|  | ||||
|     Ok(Response::new(body)) | ||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; | ||||
|  | ||||
|     tokio::task::spawn(async move { | ||||
|         if let Err(err) = conn.await { | ||||
|             println!("Connection error: {:?}", err); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let web_res = sender.send_request(req).await?; | ||||
|  | ||||
|     let res_body = web_res.into_body(); | ||||
|  | ||||
|     Ok(Response::new(res_body)) | ||||
| } | ||||
|  | ||||
| async fn api_post_response(req: Request<Body>) -> Result<Response<Body>> { | ||||
| @@ -69,13 +75,10 @@ async fn api_get_response() -> Result<Response<Body>> { | ||||
|     Ok(res) | ||||
| } | ||||
|  | ||||
| async fn response_examples( | ||||
|     req: Request<Body>, | ||||
|     client: Client<HttpConnector>, | ||||
| ) -> Result<Response<Body>> { | ||||
| async fn response_examples(req: Request<Body>) -> Result<Response<Body>> { | ||||
|     match (req.method(), req.uri().path()) { | ||||
|         (&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())), | ||||
|         (&Method::GET, "/test.html") => client_request_response(&client).await, | ||||
|         (&Method::GET, "/test.html") => client_request_response().await, | ||||
|         (&Method::POST, "/json_api") => api_post_response(req).await, | ||||
|         (&Method::GET, "/json_api") => api_get_response().await, | ||||
|         _ => { | ||||
| @@ -92,27 +95,19 @@ async fn response_examples( | ||||
| async fn main() -> Result<()> { | ||||
|     pretty_env_logger::init(); | ||||
|  | ||||
|     let addr = "127.0.0.1:1337".parse().unwrap(); | ||||
|  | ||||
|     // Share a `Client` with all `Service`s | ||||
|     let client = Client::new(); | ||||
|  | ||||
|     let new_service = make_service_fn(move |_| { | ||||
|         // Move a clone of `client` into the `service_fn`. | ||||
|         let client = client.clone(); | ||||
|         async { | ||||
|             Ok::<_, GenericError>(service_fn(move |req| { | ||||
|                 // Clone again to ensure that client outlives this closure. | ||||
|                 response_examples(req, client.to_owned()) | ||||
|             })) | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let server = Server::bind(&addr).serve(new_service); | ||||
|     let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); | ||||
|  | ||||
|     let listener = TcpListener::bind(&addr).await?; | ||||
|     println!("Listening on http://{}", addr); | ||||
|     loop { | ||||
|         let (stream, _) = listener.accept().await?; | ||||
|  | ||||
|     server.await?; | ||||
|         tokio::task::spawn(async move { | ||||
|             let service = service_fn(move |req| response_examples(req)); | ||||
|  | ||||
|     Ok(()) | ||||
|             if let Err(err) = Http::new().serve_connection(stream, service).await { | ||||
|                 println!("Failed to serve connection: {:?}", err); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										178
									
								
								src/body/body.rs
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								src/body/body.rs
									
									
									
									
									
								
							| @@ -1,23 +1,15 @@ | ||||
| use std::borrow::Cow; | ||||
| #[cfg(feature = "stream")] | ||||
| use std::error::Error as StdError; | ||||
| use std::fmt; | ||||
|  | ||||
| use bytes::Bytes; | ||||
| use futures_channel::mpsc; | ||||
| use futures_channel::oneshot; | ||||
| use futures_core::Stream; // for mpsc::Receiver | ||||
| #[cfg(feature = "stream")] | ||||
| use futures_util::TryStreamExt; | ||||
| use http::HeaderMap; | ||||
| use http_body::{Body as HttpBody, SizeHint}; | ||||
|  | ||||
| use super::DecodedLength; | ||||
| #[cfg(feature = "stream")] | ||||
| use crate::common::sync_wrapper::SyncWrapper; | ||||
| use crate::common::Future; | ||||
| #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||
| use crate::common::Never; | ||||
| use crate::common::{task, watch, Pin, Poll}; | ||||
| #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | ||||
| use crate::proto::h2::ping; | ||||
| @@ -35,9 +27,6 @@ type TrailersSender = oneshot::Sender<HeaderMap>; | ||||
| #[must_use = "streams do nothing unless polled"] | ||||
| pub struct Body { | ||||
|     kind: Kind, | ||||
|     /// Keep the extra bits in an `Option<Box<Extra>>`, so that | ||||
|     /// Body stays small in the common case (no extras needed). | ||||
|     extra: Option<Box<Extra>>, | ||||
| } | ||||
|  | ||||
| enum Kind { | ||||
| @@ -56,40 +45,6 @@ enum Kind { | ||||
|     }, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     Ffi(crate::ffi::UserBody), | ||||
|     #[cfg(feature = "stream")] | ||||
|     Wrapped( | ||||
|         SyncWrapper< | ||||
|             Pin<Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>>, | ||||
|         >, | ||||
|     ), | ||||
| } | ||||
|  | ||||
| struct Extra { | ||||
|     /// Allow the client to pass a future to delay the `Body` from returning | ||||
|     /// EOF. This allows the `Client` to try to put the idle connection | ||||
|     /// back into the pool before the body is "finished". | ||||
|     /// | ||||
|     /// The reason for this is so that creating a new request after finishing | ||||
|     /// streaming the body of a response could sometimes result in creating | ||||
|     /// a brand new connection, since the pool didn't know about the idle | ||||
|     /// connection yet. | ||||
|     delayed_eof: Option<DelayEof>, | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||
| type DelayEofUntil = oneshot::Receiver<Never>; | ||||
|  | ||||
| enum DelayEof { | ||||
|     /// Initial state, stream hasn't seen EOF yet. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     NotEof(DelayEofUntil), | ||||
|     /// Transitions to this state once we've seen `poll` try to | ||||
|     /// return EOF (`None`). This future is then polled, and | ||||
|     /// when it completes, the Body finally returns EOF (`None`). | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     Eof(DelayEofUntil), | ||||
| } | ||||
|  | ||||
| /// A sender half created through [`Body::channel()`]. | ||||
| @@ -164,41 +119,8 @@ impl Body { | ||||
|         (tx, rx) | ||||
|     } | ||||
|  | ||||
|     /// Wrap a futures `Stream` in a box inside `Body`. | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # use hyper::Body; | ||||
|     /// let chunks: Vec<Result<_, std::io::Error>> = vec![ | ||||
|     ///     Ok("hello"), | ||||
|     ///     Ok(" "), | ||||
|     ///     Ok("world"), | ||||
|     /// ]; | ||||
|     /// | ||||
|     /// let stream = futures_util::stream::iter(chunks); | ||||
|     /// | ||||
|     /// let body = Body::wrap_stream(stream); | ||||
|     /// ``` | ||||
|     /// | ||||
|     /// # Optional | ||||
|     /// | ||||
|     /// This function requires enabling the `stream` feature in your | ||||
|     /// `Cargo.toml`. | ||||
|     #[cfg(feature = "stream")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "stream")))] | ||||
|     pub fn wrap_stream<S, O, E>(stream: S) -> Body | ||||
|     where | ||||
|         S: Stream<Item = Result<O, E>> + Send + 'static, | ||||
|         O: Into<Bytes> + 'static, | ||||
|         E: Into<Box<dyn StdError + Send + Sync>> + 'static, | ||||
|     { | ||||
|         let mapped = stream.map_ok(Into::into).map_err(Into::into); | ||||
|         Body::new(Kind::Wrapped(SyncWrapper::new(Box::pin(mapped)))) | ||||
|     } | ||||
|  | ||||
|     fn new(kind: Kind) -> Body { | ||||
|         Body { kind, extra: None } | ||||
|         Body { kind } | ||||
|     } | ||||
|  | ||||
|     #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | ||||
| @@ -221,62 +143,6 @@ impl Body { | ||||
|         body | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     pub(crate) fn delayed_eof(&mut self, fut: DelayEofUntil) { | ||||
|         self.extra_mut().delayed_eof = Some(DelayEof::NotEof(fut)); | ||||
|     } | ||||
|  | ||||
|     fn take_delayed_eof(&mut self) -> Option<DelayEof> { | ||||
|         self.extra | ||||
|             .as_mut() | ||||
|             .and_then(|extra| extra.delayed_eof.take()) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     fn extra_mut(&mut self) -> &mut Extra { | ||||
|         self.extra | ||||
|             .get_or_insert_with(|| Box::new(Extra { delayed_eof: None })) | ||||
|     } | ||||
|  | ||||
|     fn poll_eof(&mut self, cx: &mut task::Context<'_>) -> Poll<Option<crate::Result<Bytes>>> { | ||||
|         match self.take_delayed_eof() { | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "client")] | ||||
|             Some(DelayEof::NotEof(mut delay)) => match self.poll_inner(cx) { | ||||
|                 ok @ Poll::Ready(Some(Ok(..))) | ok @ Poll::Pending => { | ||||
|                     self.extra_mut().delayed_eof = Some(DelayEof::NotEof(delay)); | ||||
|                     ok | ||||
|                 } | ||||
|                 Poll::Ready(None) => match Pin::new(&mut delay).poll(cx) { | ||||
|                     Poll::Ready(Ok(never)) => match never {}, | ||||
|                     Poll::Pending => { | ||||
|                         self.extra_mut().delayed_eof = Some(DelayEof::Eof(delay)); | ||||
|                         Poll::Pending | ||||
|                     } | ||||
|                     Poll::Ready(Err(_done)) => Poll::Ready(None), | ||||
|                 }, | ||||
|                 Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))), | ||||
|             }, | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "client")] | ||||
|             Some(DelayEof::Eof(mut delay)) => match Pin::new(&mut delay).poll(cx) { | ||||
|                 Poll::Ready(Ok(never)) => match never {}, | ||||
|                 Poll::Pending => { | ||||
|                     self.extra_mut().delayed_eof = Some(DelayEof::Eof(delay)); | ||||
|                     Poll::Pending | ||||
|                 } | ||||
|                 Poll::Ready(Err(_done)) => Poll::Ready(None), | ||||
|             }, | ||||
|             #[cfg(any( | ||||
|                 not(any(feature = "http1", feature = "http2")), | ||||
|                 not(feature = "client") | ||||
|             ))] | ||||
|             Some(delay_eof) => match delay_eof {}, | ||||
|             None => self.poll_inner(cx), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "ffi")] | ||||
|     pub(crate) fn as_ffi_mut(&mut self) -> &mut crate::ffi::UserBody { | ||||
|         match self.kind { | ||||
| @@ -329,12 +195,6 @@ impl Body { | ||||
|  | ||||
|             #[cfg(feature = "ffi")] | ||||
|             Kind::Ffi(ref mut body) => body.poll_data(cx), | ||||
|  | ||||
|             #[cfg(feature = "stream")] | ||||
|             Kind::Wrapped(ref mut s) => match ready!(s.get_mut().as_mut().poll_next(cx)) { | ||||
|                 Some(res) => Poll::Ready(Some(res.map_err(crate::Error::new_body))), | ||||
|                 None => Poll::Ready(None), | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -364,7 +224,7 @@ impl HttpBody for Body { | ||||
|         mut self: Pin<&mut Self>, | ||||
|         cx: &mut task::Context<'_>, | ||||
|     ) -> Poll<Option<Result<Self::Data, Self::Error>>> { | ||||
|         self.poll_eof(cx) | ||||
|         self.poll_inner(cx) | ||||
|     } | ||||
|  | ||||
|     fn poll_trailers( | ||||
| @@ -405,8 +265,6 @@ impl HttpBody for Body { | ||||
|             Kind::H2 { recv: ref h2, .. } => h2.is_end_stream(), | ||||
|             #[cfg(feature = "ffi")] | ||||
|             Kind::Ffi(..) => false, | ||||
|             #[cfg(feature = "stream")] | ||||
|             Kind::Wrapped(..) => false, | ||||
|         } | ||||
|     } | ||||
|  | ||||
| @@ -426,8 +284,6 @@ impl HttpBody for Body { | ||||
|         match self.kind { | ||||
|             Kind::Once(Some(ref val)) => SizeHint::with_exact(val.len() as u64), | ||||
|             Kind::Once(None) => SizeHint::with_exact(0), | ||||
|             #[cfg(feature = "stream")] | ||||
|             Kind::Wrapped(..) => SizeHint::default(), | ||||
|             Kind::Chan { content_length, .. } => opt_len!(content_length), | ||||
|             #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | ||||
|             Kind::H2 { content_length, .. } => opt_len!(content_length), | ||||
| @@ -457,33 +313,6 @@ impl fmt::Debug for Body { | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// # Optional | ||||
| /// | ||||
| /// This function requires enabling the `stream` feature in your | ||||
| /// `Cargo.toml`. | ||||
| #[cfg(feature = "stream")] | ||||
| impl Stream for Body { | ||||
|     type Item = crate::Result<Bytes>; | ||||
|  | ||||
|     fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Option<Self::Item>> { | ||||
|         HttpBody::poll_data(self, cx) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// # Optional | ||||
| /// | ||||
| /// This function requires enabling the `stream` feature in your | ||||
| /// `Cargo.toml`. | ||||
| #[cfg(feature = "stream")] | ||||
| impl From<Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>> for Body { | ||||
|     #[inline] | ||||
|     fn from( | ||||
|         stream: Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>, | ||||
|     ) -> Body { | ||||
|         Body::new(Kind::Wrapped(SyncWrapper::new(stream.into()))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl From<Bytes> for Body { | ||||
|     #[inline] | ||||
|     fn from(chunk: Bytes) -> Body { | ||||
| @@ -690,6 +519,7 @@ mod tests { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[cfg(not(miri))] | ||||
|     #[tokio::test] | ||||
|     async fn channel_abort() { | ||||
|         let (tx, mut rx) = Body::channel(); | ||||
| @@ -700,6 +530,7 @@ mod tests { | ||||
|         assert!(err.is_body_write_aborted(), "{:?}", err); | ||||
|     } | ||||
|  | ||||
|     #[cfg(not(miri))] | ||||
|     #[tokio::test] | ||||
|     async fn channel_abort_when_buffer_is_full() { | ||||
|         let (mut tx, mut rx) = Body::channel(); | ||||
| @@ -726,6 +557,7 @@ mod tests { | ||||
|         assert_eq!(chunk2, "chunk 2"); | ||||
|     } | ||||
|  | ||||
|     #[cfg(not(miri))] | ||||
|     #[tokio::test] | ||||
|     async fn channel_empty() { | ||||
|         let (_, mut rx) = Body::channel(); | ||||
|   | ||||
| @@ -17,17 +17,11 @@ use super::HttpBody; | ||||
| /// # Example | ||||
| /// | ||||
| /// ``` | ||||
| /// # #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||
| /// # async fn doc() -> hyper::Result<()> { | ||||
| /// use hyper::{body::HttpBody}; | ||||
| /// | ||||
| /// # let request = hyper::Request::builder() | ||||
| /// #        .method(hyper::Method::POST) | ||||
| /// #        .uri("http://httpbin.org/post") | ||||
| /// #        .header("content-type", "application/json") | ||||
| /// #        .body(hyper::Body::from(r#"{"library":"hyper"}"#)).unwrap(); | ||||
| /// # let client = hyper::Client::new(); | ||||
| /// let response = client.request(request).await?; | ||||
| /// # use hyper::{Body, Response}; | ||||
| /// # use hyper::body::HttpBody; | ||||
| /// # | ||||
| /// let response = Response::new(Body::from("response body")); | ||||
| /// | ||||
| /// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024; | ||||
| /// | ||||
|   | ||||
							
								
								
									
										1462
									
								
								src/client/client.rs
									
									
									
									
									
								
							
							
						
						
									
										1462
									
								
								src/client/client.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										504
									
								
								src/client/conn/http1.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										504
									
								
								src/client/conn/http1.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,504 @@ | ||||
| //! HTTP/1 client connections | ||||
|  | ||||
| use std::error::Error as StdError; | ||||
| use std::fmt; | ||||
| use std::sync::Arc; | ||||
|  | ||||
| use http::{Request, Response}; | ||||
| use httparse::ParserConfig; | ||||
| use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| use crate::Body; | ||||
| use crate::body::HttpBody; | ||||
| use crate::common::{ | ||||
|     exec::{BoxSendFuture, Exec}, | ||||
|     task, Future, Pin, Poll, | ||||
| }; | ||||
| use crate::upgrade::Upgraded; | ||||
| use crate::proto; | ||||
| use crate::rt::Executor; | ||||
| use super::super::dispatch; | ||||
|  | ||||
| type Dispatcher<T, B> = | ||||
|     proto::dispatch::Dispatcher<proto::dispatch::Client<B>, B, T, proto::h1::ClientTransaction>; | ||||
|  | ||||
| /// The sender side of an established connection. | ||||
| pub struct SendRequest<B> { | ||||
|     dispatch: dispatch::Sender<Request<B>, Response<Body>>, | ||||
| } | ||||
|  | ||||
| /// A future that processes all HTTP state for the IO object. | ||||
| /// | ||||
| /// In most cases, this should just be spawned into an executor, so that it | ||||
| /// can process incoming and outgoing messages, notice hangups, and the like. | ||||
| #[must_use = "futures do nothing unless polled"] | ||||
| pub struct Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     inner: Option<Dispatcher<T, B>>, | ||||
| } | ||||
|  | ||||
| /// A builder to configure an HTTP connection. | ||||
| /// | ||||
| /// After setting options, the builder is used to create a handshake future. | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct Builder { | ||||
|     pub(super) exec: Exec, | ||||
|     h09_responses: bool, | ||||
|     h1_parser_config: ParserConfig, | ||||
|     h1_writev: Option<bool>, | ||||
|     h1_title_case_headers: bool, | ||||
|     h1_preserve_header_case: bool, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     h1_preserve_header_order: bool, | ||||
|     h1_read_buf_exact_size: Option<usize>, | ||||
|     h1_max_buf_size: Option<usize>, | ||||
| } | ||||
|  | ||||
| /// Returns a handshake future over some IO. | ||||
| /// | ||||
| /// This is a shortcut for `Builder::new().handshake(io)`. | ||||
| /// See [`client::conn`](crate::client::conn) for more. | ||||
| pub async fn handshake<T>( | ||||
|     io: T, | ||||
| ) -> crate::Result<(SendRequest<crate::Body>, Connection<T, crate::Body>)> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
| { | ||||
|     Builder::new().handshake(io).await | ||||
| } | ||||
|  | ||||
| // ===== impl SendRequest | ||||
|  | ||||
| impl<B> SendRequest<B> { | ||||
|     /// Polls to determine whether this sender can be used yet for a request. | ||||
|     /// | ||||
|     /// If the associated connection is closed, this returns an Error. | ||||
|     pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> { | ||||
|         self.dispatch.poll_ready(cx) | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     pub(super) async fn when_ready(self) -> crate::Result<Self> { | ||||
|         let mut me = Some(self); | ||||
|         future::poll_fn(move |cx| { | ||||
|             ready!(me.as_mut().unwrap().poll_ready(cx))?; | ||||
|             Poll::Ready(Ok(me.take().unwrap())) | ||||
|         }) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub(super) fn is_ready(&self) -> bool { | ||||
|         self.dispatch.is_ready() | ||||
|     } | ||||
|  | ||||
|     pub(super) fn is_closed(&self) -> bool { | ||||
|         self.dispatch.is_closed() | ||||
|     } | ||||
|     */ | ||||
| } | ||||
|  | ||||
| impl<B> SendRequest<B> | ||||
| where | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     /// Sends a `Request` on the associated connection. | ||||
|     /// | ||||
|     /// Returns a future that if successful, yields the `Response`. | ||||
|     /// | ||||
|     /// # Note | ||||
|     /// | ||||
|     /// There are some key differences in what automatic things the `Client` | ||||
|     /// does for you that will not be done here: | ||||
|     /// | ||||
|     /// - `Client` requires absolute-form `Uri`s, since the scheme and | ||||
|     ///   authority are needed to connect. They aren't required here. | ||||
|     /// - Since the `Client` requires absolute-form `Uri`s, it can add | ||||
|     ///   the `Host` header based on it. You must add a `Host` header yourself | ||||
|     ///   before calling this method. | ||||
|     /// - Since absolute-form `Uri`s are not required, if received, they will | ||||
|     ///   be serialized as-is. | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # use http::header::HOST; | ||||
|     /// # use hyper::client::conn::SendRequest; | ||||
|     /// # use hyper::Body; | ||||
|     /// use hyper::Request; | ||||
|     /// | ||||
|     /// # async fn doc(mut tx: SendRequest<Body>) -> hyper::Result<()> { | ||||
|     /// // build a Request | ||||
|     /// let req = Request::builder() | ||||
|     ///     .uri("/foo/bar") | ||||
|     ///     .header(HOST, "hyper.rs") | ||||
|     ///     .body(Body::empty()) | ||||
|     ///     .unwrap(); | ||||
|     /// | ||||
|     /// // send it and await a Response | ||||
|     /// let res = tx.send_request(req).await?; | ||||
|     /// // assert the Response | ||||
|     /// assert!(res.status().is_success()); | ||||
|     /// # Ok(()) | ||||
|     /// # } | ||||
|     /// # fn main() {} | ||||
|     /// ``` | ||||
|     pub fn send_request(&mut self, req: Request<B>) -> impl Future<Output = crate::Result<Response<Body>>> { | ||||
|         let sent = self.dispatch.send(req); | ||||
|  | ||||
|         async move { | ||||
|             match sent { | ||||
|                 Ok(rx) => match rx.await { | ||||
|                     Ok(Ok(resp)) => Ok(resp), | ||||
|                     Ok(Err(err)) => Err(err), | ||||
|                     // this is definite bug if it happens, but it shouldn't happen! | ||||
|                     Err(_canceled) => panic!("dispatch dropped without returning error"), | ||||
|                 } | ||||
|                 Err(_req) => { | ||||
|                     tracing::debug!("connection was not ready"); | ||||
|  | ||||
|                     Err(crate::Error::new_canceled().with("connection was not ready")) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     pub(super) fn send_request_retryable( | ||||
|         &mut self, | ||||
|         req: Request<B>, | ||||
|     ) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> + Unpin | ||||
|     where | ||||
|         B: Send, | ||||
|     { | ||||
|         match self.dispatch.try_send(req) { | ||||
|             Ok(rx) => { | ||||
|                 Either::Left(rx.then(move |res| { | ||||
|                     match res { | ||||
|                         Ok(Ok(res)) => future::ok(res), | ||||
|                         Ok(Err(err)) => future::err(err), | ||||
|                         // this is definite bug if it happens, but it shouldn't happen! | ||||
|                         Err(_) => panic!("dispatch dropped without returning error"), | ||||
|                     } | ||||
|                 })) | ||||
|             } | ||||
|             Err(req) => { | ||||
|                 tracing::debug!("connection was not ready"); | ||||
|                 let err = crate::Error::new_canceled().with("connection was not ready"); | ||||
|                 Either::Right(future::err((err, Some(req)))) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     */ | ||||
| } | ||||
|  | ||||
| impl<B> fmt::Debug for SendRequest<B> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("SendRequest").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Connection | ||||
|  | ||||
| impl<T, B> fmt::Debug for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + fmt::Debug + Send + 'static, | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("Connection").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, B> Future for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     B: HttpBody + Send + 'static, | ||||
|     B::Data: Send, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
| { | ||||
|     type Output = crate::Result<()>; | ||||
|  | ||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         match ready!(Pin::new(self.inner.as_mut().unwrap()).poll(cx))? { | ||||
|             proto::Dispatched::Shutdown => Poll::Ready(Ok(())), | ||||
|             proto::Dispatched::Upgrade(pending) => match self.inner.take() { | ||||
|                 Some(h1) => { | ||||
|                     let (io, buf, _) = h1.into_inner(); | ||||
|                     pending.fulfill(Upgraded::new(io, buf)); | ||||
|                     Poll::Ready(Ok(())) | ||||
|                 } | ||||
|                 _ => { | ||||
|                     drop(pending); | ||||
|                     unreachable!("Upgraded twice"); | ||||
|                 } | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Builder | ||||
|  | ||||
| impl Builder { | ||||
|     /// Creates a new connection builder. | ||||
|     #[inline] | ||||
|     pub fn new() -> Builder { | ||||
|         Builder { | ||||
|             exec: Exec::Default, | ||||
|             h09_responses: false, | ||||
|             h1_writev: None, | ||||
|             h1_read_buf_exact_size: None, | ||||
|             h1_parser_config: Default::default(), | ||||
|             h1_title_case_headers: false, | ||||
|             h1_preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             h1_preserve_header_order: false, | ||||
|             h1_max_buf_size: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Provide an executor to execute background HTTP2 tasks. | ||||
|     pub fn executor<E>(&mut self, exec: E) -> &mut Builder | ||||
|     where | ||||
|         E: Executor<BoxSendFuture> + Send + Sync + 'static, | ||||
|     { | ||||
|         self.exec = Exec::Executor(Arc::new(exec)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/0.9 responses should be tolerated. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     pub fn http09_responses(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h09_responses = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections will accept spaces between header names | ||||
|     /// and the colon that follow them in responses. | ||||
|     /// | ||||
|     /// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has | ||||
|     /// to say about it: | ||||
|     /// | ||||
|     /// > No whitespace is allowed between the header field-name and colon. In | ||||
|     /// > the past, differences in the handling of such whitespace have led to | ||||
|     /// > security vulnerabilities in request routing and response handling. A | ||||
|     /// > server MUST reject any received request message that contains | ||||
|     /// > whitespace between a header field-name and colon with a response code | ||||
|     /// > of 400 (Bad Request). A proxy MUST remove any such whitespace from a | ||||
|     /// > response message before forwarding the message downstream. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     /// | ||||
|     /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4 | ||||
|     pub fn http1_allow_spaces_after_header_name_in_responses( | ||||
|         &mut self, | ||||
|         enabled: bool, | ||||
|     ) -> &mut Builder { | ||||
|         self.h1_parser_config | ||||
|             .allow_spaces_after_header_name_in_responses(enabled); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections will accept obsolete line folding for | ||||
|     /// header values. | ||||
|     /// | ||||
|     /// Newline codepoints (`\r` and `\n`) will be transformed to spaces when | ||||
|     /// parsing. | ||||
|     /// | ||||
|     /// You probably don't need this, here is what [RFC 7230 Section 3.2.4.] has | ||||
|     /// to say about it: | ||||
|     /// | ||||
|     /// > A server that receives an obs-fold in a request message that is not | ||||
|     /// > within a message/http container MUST either reject the message by | ||||
|     /// > sending a 400 (Bad Request), preferably with a representation | ||||
|     /// > explaining that obsolete line folding is unacceptable, or replace | ||||
|     /// > each received obs-fold with one or more SP octets prior to | ||||
|     /// > interpreting the field value or forwarding the message downstream. | ||||
|     /// | ||||
|     /// > A proxy or gateway that receives an obs-fold in a response message | ||||
|     /// > that is not within a message/http container MUST either discard the | ||||
|     /// > message and replace it with a 502 (Bad Gateway) response, preferably | ||||
|     /// > with a representation explaining that unacceptable line folding was | ||||
|     /// > received, or replace each received obs-fold with one or more SP | ||||
|     /// > octets prior to interpreting the field value or forwarding the | ||||
|     /// > message downstream. | ||||
|     /// | ||||
|     /// > A user agent that receives an obs-fold in a response message that is | ||||
|     /// > not within a message/http container MUST replace each received | ||||
|     /// > obs-fold with one or more SP octets prior to interpreting the field | ||||
|     /// > value. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     /// | ||||
|     /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4 | ||||
|     pub fn http1_allow_obsolete_multiline_headers_in_responses( | ||||
|         &mut self, | ||||
|         enabled: bool, | ||||
|     ) -> &mut Builder { | ||||
|         self.h1_parser_config | ||||
|             .allow_obsolete_multiline_headers_in_responses(enabled); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections should try to use vectored writes, | ||||
|     /// or always flatten into a single buffer. | ||||
|     /// | ||||
|     /// Note that setting this to false may mean more copies of body data, | ||||
|     /// but may also improve performance when an IO transport doesn't | ||||
|     /// support vectored writes well, such as most TLS implementations. | ||||
|     /// | ||||
|     /// Setting this to true will force hyper to use queued strategy | ||||
|     /// which may eliminate unnecessary cloning on some TLS backends | ||||
|     /// | ||||
|     /// Default is `auto`. In this mode hyper will try to guess which | ||||
|     /// mode to use | ||||
|     pub fn http1_writev(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h1_writev = Some(enabled); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections will write header names as title case at | ||||
|     /// the socket level. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     pub fn http1_title_case_headers(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h1_title_case_headers = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether to support preserving original header cases. | ||||
|     /// | ||||
|     /// Currently, this will record the original cases received, and store them | ||||
|     /// in a private extension on the `Response`. It will also look for and use | ||||
|     /// such an extension in any provided `Request`. | ||||
|     /// | ||||
|     /// Since the relevant extension is still private, there is no way to | ||||
|     /// interact with the original cases. The only effect this can have now is | ||||
|     /// to forward the cases in a proxy-like fashion. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     pub fn http1_preserve_header_case(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h1_preserve_header_case = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether to support preserving original header order. | ||||
|     /// | ||||
|     /// Currently, this will record the order in which headers are received, and store this | ||||
|     /// ordering in a private extension on the `Response`. It will also look for and use | ||||
|     /// such an extension in any provided `Request`. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     #[cfg(feature = "ffi")] | ||||
|     pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h1_preserve_header_order = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the exact size of the read buffer to *always* use. | ||||
|     /// | ||||
|     /// Note that setting this option unsets the `http1_max_buf_size` option. | ||||
|     /// | ||||
|     /// Default is an adaptive read buffer. | ||||
|     pub fn http1_read_buf_exact_size(&mut self, sz: Option<usize>) -> &mut Builder { | ||||
|         self.h1_read_buf_exact_size = sz; | ||||
|         self.h1_max_buf_size = None; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the maximum buffer size for the connection. | ||||
|     /// | ||||
|     /// Default is ~400kb. | ||||
|     /// | ||||
|     /// Note that setting this option unsets the `http1_read_exact_buf_size` option. | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// | ||||
|     /// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_max_buf_size(&mut self, max: usize) -> &mut Self { | ||||
|         assert!( | ||||
|             max >= proto::h1::MINIMUM_MAX_BUFFER_SIZE, | ||||
|             "the max_buf_size cannot be smaller than the minimum that h1 specifies." | ||||
|         ); | ||||
|  | ||||
|         self.h1_max_buf_size = Some(max); | ||||
|         self.h1_read_buf_exact_size = None; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Constructs a connection with the configured options and IO. | ||||
|     /// See [`client::conn`](crate::client::conn) for more. | ||||
|     /// | ||||
|     /// Note, if [`Connection`] is not `await`-ed, [`SendRequest`] will | ||||
|     /// do nothing. | ||||
|     pub fn handshake<T, B>( | ||||
|         &self, | ||||
|         io: T, | ||||
|     ) -> impl Future<Output = crate::Result<(SendRequest<B>, Connection<T, B>)>> | ||||
|     where | ||||
|         T: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|         B: HttpBody + 'static, | ||||
|         B::Data: Send, | ||||
|         B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     { | ||||
|         let opts = self.clone(); | ||||
|  | ||||
|         async move { | ||||
|             tracing::trace!("client handshake HTTP/1"); | ||||
|  | ||||
|             let (tx, rx) = dispatch::channel(); | ||||
|             let mut conn = proto::Conn::new(io); | ||||
|             conn.set_h1_parser_config(opts.h1_parser_config); | ||||
|             if let Some(writev) = opts.h1_writev { | ||||
|                 if writev { | ||||
|                     conn.set_write_strategy_queue(); | ||||
|                 } else { | ||||
|                     conn.set_write_strategy_flatten(); | ||||
|                 } | ||||
|             } | ||||
|             if opts.h1_title_case_headers { | ||||
|                 conn.set_title_case_headers(); | ||||
|             } | ||||
|             if opts.h1_preserve_header_case { | ||||
|                 conn.set_preserve_header_case(); | ||||
|             } | ||||
|             #[cfg(feature = "ffi")] | ||||
|             if opts.h1_preserve_header_order { | ||||
|                 conn.set_preserve_header_order(); | ||||
|             } | ||||
|             if opts.h09_responses { | ||||
|                 conn.set_h09_responses(); | ||||
|             } | ||||
|  | ||||
|             if let Some(sz) = opts.h1_read_buf_exact_size { | ||||
|                 conn.set_read_buf_exact_size(sz); | ||||
|             } | ||||
|             if let Some(max) = opts.h1_max_buf_size { | ||||
|                 conn.set_max_buf_size(max); | ||||
|             } | ||||
|             let cd = proto::h1::dispatch::Client::new(rx); | ||||
|             let proto = proto::h1::Dispatcher::new(cd, conn); | ||||
|  | ||||
|             Ok(( | ||||
|                 SendRequest { dispatch: tx }, | ||||
|                 Connection { inner: Some(proto) }, | ||||
|             )) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
							
								
								
									
										436
									
								
								src/client/conn/http2.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										436
									
								
								src/client/conn/http2.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,436 @@ | ||||
| //! HTTP/2 client connections | ||||
|  | ||||
| use std::error::Error as StdError; | ||||
| use std::fmt; | ||||
| use std::marker::PhantomData; | ||||
| use std::sync::Arc; | ||||
| #[cfg(feature = "runtime")] | ||||
| use std::time::Duration; | ||||
|  | ||||
| use http::{Request, Response}; | ||||
| use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| use crate::Body; | ||||
| use crate::body::HttpBody; | ||||
| use crate::common::{ | ||||
|     exec::{BoxSendFuture, Exec}, | ||||
|     task, Future, Pin, Poll, | ||||
| }; | ||||
| use crate::proto; | ||||
| use crate::rt::Executor; | ||||
| use super::super::dispatch; | ||||
|  | ||||
| /// The sender side of an established connection. | ||||
| pub struct SendRequest<B> { | ||||
|     dispatch: dispatch::UnboundedSender<Request<B>, Response<Body>>, | ||||
| } | ||||
|  | ||||
| /// A future that processes all HTTP state for the IO object. | ||||
| /// | ||||
| /// In most cases, this should just be spawned into an executor, so that it | ||||
| /// can process incoming and outgoing messages, notice hangups, and the like. | ||||
| #[must_use = "futures do nothing unless polled"] | ||||
| pub struct Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + Send + 'static, | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     inner: (PhantomData<T>, proto::h2::ClientTask<B>), | ||||
| } | ||||
|  | ||||
| /// A builder to configure an HTTP connection. | ||||
| /// | ||||
| /// After setting options, the builder is used to create a handshake future. | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct Builder { | ||||
|     pub(super) exec: Exec, | ||||
|     h2_builder: proto::h2::client::Config, | ||||
| } | ||||
|  | ||||
| /// Returns a handshake future over some IO. | ||||
| /// | ||||
| /// This is a shortcut for `Builder::new().handshake(io)`. | ||||
| /// See [`client::conn`](crate::client::conn) for more. | ||||
| pub async fn handshake<T>( | ||||
|     io: T, | ||||
| ) -> crate::Result<(SendRequest<crate::Body>, Connection<T, crate::Body>)> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
| { | ||||
|     Builder::new().handshake(io).await | ||||
| } | ||||
|  | ||||
| // ===== impl SendRequest | ||||
|  | ||||
| impl<B> SendRequest<B> { | ||||
|     /// Polls to determine whether this sender can be used yet for a request. | ||||
|     /// | ||||
|     /// If the associated connection is closed, this returns an Error. | ||||
|     pub fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> { | ||||
|         if self.is_closed() { | ||||
|             Poll::Ready(Err(crate::Error::new_closed())) | ||||
|         } else { | ||||
|             Poll::Ready(Ok(())) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     pub(super) async fn when_ready(self) -> crate::Result<Self> { | ||||
|         let mut me = Some(self); | ||||
|         future::poll_fn(move |cx| { | ||||
|             ready!(me.as_mut().unwrap().poll_ready(cx))?; | ||||
|             Poll::Ready(Ok(me.take().unwrap())) | ||||
|         }) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     pub(super) fn is_ready(&self) -> bool { | ||||
|         self.dispatch.is_ready() | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     pub(super) fn is_closed(&self) -> bool { | ||||
|         self.dispatch.is_closed() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<B> SendRequest<B> | ||||
| where | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     /// Sends a `Request` on the associated connection. | ||||
|     /// | ||||
|     /// Returns a future that if successful, yields the `Response`. | ||||
|     /// | ||||
|     /// # Note | ||||
|     /// | ||||
|     /// There are some key differences in what automatic things the `Client` | ||||
|     /// does for you that will not be done here: | ||||
|     /// | ||||
|     /// - `Client` requires absolute-form `Uri`s, since the scheme and | ||||
|     ///   authority are needed to connect. They aren't required here. | ||||
|     /// - Since the `Client` requires absolute-form `Uri`s, it can add | ||||
|     ///   the `Host` header based on it. You must add a `Host` header yourself | ||||
|     ///   before calling this method. | ||||
|     /// - Since absolute-form `Uri`s are not required, if received, they will | ||||
|     ///   be serialized as-is. | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # use http::header::HOST; | ||||
|     /// # use hyper::client::conn::SendRequest; | ||||
|     /// # use hyper::Body; | ||||
|     /// use hyper::Request; | ||||
|     /// | ||||
|     /// # async fn doc(mut tx: SendRequest<Body>) -> hyper::Result<()> { | ||||
|     /// // build a Request | ||||
|     /// let req = Request::builder() | ||||
|     ///     .uri("/foo/bar") | ||||
|     ///     .header(HOST, "hyper.rs") | ||||
|     ///     .body(Body::empty()) | ||||
|     ///     .unwrap(); | ||||
|     /// | ||||
|     /// // send it and await a Response | ||||
|     /// let res = tx.send_request(req).await?; | ||||
|     /// // assert the Response | ||||
|     /// assert!(res.status().is_success()); | ||||
|     /// # Ok(()) | ||||
|     /// # } | ||||
|     /// # fn main() {} | ||||
|     /// ``` | ||||
|     pub fn send_request(&mut self, req: Request<B>) -> impl Future<Output = crate::Result<Response<Body>>> { | ||||
|         let sent = self.dispatch.send(req); | ||||
|  | ||||
|         async move { | ||||
|             match sent { | ||||
|                 Ok(rx) => match rx.await { | ||||
|                     Ok(Ok(resp)) => Ok(resp), | ||||
|                     Ok(Err(err)) => Err(err), | ||||
|                     // this is definite bug if it happens, but it shouldn't happen! | ||||
|                     Err(_canceled) => panic!("dispatch dropped without returning error"), | ||||
|                 } | ||||
|                 Err(_req) => { | ||||
|                     tracing::debug!("connection was not ready"); | ||||
|  | ||||
|                     Err(crate::Error::new_canceled().with("connection was not ready")) | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     pub(super) fn send_request_retryable( | ||||
|         &mut self, | ||||
|         req: Request<B>, | ||||
|     ) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> + Unpin | ||||
|     where | ||||
|         B: Send, | ||||
|     { | ||||
|         match self.dispatch.try_send(req) { | ||||
|             Ok(rx) => { | ||||
|                 Either::Left(rx.then(move |res| { | ||||
|                     match res { | ||||
|                         Ok(Ok(res)) => future::ok(res), | ||||
|                         Ok(Err(err)) => future::err(err), | ||||
|                         // this is definite bug if it happens, but it shouldn't happen! | ||||
|                         Err(_) => panic!("dispatch dropped without returning error"), | ||||
|                     } | ||||
|                 })) | ||||
|             } | ||||
|             Err(req) => { | ||||
|                 tracing::debug!("connection was not ready"); | ||||
|                 let err = crate::Error::new_canceled().with("connection was not ready"); | ||||
|                 Either::Right(future::err((err, Some(req)))) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|     */ | ||||
| } | ||||
|  | ||||
| impl<B> fmt::Debug for SendRequest<B> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("SendRequest").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Connection | ||||
|  | ||||
| impl<T, B> fmt::Debug for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + fmt::Debug + Send + 'static, | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("Connection").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, B> Future for Connection<T, B> | ||||
| where | ||||
|     T: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     B: HttpBody + Send + 'static, | ||||
|     B::Data: Send, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
| { | ||||
|     type Output = crate::Result<()>; | ||||
|  | ||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         match ready!(Pin::new(&mut self.inner.1).poll(cx))? { | ||||
|             proto::Dispatched::Shutdown => Poll::Ready(Ok(())), | ||||
|             #[cfg(feature = "http1")] | ||||
|             proto::Dispatched::Upgrade(_pending) => unreachable!("http2 cannot upgrade"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Builder | ||||
|  | ||||
| impl Builder { | ||||
|     /// Creates a new connection builder. | ||||
|     #[inline] | ||||
|     pub fn new() -> Builder { | ||||
|         Builder { | ||||
|             exec: Exec::Default, | ||||
|             h2_builder: Default::default(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Provide an executor to execute background HTTP2 tasks. | ||||
|     pub fn executor<E>(&mut self, exec: E) -> &mut Builder | ||||
|     where | ||||
|         E: Executor<BoxSendFuture> + Send + Sync + 'static, | ||||
|     { | ||||
|         self.exec = Exec::Executor(Arc::new(exec)); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 | ||||
|     /// stream-level flow control. | ||||
|     /// | ||||
|     /// Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// If not set, hyper will use a default. | ||||
|     /// | ||||
|     /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_initial_stream_window_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self { | ||||
|         if let Some(sz) = sz.into() { | ||||
|             self.h2_builder.adaptive_window = false; | ||||
|             self.h2_builder.initial_stream_window_size = sz; | ||||
|         } | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the max connection-level flow control for HTTP2 | ||||
|     /// | ||||
|     /// Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// If not set, hyper will use a default. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_initial_connection_window_size( | ||||
|         &mut self, | ||||
|         sz: impl Into<Option<u32>>, | ||||
|     ) -> &mut Self { | ||||
|         if let Some(sz) = sz.into() { | ||||
|             self.h2_builder.adaptive_window = false; | ||||
|             self.h2_builder.initial_conn_window_size = sz; | ||||
|         } | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets whether to use an adaptive flow control. | ||||
|     /// | ||||
|     /// Enabling this will override the limits set in | ||||
|     /// `http2_initial_stream_window_size` and | ||||
|     /// `http2_initial_connection_window_size`. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_adaptive_window(&mut self, enabled: bool) -> &mut Self { | ||||
|         use proto::h2::SPEC_WINDOW_SIZE; | ||||
|  | ||||
|         self.h2_builder.adaptive_window = enabled; | ||||
|         if enabled { | ||||
|             self.h2_builder.initial_conn_window_size = SPEC_WINDOW_SIZE; | ||||
|             self.h2_builder.initial_stream_window_size = SPEC_WINDOW_SIZE; | ||||
|         } | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the maximum frame size to use for HTTP2. | ||||
|     /// | ||||
|     /// Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// If not set, hyper will use a default. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_frame_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self { | ||||
|         if let Some(sz) = sz.into() { | ||||
|             self.h2_builder.max_frame_size = sz; | ||||
|         } | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets an interval for HTTP2 Ping frames should be sent to keep a | ||||
|     /// connection alive. | ||||
|     /// | ||||
|     /// Pass `None` to disable HTTP2 keep-alive. | ||||
|     /// | ||||
|     /// Default is currently disabled. | ||||
|     /// | ||||
|     /// # Cargo Feature | ||||
|     /// | ||||
|     /// Requires the `runtime` cargo feature to be enabled. | ||||
|     #[cfg(feature = "runtime")] | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_keep_alive_interval( | ||||
|         &mut self, | ||||
|         interval: impl Into<Option<Duration>>, | ||||
|     ) -> &mut Self { | ||||
|         self.h2_builder.keep_alive_interval = interval.into(); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. | ||||
|     /// | ||||
|     /// If the ping is not acknowledged within the timeout, the connection will | ||||
|     /// be closed. Does nothing if `http2_keep_alive_interval` is disabled. | ||||
|     /// | ||||
|     /// Default is 20 seconds. | ||||
|     /// | ||||
|     /// # Cargo Feature | ||||
|     /// | ||||
|     /// Requires the `runtime` cargo feature to be enabled. | ||||
|     #[cfg(feature = "runtime")] | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self { | ||||
|         self.h2_builder.keep_alive_timeout = timeout; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets whether HTTP2 keep-alive should apply while the connection is idle. | ||||
|     /// | ||||
|     /// If disabled, keep-alive pings are only sent while there are open | ||||
|     /// request/responses streams. If enabled, pings are also sent when no | ||||
|     /// streams are active. Does nothing if `http2_keep_alive_interval` is | ||||
|     /// disabled. | ||||
|     /// | ||||
|     /// Default is `false`. | ||||
|     /// | ||||
|     /// # Cargo Feature | ||||
|     /// | ||||
|     /// Requires the `runtime` cargo feature to be enabled. | ||||
|     #[cfg(feature = "runtime")] | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_keep_alive_while_idle(&mut self, enabled: bool) -> &mut Self { | ||||
|         self.h2_builder.keep_alive_while_idle = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the maximum number of HTTP2 concurrent locally reset streams. | ||||
|     /// | ||||
|     /// See the documentation of [`h2::client::Builder::max_concurrent_reset_streams`] for more | ||||
|     /// details. | ||||
|     /// | ||||
|     /// The default value is determined by the `h2` crate. | ||||
|     /// | ||||
|     /// [`h2::client::Builder::max_concurrent_reset_streams`]: https://docs.rs/h2/client/struct.Builder.html#method.max_concurrent_reset_streams | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_concurrent_reset_streams(&mut self, max: usize) -> &mut Self { | ||||
|         self.h2_builder.max_concurrent_reset_streams = Some(max); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the maximum write buffer size for each HTTP/2 stream. | ||||
|     /// | ||||
|     /// Default is currently 1MB, but may change. | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// | ||||
|     /// The value must be no larger than `u32::MAX`. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_send_buf_size(&mut self, max: usize) -> &mut Self { | ||||
|         assert!(max <= std::u32::MAX as usize); | ||||
|         self.h2_builder.max_send_buffer_size = max; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Constructs a connection with the configured options and IO. | ||||
|     /// See [`client::conn`](crate::client::conn) for more. | ||||
|     /// | ||||
|     /// Note, if [`Connection`] is not `await`-ed, [`SendRequest`] will | ||||
|     /// do nothing. | ||||
|     pub fn handshake<T, B>( | ||||
|         &self, | ||||
|         io: T, | ||||
|     ) -> impl Future<Output = crate::Result<(SendRequest<B>, Connection<T, B>)>> | ||||
|     where | ||||
|         T: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|         B: HttpBody + 'static, | ||||
|         B::Data: Send, | ||||
|         B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     { | ||||
|         let opts = self.clone(); | ||||
|  | ||||
|         async move { | ||||
|             tracing::trace!("client handshake HTTP/1"); | ||||
|  | ||||
|             let (tx, rx) = dispatch::channel(); | ||||
|             let h2 = | ||||
|                 proto::h2::client::handshake(io, rx, &opts.h2_builder, opts.exec) | ||||
|                     .await?; | ||||
|             Ok(( | ||||
|                 SendRequest { dispatch: tx.unbound() }, | ||||
|                 Connection { inner: (PhantomData, h2) }, | ||||
|             )) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| @@ -63,7 +63,7 @@ use std::sync::Arc; | ||||
| use std::time::Duration; | ||||
| 
 | ||||
| use bytes::Bytes; | ||||
| use futures_util::future::{self, Either, FutureExt as _}; | ||||
| use futures_util::future; | ||||
| use httparse::ParserConfig; | ||||
| use pin_project_lite::pin_project; | ||||
| use tokio::io::{AsyncRead, AsyncWrite}; | ||||
| @@ -84,6 +84,11 @@ use crate::rt::Executor; | ||||
| use crate::upgrade::Upgraded; | ||||
| use crate::{Body, Request, Response}; | ||||
| 
 | ||||
| #[cfg(feature = "http1")] | ||||
| pub mod http1; | ||||
| #[cfg(feature = "http2")] | ||||
| pub mod http2; | ||||
| 
 | ||||
| #[cfg(feature = "http1")] | ||||
| type Http1Dispatcher<T, B> = | ||||
|     proto::dispatch::Dispatcher<proto::dispatch::Client<B>, B, T, proto::h1::ClientTransaction>; | ||||
| @@ -156,6 +161,8 @@ pub struct Builder { | ||||
|     h1_writev: Option<bool>, | ||||
|     h1_title_case_headers: bool, | ||||
|     h1_preserve_header_case: bool, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     h1_preserve_header_order: bool, | ||||
|     h1_read_buf_exact_size: Option<usize>, | ||||
|     h1_max_buf_size: Option<usize>, | ||||
|     #[cfg(feature = "ffi")] | ||||
| @@ -207,16 +214,6 @@ pub struct Parts<T> { | ||||
|     _inner: (), | ||||
| } | ||||
| 
 | ||||
| // ========== internal client api
 | ||||
| 
 | ||||
| // A `SendRequest` that can be cloned to send HTTP2 requests.
 | ||||
| // private for now, probably not a great idea of a type...
 | ||||
| #[must_use = "futures do nothing unless polled"] | ||||
| #[cfg(feature = "http2")] | ||||
| pub(super) struct Http2SendRequest<B> { | ||||
|     dispatch: dispatch::UnboundedSender<Request<B>, Response<Body>>, | ||||
| } | ||||
| 
 | ||||
| // ===== impl SendRequest
 | ||||
| 
 | ||||
| impl<B> SendRequest<B> { | ||||
| @@ -226,30 +223,6 @@ impl<B> SendRequest<B> { | ||||
|     pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> { | ||||
|         self.dispatch.poll_ready(cx) | ||||
|     } | ||||
| 
 | ||||
|     pub(super) async fn when_ready(self) -> crate::Result<Self> { | ||||
|         let mut me = Some(self); | ||||
|         future::poll_fn(move |cx| { | ||||
|             ready!(me.as_mut().unwrap().poll_ready(cx))?; | ||||
|             Poll::Ready(Ok(me.take().unwrap())) | ||||
|         }) | ||||
|         .await | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_ready(&self) -> bool { | ||||
|         self.dispatch.is_ready() | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_closed(&self) -> bool { | ||||
|         self.dispatch.is_closed() | ||||
|     } | ||||
| 
 | ||||
|     #[cfg(feature = "http2")] | ||||
|     pub(super) fn into_http2(self) -> Http2SendRequest<B> { | ||||
|         Http2SendRequest { | ||||
|             dispatch: self.dispatch.unbound(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<B> SendRequest<B> | ||||
| @@ -309,32 +282,6 @@ where | ||||
| 
 | ||||
|         ResponseFuture { inner } | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn send_request_retryable( | ||||
|         &mut self, | ||||
|         req: Request<B>, | ||||
|     ) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> + Unpin | ||||
|     where | ||||
|         B: Send, | ||||
|     { | ||||
|         match self.dispatch.try_send(req) { | ||||
|             Ok(rx) => { | ||||
|                 Either::Left(rx.then(move |res| { | ||||
|                     match res { | ||||
|                         Ok(Ok(res)) => future::ok(res), | ||||
|                         Ok(Err(err)) => future::err(err), | ||||
|                         // this is definite bug if it happens, but it shouldn't happen!
 | ||||
|                         Err(_) => panic!("dispatch dropped without returning error"), | ||||
|                     } | ||||
|                 })) | ||||
|             } | ||||
|             Err(req) => { | ||||
|                 debug!("connection was not ready"); | ||||
|                 let err = crate::Error::new_canceled().with("connection was not ready"); | ||||
|                 Either::Right(future::err((err, Some(req)))) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| impl<B> Service<Request<B>> for SendRequest<B> | ||||
| @@ -360,67 +307,6 @@ impl<B> fmt::Debug for SendRequest<B> { | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ===== impl Http2SendRequest
 | ||||
| 
 | ||||
| #[cfg(feature = "http2")] | ||||
| impl<B> Http2SendRequest<B> { | ||||
|     pub(super) fn is_ready(&self) -> bool { | ||||
|         self.dispatch.is_ready() | ||||
|     } | ||||
| 
 | ||||
|     pub(super) fn is_closed(&self) -> bool { | ||||
|         self.dispatch.is_closed() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "http2")] | ||||
| impl<B> Http2SendRequest<B> | ||||
| where | ||||
|     B: HttpBody + 'static, | ||||
| { | ||||
|     pub(super) fn send_request_retryable( | ||||
|         &mut self, | ||||
|         req: Request<B>, | ||||
|     ) -> impl Future<Output = Result<Response<Body>, (crate::Error, Option<Request<B>>)>> | ||||
|     where | ||||
|         B: Send, | ||||
|     { | ||||
|         match self.dispatch.try_send(req) { | ||||
|             Ok(rx) => { | ||||
|                 Either::Left(rx.then(move |res| { | ||||
|                     match res { | ||||
|                         Ok(Ok(res)) => future::ok(res), | ||||
|                         Ok(Err(err)) => future::err(err), | ||||
|                         // this is definite bug if it happens, but it shouldn't happen!
 | ||||
|                         Err(_) => panic!("dispatch dropped without returning error"), | ||||
|                     } | ||||
|                 })) | ||||
|             } | ||||
|             Err(req) => { | ||||
|                 debug!("connection was not ready"); | ||||
|                 let err = crate::Error::new_canceled().with("connection was not ready"); | ||||
|                 Either::Right(future::err((err, Some(req)))) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "http2")] | ||||
| impl<B> fmt::Debug for Http2SendRequest<B> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("Http2SendRequest").finish() | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| #[cfg(feature = "http2")] | ||||
| impl<B> Clone for Http2SendRequest<B> { | ||||
|     fn clone(&self) -> Self { | ||||
|         Http2SendRequest { | ||||
|             dispatch: self.dispatch.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| // ===== impl Connection
 | ||||
| 
 | ||||
| impl<T, B> Connection<T, B> | ||||
| @@ -492,7 +378,7 @@ where | ||||
|     ///
 | ||||
|     /// This setting is configured by the server peer by sending the
 | ||||
|     /// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
 | ||||
|     /// This method returns the currently acknowledged value recieved from the
 | ||||
|     /// This method returns the currently acknowledged value received from the
 | ||||
|     /// remote.
 | ||||
|     ///
 | ||||
|     /// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
 | ||||
| @@ -558,6 +444,8 @@ impl Builder { | ||||
|             h1_parser_config: Default::default(), | ||||
|             h1_title_case_headers: false, | ||||
|             h1_preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             h1_preserve_header_order: false, | ||||
|             h1_max_buf_size: None, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             h1_headers_raw: false, | ||||
| @@ -704,6 +592,21 @@ impl Builder { | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     /// Set whether to support preserving original header order.
 | ||||
|     ///
 | ||||
|     /// Currently, this will record the order in which headers are received, and store this
 | ||||
|     /// ordering in a private extension on the `Response`. It will also look for and use
 | ||||
|     /// such an extension in any provided `Request`.
 | ||||
|     ///
 | ||||
|     /// Note that this setting does not affect HTTP/2.
 | ||||
|     ///
 | ||||
|     /// Default is false.
 | ||||
|     #[cfg(feature = "ffi")] | ||||
|     pub fn http1_preserve_header_order(&mut self, enabled: bool) -> &mut Builder { | ||||
|         self.h1_preserve_header_order = enabled; | ||||
|         self | ||||
|     } | ||||
| 
 | ||||
|     /// Sets the exact size of the read buffer to *always* use.
 | ||||
|     ///
 | ||||
|     /// Note that setting this option unsets the `http1_max_buf_size` option.
 | ||||
| @@ -951,6 +854,10 @@ impl Builder { | ||||
|                     if opts.h1_preserve_header_case { | ||||
|                         conn.set_preserve_header_case(); | ||||
|                     } | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     if opts.h1_preserve_header_order { | ||||
|                         conn.set_preserve_header_order(); | ||||
|                     } | ||||
|                     if opts.h09_responses { | ||||
|                         conn.set_h09_responses(); | ||||
|                     } | ||||
| @@ -1,425 +0,0 @@ | ||||
| //! DNS Resolution used by the `HttpConnector`. | ||||
| //! | ||||
| //! This module contains: | ||||
| //! | ||||
| //! - A [`GaiResolver`](GaiResolver) that is the default resolver for the | ||||
| //!   `HttpConnector`. | ||||
| //! - The `Name` type used as an argument to custom resolvers. | ||||
| //! | ||||
| //! # Resolvers are `Service`s | ||||
| //! | ||||
| //! A resolver is just a | ||||
| //! `Service<Name, Response = impl Iterator<Item = SocketAddr>>`. | ||||
| //! | ||||
| //! A simple resolver that ignores the name and always returns a specific | ||||
| //! address: | ||||
| //! | ||||
| //! ```rust,ignore | ||||
| //! use std::{convert::Infallible, iter, net::SocketAddr}; | ||||
| //! | ||||
| //! let resolver = tower::service_fn(|_name| async { | ||||
| //!     Ok::<_, Infallible>(iter::once(SocketAddr::from(([127, 0, 0, 1], 8080)))) | ||||
| //! }); | ||||
| //! ``` | ||||
| use std::error::Error; | ||||
| use std::future::Future; | ||||
| use std::net::{Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs}; | ||||
| use std::pin::Pin; | ||||
| use std::str::FromStr; | ||||
| use std::task::{self, Poll}; | ||||
| use std::{fmt, io, vec}; | ||||
|  | ||||
| use tokio::task::JoinHandle; | ||||
| use tower_service::Service; | ||||
| use tracing::debug; | ||||
|  | ||||
| pub(super) use self::sealed::Resolve; | ||||
|  | ||||
| /// A domain name to resolve into IP addresses. | ||||
| #[derive(Clone, Hash, Eq, PartialEq)] | ||||
| pub struct Name { | ||||
|     host: Box<str>, | ||||
| } | ||||
|  | ||||
| /// A resolver using blocking `getaddrinfo` calls in a threadpool. | ||||
| #[derive(Clone)] | ||||
| pub struct GaiResolver { | ||||
|     _priv: (), | ||||
| } | ||||
|  | ||||
| /// An iterator of IP addresses returned from `getaddrinfo`. | ||||
| pub struct GaiAddrs { | ||||
|     inner: SocketAddrs, | ||||
| } | ||||
|  | ||||
| /// A future to resolve a name returned by `GaiResolver`. | ||||
| pub struct GaiFuture { | ||||
|     inner: JoinHandle<Result<SocketAddrs, io::Error>>, | ||||
| } | ||||
|  | ||||
| impl Name { | ||||
|     pub(super) fn new(host: Box<str>) -> Name { | ||||
|         Name { host } | ||||
|     } | ||||
|  | ||||
|     /// View the hostname as a string slice. | ||||
|     pub fn as_str(&self) -> &str { | ||||
|         &self.host | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for Name { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         fmt::Debug::fmt(&self.host, f) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Display for Name { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         fmt::Display::fmt(&self.host, f) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl FromStr for Name { | ||||
|     type Err = InvalidNameError; | ||||
|  | ||||
|     fn from_str(host: &str) -> Result<Self, Self::Err> { | ||||
|         // Possibly add validation later | ||||
|         Ok(Name::new(host.into())) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Error indicating a given string was not a valid domain name. | ||||
| #[derive(Debug)] | ||||
| pub struct InvalidNameError(()); | ||||
|  | ||||
| impl fmt::Display for InvalidNameError { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.write_str("Not a valid domain name") | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Error for InvalidNameError {} | ||||
|  | ||||
| impl GaiResolver { | ||||
|     /// Construct a new `GaiResolver`. | ||||
|     pub fn new() -> Self { | ||||
|         GaiResolver { _priv: () } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Service<Name> for GaiResolver { | ||||
|     type Response = GaiAddrs; | ||||
|     type Error = io::Error; | ||||
|     type Future = GaiFuture; | ||||
|  | ||||
|     fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, name: Name) -> Self::Future { | ||||
|         let blocking = tokio::task::spawn_blocking(move || { | ||||
|             debug!("resolving host={:?}", name.host); | ||||
|             (&*name.host, 0) | ||||
|                 .to_socket_addrs() | ||||
|                 .map(|i| SocketAddrs { iter: i }) | ||||
|         }); | ||||
|  | ||||
|         GaiFuture { inner: blocking } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for GaiResolver { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.pad("GaiResolver") | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Future for GaiFuture { | ||||
|     type Output = Result<GaiAddrs, io::Error>; | ||||
|  | ||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         Pin::new(&mut self.inner).poll(cx).map(|res| match res { | ||||
|             Ok(Ok(addrs)) => Ok(GaiAddrs { inner: addrs }), | ||||
|             Ok(Err(err)) => Err(err), | ||||
|             Err(join_err) => { | ||||
|                 if join_err.is_cancelled() { | ||||
|                     Err(io::Error::new(io::ErrorKind::Interrupted, join_err)) | ||||
|                 } else { | ||||
|                     panic!("gai background task failed: {:?}", join_err) | ||||
|                 } | ||||
|             } | ||||
|         }) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for GaiFuture { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.pad("GaiFuture") | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Drop for GaiFuture { | ||||
|     fn drop(&mut self) { | ||||
|         self.inner.abort(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Iterator for GaiAddrs { | ||||
|     type Item = SocketAddr; | ||||
|  | ||||
|     fn next(&mut self) -> Option<Self::Item> { | ||||
|         self.inner.next() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for GaiAddrs { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.pad("GaiAddrs") | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) struct SocketAddrs { | ||||
|     iter: vec::IntoIter<SocketAddr>, | ||||
| } | ||||
|  | ||||
| impl SocketAddrs { | ||||
|     pub(super) fn new(addrs: Vec<SocketAddr>) -> Self { | ||||
|         SocketAddrs { | ||||
|             iter: addrs.into_iter(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(super) fn try_parse(host: &str, port: u16) -> Option<SocketAddrs> { | ||||
|         if let Ok(addr) = host.parse::<Ipv4Addr>() { | ||||
|             let addr = SocketAddrV4::new(addr, port); | ||||
|             return Some(SocketAddrs { | ||||
|                 iter: vec![SocketAddr::V4(addr)].into_iter(), | ||||
|             }); | ||||
|         } | ||||
|         if let Ok(addr) = host.parse::<Ipv6Addr>() { | ||||
|             let addr = SocketAddrV6::new(addr, port, 0, 0); | ||||
|             return Some(SocketAddrs { | ||||
|                 iter: vec![SocketAddr::V6(addr)].into_iter(), | ||||
|             }); | ||||
|         } | ||||
|         None | ||||
|     } | ||||
|  | ||||
|     #[inline] | ||||
|     fn filter(self, predicate: impl FnMut(&SocketAddr) -> bool) -> SocketAddrs { | ||||
|         SocketAddrs::new(self.iter.filter(predicate).collect()) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn split_by_preference( | ||||
|         self, | ||||
|         local_addr_ipv4: Option<Ipv4Addr>, | ||||
|         local_addr_ipv6: Option<Ipv6Addr>, | ||||
|     ) -> (SocketAddrs, SocketAddrs) { | ||||
|         match (local_addr_ipv4, local_addr_ipv6) { | ||||
|             (Some(_), None) => (self.filter(SocketAddr::is_ipv4), SocketAddrs::new(vec![])), | ||||
|             (None, Some(_)) => (self.filter(SocketAddr::is_ipv6), SocketAddrs::new(vec![])), | ||||
|             _ => { | ||||
|                 let preferring_v6 = self | ||||
|                     .iter | ||||
|                     .as_slice() | ||||
|                     .first() | ||||
|                     .map(SocketAddr::is_ipv6) | ||||
|                     .unwrap_or(false); | ||||
|  | ||||
|                 let (preferred, fallback) = self | ||||
|                     .iter | ||||
|                     .partition::<Vec<_>, _>(|addr| addr.is_ipv6() == preferring_v6); | ||||
|  | ||||
|                 (SocketAddrs::new(preferred), SocketAddrs::new(fallback)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(super) fn is_empty(&self) -> bool { | ||||
|         self.iter.as_slice().is_empty() | ||||
|     } | ||||
|  | ||||
|     pub(super) fn len(&self) -> usize { | ||||
|         self.iter.as_slice().len() | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Iterator for SocketAddrs { | ||||
|     type Item = SocketAddr; | ||||
|     #[inline] | ||||
|     fn next(&mut self) -> Option<SocketAddr> { | ||||
|         self.iter.next() | ||||
|     } | ||||
| } | ||||
|  | ||||
| /* | ||||
| /// A resolver using `getaddrinfo` calls via the `tokio_executor::threadpool::blocking` API. | ||||
| /// | ||||
| /// Unlike the `GaiResolver` this will not spawn dedicated threads, but only works when running on the | ||||
| /// multi-threaded Tokio runtime. | ||||
| #[cfg(feature = "runtime")] | ||||
| #[derive(Clone, Debug)] | ||||
| pub struct TokioThreadpoolGaiResolver(()); | ||||
|  | ||||
| /// The future returned by `TokioThreadpoolGaiResolver`. | ||||
| #[cfg(feature = "runtime")] | ||||
| #[derive(Debug)] | ||||
| pub struct TokioThreadpoolGaiFuture { | ||||
|     name: Name, | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "runtime")] | ||||
| impl TokioThreadpoolGaiResolver { | ||||
|     /// Creates a new DNS resolver that will use tokio threadpool's blocking | ||||
|     /// feature. | ||||
|     /// | ||||
|     /// **Requires** its futures to be run on the threadpool runtime. | ||||
|     pub fn new() -> Self { | ||||
|         TokioThreadpoolGaiResolver(()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "runtime")] | ||||
| impl Service<Name> for TokioThreadpoolGaiResolver { | ||||
|     type Response = GaiAddrs; | ||||
|     type Error = io::Error; | ||||
|     type Future = TokioThreadpoolGaiFuture; | ||||
|  | ||||
|     fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), io::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, name: Name) -> Self::Future { | ||||
|         TokioThreadpoolGaiFuture { name } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "runtime")] | ||||
| impl Future for TokioThreadpoolGaiFuture { | ||||
|     type Output = Result<GaiAddrs, io::Error>; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         match ready!(tokio_executor::threadpool::blocking(|| ( | ||||
|             self.name.as_str(), | ||||
|             0 | ||||
|         ) | ||||
|             .to_socket_addrs())) | ||||
|         { | ||||
|             Ok(Ok(iter)) => Poll::Ready(Ok(GaiAddrs { | ||||
|                 inner: IpAddrs { iter }, | ||||
|             })), | ||||
|             Ok(Err(e)) => Poll::Ready(Err(e)), | ||||
|             // a BlockingError, meaning not on a tokio_executor::threadpool :( | ||||
|             Err(e) => Poll::Ready(Err(io::Error::new(io::ErrorKind::Other, e))), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| */ | ||||
|  | ||||
| mod sealed { | ||||
|     use super::{SocketAddr, Name}; | ||||
|     use crate::common::{task, Future, Poll}; | ||||
|     use tower_service::Service; | ||||
|  | ||||
|     // "Trait alias" for `Service<Name, Response = Addrs>` | ||||
|     pub trait Resolve { | ||||
|         type Addrs: Iterator<Item = SocketAddr>; | ||||
|         type Error: Into<Box<dyn std::error::Error + Send + Sync>>; | ||||
|         type Future: Future<Output = Result<Self::Addrs, Self::Error>>; | ||||
|  | ||||
|         fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>; | ||||
|         fn resolve(&mut self, name: Name) -> Self::Future; | ||||
|     } | ||||
|  | ||||
|     impl<S> Resolve for S | ||||
|     where | ||||
|         S: Service<Name>, | ||||
|         S::Response: Iterator<Item = SocketAddr>, | ||||
|         S::Error: Into<Box<dyn std::error::Error + Send + Sync>>, | ||||
|     { | ||||
|         type Addrs = S::Response; | ||||
|         type Error = S::Error; | ||||
|         type Future = S::Future; | ||||
|  | ||||
|         fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||
|             Service::poll_ready(self, cx) | ||||
|         } | ||||
|  | ||||
|         fn resolve(&mut self, name: Name) -> Self::Future { | ||||
|             Service::call(self, name) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| pub(super) async fn resolve<R>(resolver: &mut R, name: Name) -> Result<R::Addrs, R::Error> | ||||
| where | ||||
|     R: Resolve, | ||||
| { | ||||
|     futures_util::future::poll_fn(|cx| resolver.poll_ready(cx)).await?; | ||||
|     resolver.resolve(name).await | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|     use std::net::{Ipv4Addr, Ipv6Addr}; | ||||
|  | ||||
|     #[test] | ||||
|     fn test_ip_addrs_split_by_preference() { | ||||
|         let ip_v4 = Ipv4Addr::new(127, 0, 0, 1); | ||||
|         let ip_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1); | ||||
|         let v4_addr = (ip_v4, 80).into(); | ||||
|         let v6_addr = (ip_v6, 80).into(); | ||||
|  | ||||
|         let (mut preferred, mut fallback) = SocketAddrs { | ||||
|             iter: vec![v4_addr, v6_addr].into_iter(), | ||||
|         } | ||||
|         .split_by_preference(None, None); | ||||
|         assert!(preferred.next().unwrap().is_ipv4()); | ||||
|         assert!(fallback.next().unwrap().is_ipv6()); | ||||
|  | ||||
|         let (mut preferred, mut fallback) = SocketAddrs { | ||||
|             iter: vec![v6_addr, v4_addr].into_iter(), | ||||
|         } | ||||
|         .split_by_preference(None, None); | ||||
|         assert!(preferred.next().unwrap().is_ipv6()); | ||||
|         assert!(fallback.next().unwrap().is_ipv4()); | ||||
|  | ||||
|         let (mut preferred, mut fallback) = SocketAddrs { | ||||
|             iter: vec![v4_addr, v6_addr].into_iter(), | ||||
|         } | ||||
|         .split_by_preference(Some(ip_v4), Some(ip_v6)); | ||||
|         assert!(preferred.next().unwrap().is_ipv4()); | ||||
|         assert!(fallback.next().unwrap().is_ipv6()); | ||||
|  | ||||
|         let (mut preferred, mut fallback) = SocketAddrs { | ||||
|             iter: vec![v6_addr, v4_addr].into_iter(), | ||||
|         } | ||||
|         .split_by_preference(Some(ip_v4), Some(ip_v6)); | ||||
|         assert!(preferred.next().unwrap().is_ipv6()); | ||||
|         assert!(fallback.next().unwrap().is_ipv4()); | ||||
|  | ||||
|         let (mut preferred, fallback) = SocketAddrs { | ||||
|             iter: vec![v4_addr, v6_addr].into_iter(), | ||||
|         } | ||||
|         .split_by_preference(Some(ip_v4), None); | ||||
|         assert!(preferred.next().unwrap().is_ipv4()); | ||||
|         assert!(fallback.is_empty()); | ||||
|  | ||||
|         let (mut preferred, fallback) = SocketAddrs { | ||||
|             iter: vec![v4_addr, v6_addr].into_iter(), | ||||
|         } | ||||
|         .split_by_preference(None, Some(ip_v6)); | ||||
|         assert!(preferred.next().unwrap().is_ipv6()); | ||||
|         assert!(fallback.is_empty()); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_name_from_str() { | ||||
|         const DOMAIN: &str = "test.example.com"; | ||||
|         let name = Name::from_str(DOMAIN).expect("Should be a valid domain"); | ||||
|         assert_eq!(name.as_str(), DOMAIN); | ||||
|         assert_eq!(name.to_string(), DOMAIN); | ||||
|     } | ||||
| } | ||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,412 +0,0 @@ | ||||
| //! Connectors used by the `Client`. | ||||
| //! | ||||
| //! This module contains: | ||||
| //! | ||||
| //! - A default [`HttpConnector`][] that does DNS resolution and establishes | ||||
| //!   connections over TCP. | ||||
| //! - Types to build custom connectors. | ||||
| //! | ||||
| //! # Connectors | ||||
| //! | ||||
| //! A "connector" is a [`Service`][] that takes a [`Uri`][] destination, and | ||||
| //! its `Response` is some type implementing [`AsyncRead`][], [`AsyncWrite`][], | ||||
| //! and [`Connection`][]. | ||||
| //! | ||||
| //! ## Custom Connectors | ||||
| //! | ||||
| //! A simple connector that ignores the `Uri` destination and always returns | ||||
| //! a TCP connection to the same address could be written like this: | ||||
| //! | ||||
| //! ```rust,ignore | ||||
| //! let connector = tower::service_fn(|_dst| async { | ||||
| //!     tokio::net::TcpStream::connect("127.0.0.1:1337") | ||||
| //! }) | ||||
| //! ``` | ||||
| //! | ||||
| //! Or, fully written out: | ||||
| //! | ||||
| //! ``` | ||||
| //! # #[cfg(feature = "runtime")] | ||||
| //! # mod rt { | ||||
| //! use std::{future::Future, net::SocketAddr, pin::Pin, task::{self, Poll}}; | ||||
| //! use hyper::{service::Service, Uri}; | ||||
| //! use tokio::net::TcpStream; | ||||
| //! | ||||
| //! #[derive(Clone)] | ||||
| //! struct LocalConnector; | ||||
| //! | ||||
| //! impl Service<Uri> for LocalConnector { | ||||
| //!     type Response = TcpStream; | ||||
| //!     type Error = std::io::Error; | ||||
| //!     // We can't "name" an `async` generated future. | ||||
| //!     type Future = Pin<Box< | ||||
| //!         dyn Future<Output = Result<Self::Response, Self::Error>> + Send | ||||
| //!     >>; | ||||
| //! | ||||
| //!     fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||
| //!         // This connector is always ready, but others might not be. | ||||
| //!         Poll::Ready(Ok(())) | ||||
| //!     } | ||||
| //! | ||||
| //!     fn call(&mut self, _: Uri) -> Self::Future { | ||||
| //!         Box::pin(TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 1337)))) | ||||
| //!     } | ||||
| //! } | ||||
| //! # } | ||||
| //! ``` | ||||
| //! | ||||
| //! It's worth noting that for `TcpStream`s, the [`HttpConnector`][] is a | ||||
| //! better starting place to extend from. | ||||
| //! | ||||
| //! Using either of the above connector examples, it can be used with the | ||||
| //! `Client` like this: | ||||
| //! | ||||
| //! ``` | ||||
| //! # #[cfg(feature = "runtime")] | ||||
| //! # fn rt () { | ||||
| //! # let connector = hyper::client::HttpConnector::new(); | ||||
| //! // let connector = ... | ||||
| //! | ||||
| //! let client = hyper::Client::builder() | ||||
| //!     .build::<_, hyper::Body>(connector); | ||||
| //! # } | ||||
| //! ``` | ||||
| //! | ||||
| //! | ||||
| //! [`HttpConnector`]: HttpConnector | ||||
| //! [`Service`]: crate::service::Service | ||||
| //! [`Uri`]: ::http::Uri | ||||
| //! [`AsyncRead`]: tokio::io::AsyncRead | ||||
| //! [`AsyncWrite`]: tokio::io::AsyncWrite | ||||
| //! [`Connection`]: Connection | ||||
| use std::fmt; | ||||
|  | ||||
| use ::http::Extensions; | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![feature = "tcp"] | ||||
|  | ||||
|     pub use self::http::{HttpConnector, HttpInfo}; | ||||
|  | ||||
|     pub mod dns; | ||||
|     mod http; | ||||
| } | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![any(feature = "http1", feature = "http2")] | ||||
|  | ||||
|     pub use self::sealed::Connect; | ||||
| } | ||||
|  | ||||
| /// Describes a type returned by a connector. | ||||
| pub trait Connection { | ||||
|     /// Return metadata describing the connection. | ||||
|     fn connected(&self) -> Connected; | ||||
| } | ||||
|  | ||||
| /// Extra information about the connected transport. | ||||
| /// | ||||
| /// This can be used to inform recipients about things like if ALPN | ||||
| /// was used, or if connected to an HTTP proxy. | ||||
| #[derive(Debug)] | ||||
| pub struct Connected { | ||||
|     pub(super) alpn: Alpn, | ||||
|     pub(super) is_proxied: bool, | ||||
|     pub(super) extra: Option<Extra>, | ||||
| } | ||||
|  | ||||
| pub(super) struct Extra(Box<dyn ExtraInner>); | ||||
|  | ||||
| #[derive(Clone, Copy, Debug, PartialEq)] | ||||
| pub(super) enum Alpn { | ||||
|     H2, | ||||
|     None, | ||||
| } | ||||
|  | ||||
| impl Connected { | ||||
|     /// Create new `Connected` type with empty metadata. | ||||
|     pub fn new() -> Connected { | ||||
|         Connected { | ||||
|             alpn: Alpn::None, | ||||
|             is_proxied: false, | ||||
|             extra: None, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Set whether the connected transport is to an HTTP proxy. | ||||
|     /// | ||||
|     /// This setting will affect if HTTP/1 requests written on the transport | ||||
|     /// will have the request-target in absolute-form or origin-form: | ||||
|     /// | ||||
|     /// - When `proxy(false)`: | ||||
|     /// | ||||
|     /// ```http | ||||
|     /// GET /guide HTTP/1.1 | ||||
|     /// ``` | ||||
|     /// | ||||
|     /// - When `proxy(true)`: | ||||
|     /// | ||||
|     /// ```http | ||||
|     /// GET http://hyper.rs/guide HTTP/1.1 | ||||
|     /// ``` | ||||
|     /// | ||||
|     /// Default is `false`. | ||||
|     pub fn proxy(mut self, is_proxied: bool) -> Connected { | ||||
|         self.is_proxied = is_proxied; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Determines if the connected transport is to an HTTP proxy. | ||||
|     pub fn is_proxied(&self) -> bool { | ||||
|         self.is_proxied | ||||
|     } | ||||
|  | ||||
|     /// Set extra connection information to be set in the extensions of every `Response`. | ||||
|     pub fn extra<T: Clone + Send + Sync + 'static>(mut self, extra: T) -> Connected { | ||||
|         if let Some(prev) = self.extra { | ||||
|             self.extra = Some(Extra(Box::new(ExtraChain(prev.0, extra)))); | ||||
|         } else { | ||||
|             self.extra = Some(Extra(Box::new(ExtraEnvelope(extra)))); | ||||
|         } | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Copies the extra connection information into an `Extensions` map. | ||||
|     pub fn get_extras(&self, extensions: &mut Extensions) { | ||||
|         if let Some(extra) = &self.extra { | ||||
|             extra.set(extensions); | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Set that the connected transport negotiated HTTP/2 as its next protocol. | ||||
|     pub fn negotiated_h2(mut self) -> Connected { | ||||
|         self.alpn = Alpn::H2; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Determines if the connected transport negotiated HTTP/2 as its next protocol. | ||||
|     pub fn is_negotiated_h2(&self) -> bool { | ||||
|         self.alpn == Alpn::H2 | ||||
|     } | ||||
|  | ||||
|     // Don't public expose that `Connected` is `Clone`, unsure if we want to | ||||
|     // keep that contract... | ||||
|     #[cfg(feature = "http2")] | ||||
|     pub(super) fn clone(&self) -> Connected { | ||||
|         Connected { | ||||
|             alpn: self.alpn.clone(), | ||||
|             is_proxied: self.is_proxied, | ||||
|             extra: self.extra.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Extra ===== | ||||
|  | ||||
| impl Extra { | ||||
|     pub(super) fn set(&self, res: &mut Extensions) { | ||||
|         self.0.set(res); | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Clone for Extra { | ||||
|     fn clone(&self) -> Extra { | ||||
|         Extra(self.0.clone_box()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for Extra { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("Extra").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| trait ExtraInner: Send + Sync { | ||||
|     fn clone_box(&self) -> Box<dyn ExtraInner>; | ||||
|     fn set(&self, res: &mut Extensions); | ||||
| } | ||||
|  | ||||
| // This indirection allows the `Connected` to have a type-erased "extra" value, | ||||
| // while that type still knows its inner extra type. This allows the correct | ||||
| // TypeId to be used when inserting into `res.extensions_mut()`. | ||||
| #[derive(Clone)] | ||||
| struct ExtraEnvelope<T>(T); | ||||
|  | ||||
| impl<T> ExtraInner for ExtraEnvelope<T> | ||||
| where | ||||
|     T: Clone + Send + Sync + 'static, | ||||
| { | ||||
|     fn clone_box(&self) -> Box<dyn ExtraInner> { | ||||
|         Box::new(self.clone()) | ||||
|     } | ||||
|  | ||||
|     fn set(&self, res: &mut Extensions) { | ||||
|         res.insert(self.0.clone()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct ExtraChain<T>(Box<dyn ExtraInner>, T); | ||||
|  | ||||
| impl<T: Clone> Clone for ExtraChain<T> { | ||||
|     fn clone(&self) -> Self { | ||||
|         ExtraChain(self.0.clone_box(), self.1.clone()) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T> ExtraInner for ExtraChain<T> | ||||
| where | ||||
|     T: Clone + Send + Sync + 'static, | ||||
| { | ||||
|     fn clone_box(&self) -> Box<dyn ExtraInner> { | ||||
|         Box::new(self.clone()) | ||||
|     } | ||||
|  | ||||
|     fn set(&self, res: &mut Extensions) { | ||||
|         self.0.set(res); | ||||
|         res.insert(self.1.clone()); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pub(super) mod sealed { | ||||
|     use std::error::Error as StdError; | ||||
|  | ||||
|     use ::http::Uri; | ||||
|     use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
|     use super::Connection; | ||||
|     use crate::common::{Future, Unpin}; | ||||
|  | ||||
|     /// Connect to a destination, returning an IO transport. | ||||
|     /// | ||||
|     /// A connector receives a [`Uri`](::http::Uri) and returns a `Future` of the | ||||
|     /// ready connection. | ||||
|     /// | ||||
|     /// # Trait Alias | ||||
|     /// | ||||
|     /// This is really just an *alias* for the `tower::Service` trait, with | ||||
|     /// additional bounds set for convenience *inside* hyper. You don't actually | ||||
|     /// implement this trait, but `tower::Service<Uri>` instead. | ||||
|     // The `Sized` bound is to prevent creating `dyn Connect`, since they cannot | ||||
|     // fit the `Connect` bounds because of the blanket impl for `Service`. | ||||
|     pub trait Connect: Sealed + Sized { | ||||
|         #[doc(hidden)] | ||||
|         type _Svc: ConnectSvc; | ||||
|         #[doc(hidden)] | ||||
|         fn connect(self, internal_only: Internal, dst: Uri) -> <Self::_Svc as ConnectSvc>::Future; | ||||
|     } | ||||
|  | ||||
|     pub trait ConnectSvc { | ||||
|         type Connection: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static; | ||||
|         type Error: Into<Box<dyn StdError + Send + Sync>>; | ||||
|         type Future: Future<Output = Result<Self::Connection, Self::Error>> + Unpin + Send + 'static; | ||||
|  | ||||
|         fn connect(self, internal_only: Internal, dst: Uri) -> Self::Future; | ||||
|     } | ||||
|  | ||||
|     impl<S, T> Connect for S | ||||
|     where | ||||
|         S: tower_service::Service<Uri, Response = T> + Send + 'static, | ||||
|         S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         S::Future: Unpin + Send, | ||||
|         T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, | ||||
|     { | ||||
|         type _Svc = S; | ||||
|  | ||||
|         fn connect(self, _: Internal, dst: Uri) -> crate::service::Oneshot<S, Uri> { | ||||
|             crate::service::oneshot(self, dst) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl<S, T> ConnectSvc for S | ||||
|     where | ||||
|         S: tower_service::Service<Uri, Response = T> + Send + 'static, | ||||
|         S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         S::Future: Unpin + Send, | ||||
|         T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, | ||||
|     { | ||||
|         type Connection = T; | ||||
|         type Error = S::Error; | ||||
|         type Future = crate::service::Oneshot<S, Uri>; | ||||
|  | ||||
|         fn connect(self, _: Internal, dst: Uri) -> Self::Future { | ||||
|             crate::service::oneshot(self, dst) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl<S, T> Sealed for S | ||||
|     where | ||||
|         S: tower_service::Service<Uri, Response = T> + Send, | ||||
|         S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         S::Future: Unpin + Send, | ||||
|         T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static, | ||||
|     { | ||||
|     } | ||||
|  | ||||
|     pub trait Sealed {} | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     pub struct Internal; | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::Connected; | ||||
|  | ||||
|     #[derive(Clone, Debug, PartialEq)] | ||||
|     struct Ex1(usize); | ||||
|  | ||||
|     #[derive(Clone, Debug, PartialEq)] | ||||
|     struct Ex2(&'static str); | ||||
|  | ||||
|     #[derive(Clone, Debug, PartialEq)] | ||||
|     struct Ex3(&'static str); | ||||
|  | ||||
|     #[test] | ||||
|     fn test_connected_extra() { | ||||
|         let c1 = Connected::new().extra(Ex1(41)); | ||||
|  | ||||
|         let mut ex = ::http::Extensions::new(); | ||||
|  | ||||
|         assert_eq!(ex.get::<Ex1>(), None); | ||||
|  | ||||
|         c1.extra.as_ref().expect("c1 extra").set(&mut ex); | ||||
|  | ||||
|         assert_eq!(ex.get::<Ex1>(), Some(&Ex1(41))); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_connected_extra_chain() { | ||||
|         // If a user composes connectors and at each stage, there's "extra" | ||||
|         // info to attach, it shouldn't override the previous extras. | ||||
|  | ||||
|         let c1 = Connected::new() | ||||
|             .extra(Ex1(45)) | ||||
|             .extra(Ex2("zoom")) | ||||
|             .extra(Ex3("pew pew")); | ||||
|  | ||||
|         let mut ex1 = ::http::Extensions::new(); | ||||
|  | ||||
|         assert_eq!(ex1.get::<Ex1>(), None); | ||||
|         assert_eq!(ex1.get::<Ex2>(), None); | ||||
|         assert_eq!(ex1.get::<Ex3>(), None); | ||||
|  | ||||
|         c1.extra.as_ref().expect("c1 extra").set(&mut ex1); | ||||
|  | ||||
|         assert_eq!(ex1.get::<Ex1>(), Some(&Ex1(45))); | ||||
|         assert_eq!(ex1.get::<Ex2>(), Some(&Ex2("zoom"))); | ||||
|         assert_eq!(ex1.get::<Ex3>(), Some(&Ex3("pew pew"))); | ||||
|  | ||||
|         // Just like extensions, inserting the same type overrides previous type. | ||||
|         let c2 = Connected::new() | ||||
|             .extra(Ex1(33)) | ||||
|             .extra(Ex2("hiccup")) | ||||
|             .extra(Ex1(99)); | ||||
|  | ||||
|         let mut ex2 = ::http::Extensions::new(); | ||||
|  | ||||
|         c2.extra.as_ref().expect("c2 extra").set(&mut ex2); | ||||
|  | ||||
|         assert_eq!(ex2.get::<Ex1>(), Some(&Ex1(99))); | ||||
|         assert_eq!(ex2.get::<Ex2>(), Some(&Ex2("hiccup"))); | ||||
|     } | ||||
| } | ||||
| @@ -1,13 +1,13 @@ | ||||
| #[cfg(feature = "http2")] | ||||
| use std::future::Future; | ||||
|  | ||||
| use futures_util::FutureExt; | ||||
| use tokio::sync::{mpsc, oneshot}; | ||||
|  | ||||
| #[cfg(feature = "http2")] | ||||
| use crate::common::Pin; | ||||
| use crate::common::{task, Poll}; | ||||
|  | ||||
| #[cfg(test)] | ||||
| pub(crate) type RetryPromise<T, U> = oneshot::Receiver<Result<U, (crate::Error, Option<T>)>>; | ||||
| pub(crate) type Promise<T> = oneshot::Receiver<Result<T, crate::Error>>; | ||||
|  | ||||
| @@ -59,13 +59,16 @@ impl<T, U> Sender<T, U> { | ||||
|             .map_err(|_| crate::Error::new_closed()) | ||||
|     } | ||||
|  | ||||
|     #[cfg(test)] | ||||
|     pub(crate) fn is_ready(&self) -> bool { | ||||
|         self.giver.is_wanting() | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     pub(crate) fn is_closed(&self) -> bool { | ||||
|         self.giver.is_canceled() | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     fn can_send(&mut self) -> bool { | ||||
|         if self.giver.give() || !self.buffered_once { | ||||
| @@ -80,6 +83,7 @@ impl<T, U> Sender<T, U> { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(test)] | ||||
|     pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { | ||||
|         if !self.can_send() { | ||||
|             return Err(val); | ||||
| @@ -113,14 +117,17 @@ impl<T, U> Sender<T, U> { | ||||
|  | ||||
| #[cfg(feature = "http2")] | ||||
| impl<T, U> UnboundedSender<T, U> { | ||||
|     /* | ||||
|     pub(crate) fn is_ready(&self) -> bool { | ||||
|         !self.giver.is_canceled() | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     pub(crate) fn is_closed(&self) -> bool { | ||||
|         self.giver.is_canceled() | ||||
|     } | ||||
|  | ||||
|     #[cfg(test)] | ||||
|     pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { | ||||
|         let (tx, rx) = oneshot::channel(); | ||||
|         self.inner | ||||
| @@ -128,6 +135,14 @@ impl<T, U> UnboundedSender<T, U> { | ||||
|             .map(move |_| rx) | ||||
|             .map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn send(&mut self, val: T) -> Result<Promise<U>, T> { | ||||
|         let (tx, rx) = oneshot::channel(); | ||||
|         self.inner | ||||
|             .send(Envelope(Some((val, Callback::NoRetry(tx))))) | ||||
|             .map(move |_| rx) | ||||
|             .map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "http2")] | ||||
| @@ -169,6 +184,7 @@ impl<T, U> Receiver<T, U> { | ||||
|  | ||||
|     #[cfg(feature = "http1")] | ||||
|     pub(crate) fn try_recv(&mut self) -> Option<(T, Callback<T, U>)> { | ||||
|         use futures_util::FutureExt; | ||||
|         match self.inner.recv().now_or_never() { | ||||
|             Some(Some(mut env)) => env.0.take(), | ||||
|             _ => None, | ||||
| @@ -198,6 +214,7 @@ impl<T, U> Drop for Envelope<T, U> { | ||||
| } | ||||
|  | ||||
| pub(crate) enum Callback<T, U> { | ||||
|     #[allow(unused)] | ||||
|     Retry(oneshot::Sender<Result<U, (crate::Error, Option<T>)>>), | ||||
|     NoRetry(oneshot::Sender<Result<U, crate::Error>>), | ||||
| } | ||||
| @@ -301,6 +318,7 @@ mod tests { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(not(miri))] | ||||
|     #[tokio::test] | ||||
|     async fn drop_receiver_sends_cancel_errors() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
| @@ -323,6 +341,7 @@ mod tests { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(not(miri))] | ||||
|     #[tokio::test] | ||||
|     async fn sender_checks_for_want_on_send() { | ||||
|         let (mut tx, mut rx) = channel::<Custom, ()>(); | ||||
| @@ -363,7 +382,6 @@ mod tests { | ||||
|         use crate::{Body, Request, Response}; | ||||
|  | ||||
|         let rt = tokio::runtime::Builder::new_current_thread() | ||||
|             .enable_all() | ||||
|             .build() | ||||
|             .unwrap(); | ||||
|         let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>(); | ||||
| @@ -386,7 +404,6 @@ mod tests { | ||||
|     #[bench] | ||||
|     fn giver_queue_not_ready(b: &mut test::Bencher) { | ||||
|         let rt = tokio::runtime::Builder::new_current_thread() | ||||
|             .enable_all() | ||||
|             .build() | ||||
|             .unwrap(); | ||||
|         let (_tx, mut rx) = channel::<i32, ()>(); | ||||
|   | ||||
| @@ -1,68 +1,18 @@ | ||||
| //! HTTP Client | ||||
| //! | ||||
| //! There are two levels of APIs provided for construct HTTP clients: | ||||
| //! | ||||
| //! - The higher-level [`Client`](Client) type. | ||||
| //! - The lower-level [`conn`](conn) module. | ||||
| //! | ||||
| //! # Client | ||||
| //! | ||||
| //! The [`Client`](Client) is the main way to send HTTP requests to a server. | ||||
| //! The default `Client` provides these things on top of the lower-level API: | ||||
| //! | ||||
| //! - A default **connector**, able to resolve hostnames and connect to | ||||
| //!   destinations over plain-text TCP. | ||||
| //! - A **pool** of existing connections, allowing better performance when | ||||
| //!   making multiple requests to the same hostname. | ||||
| //! - Automatic setting of the `Host` header, based on the request `Uri`. | ||||
| //! - Automatic request **retries** when a pooled connection is closed by the | ||||
| //!   server before any bytes have been written. | ||||
| //! | ||||
| //! Many of these features can configured, by making use of | ||||
| //! [`Client::builder`](Client::builder). | ||||
| //! hyper provides HTTP over a single connection. See the [`conn`](conn) module. | ||||
| //! | ||||
| //! ## Example | ||||
| //! | ||||
| //! For a small example program simply fetching a URL, take a look at the | ||||
| //! [full client example](https://github.com/hyperium/hyper/blob/master/examples/client.rs). | ||||
| //! | ||||
| //! ``` | ||||
| //! # #[cfg(all(feature = "tcp", feature = "client", any(feature = "http1", feature = "http2")))] | ||||
| //! # async fn fetch_httpbin() -> hyper::Result<()> { | ||||
| //! use hyper::{body::HttpBody as _, Client, Uri}; | ||||
| //! | ||||
| //! let client = Client::new(); | ||||
| //! | ||||
| //! // Make a GET /ip to 'http://httpbin.org' | ||||
| //! let res = client.get(Uri::from_static("http://httpbin.org/ip")).await?; | ||||
| //! | ||||
| //! // And then, if the request gets a response... | ||||
| //! println!("status: {}", res.status()); | ||||
| //! | ||||
| //! // Concatenate the body stream into a single buffer... | ||||
| //! let buf = hyper::body::to_bytes(res).await?; | ||||
| //! | ||||
| //! println!("body: {:?}", buf); | ||||
| //! # Ok(()) | ||||
| //! # } | ||||
| //! # fn main () {} | ||||
| //! ``` | ||||
|  | ||||
| #[cfg(feature = "tcp")] | ||||
| pub use self::connect::HttpConnector; | ||||
|  | ||||
| pub mod connect; | ||||
| #[cfg(all(test, feature = "runtime"))] | ||||
| mod tests; | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![any(feature = "http1", feature = "http2")] | ||||
|  | ||||
|     pub use self::client::{Builder, Client, ResponseFuture}; | ||||
|  | ||||
|     mod client; | ||||
|     pub mod conn; | ||||
|     pub(super) mod dispatch; | ||||
|     mod pool; | ||||
|     pub mod service; | ||||
| } | ||||
|   | ||||
							
								
								
									
										1044
									
								
								src/client/pool.rs
									
									
									
									
									
								
							
							
						
						
									
										1044
									
								
								src/client/pool.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -1,89 +0,0 @@ | ||||
| //! Utilities used to interact with the Tower ecosystem. | ||||
| //! | ||||
| //! This module provides `Connect` which hook-ins into the Tower ecosystem. | ||||
|  | ||||
| use std::error::Error as StdError; | ||||
| use std::future::Future; | ||||
| use std::marker::PhantomData; | ||||
|  | ||||
| use tracing::debug; | ||||
|  | ||||
| use super::conn::{Builder, SendRequest}; | ||||
| use crate::{ | ||||
|     body::HttpBody, | ||||
|     common::{task, Pin, Poll}, | ||||
|     service::{MakeConnection, Service}, | ||||
| }; | ||||
|  | ||||
| /// Creates a connection via `SendRequest`. | ||||
| /// | ||||
| /// This accepts a `hyper::client::conn::Builder` and provides | ||||
| /// a `MakeService` implementation to create connections from some | ||||
| /// target `T`. | ||||
| #[derive(Debug)] | ||||
| pub struct Connect<C, B, T> { | ||||
|     inner: C, | ||||
|     builder: Builder, | ||||
|     _pd: PhantomData<fn(T, B)>, | ||||
| } | ||||
|  | ||||
| impl<C, B, T> Connect<C, B, T> { | ||||
|     /// Create a new `Connect` with some inner connector `C` and a connection | ||||
|     /// builder. | ||||
|     pub fn new(inner: C, builder: Builder) -> Self { | ||||
|         Self { | ||||
|             inner, | ||||
|             builder, | ||||
|             _pd: PhantomData, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<C, B, T> Service<T> for Connect<C, B, T> | ||||
| where | ||||
|     C: MakeConnection<T>, | ||||
|     C::Connection: Unpin + Send + 'static, | ||||
|     C::Future: Send + 'static, | ||||
|     C::Error: Into<Box<dyn StdError + Send + Sync>> + Send, | ||||
|     B: HttpBody + Unpin + Send + 'static, | ||||
|     B::Data: Send + Unpin, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
| { | ||||
|     type Response = SendRequest<B>; | ||||
|     type Error = crate::Error; | ||||
|     type Future = | ||||
|         Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>>; | ||||
|  | ||||
|     fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||
|         self.inner | ||||
|             .poll_ready(cx) | ||||
|             .map_err(|e| crate::Error::new(crate::error::Kind::Connect).with(e.into())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, req: T) -> Self::Future { | ||||
|         let builder = self.builder.clone(); | ||||
|         let io = self.inner.make_connection(req); | ||||
|  | ||||
|         let fut = async move { | ||||
|             match io.await { | ||||
|                 Ok(io) => match builder.handshake(io).await { | ||||
|                     Ok((sr, conn)) => { | ||||
|                         builder.exec.execute(async move { | ||||
|                             if let Err(e) = conn.await { | ||||
|                                 debug!("connection error: {:?}", e); | ||||
|                             } | ||||
|                         }); | ||||
|                         Ok(sr) | ||||
|                     } | ||||
|                     Err(e) => Err(e), | ||||
|                 }, | ||||
|                 Err(e) => { | ||||
|                     let err = crate::Error::new(crate::error::Kind::Connect).with(e.into()); | ||||
|                     Err(err) | ||||
|                 } | ||||
|             } | ||||
|         }; | ||||
|  | ||||
|         Box::pin(fut) | ||||
|     } | ||||
| } | ||||
| @@ -1,28 +1,3 @@ | ||||
| use std::io; | ||||
|  | ||||
| use futures_util::future; | ||||
| use tokio::net::TcpStream; | ||||
|  | ||||
| use super::Client; | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn client_connect_uri_argument() { | ||||
|     let connector = tower::service_fn(|dst: http::Uri| { | ||||
|         assert_eq!(dst.scheme(), Some(&http::uri::Scheme::HTTP)); | ||||
|         assert_eq!(dst.host(), Some("example.local")); | ||||
|         assert_eq!(dst.port(), None); | ||||
|         assert_eq!(dst.path(), "/", "path should be removed"); | ||||
|  | ||||
|         future::err::<TcpStream, _>(io::Error::new(io::ErrorKind::Other, "expect me")) | ||||
|     }); | ||||
|  | ||||
|     let client = Client::builder().build::<_, crate::Body>(connector); | ||||
|     let _ = client | ||||
|         .get("http://example.local/and/a/path".parse().unwrap()) | ||||
|         .await | ||||
|         .expect_err("response should fail"); | ||||
| } | ||||
|  | ||||
| /* | ||||
| // FIXME: re-implement tests with `async/await` | ||||
| #[test] | ||||
|   | ||||
| @@ -1,217 +0,0 @@ | ||||
| use std::mem; | ||||
|  | ||||
| use pin_project_lite::pin_project; | ||||
| use tokio::sync::watch; | ||||
|  | ||||
| use super::{task, Future, Pin, Poll}; | ||||
|  | ||||
| pub(crate) fn channel() -> (Signal, Watch) { | ||||
|     let (tx, rx) = watch::channel(()); | ||||
|     (Signal { tx }, Watch { rx }) | ||||
| } | ||||
|  | ||||
| pub(crate) struct Signal { | ||||
|     tx: watch::Sender<()>, | ||||
| } | ||||
|  | ||||
| pub(crate) struct Draining(Pin<Box<dyn Future<Output = ()> + Send + Sync>>); | ||||
|  | ||||
| #[derive(Clone)] | ||||
| pub(crate) struct Watch { | ||||
|     rx: watch::Receiver<()>, | ||||
| } | ||||
|  | ||||
| pin_project! { | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     pub struct Watching<F, FN> { | ||||
|         #[pin] | ||||
|         future: F, | ||||
|         state: State<FN>, | ||||
|         watch: Pin<Box<dyn Future<Output = ()> + Send + Sync>>, | ||||
|         _rx: watch::Receiver<()>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| enum State<F> { | ||||
|     Watch(F), | ||||
|     Draining, | ||||
| } | ||||
|  | ||||
| impl Signal { | ||||
|     pub(crate) fn drain(self) -> Draining { | ||||
|         let _ = self.tx.send(()); | ||||
|         Draining(Box::pin(async move { self.tx.closed().await })) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Future for Draining { | ||||
|     type Output = (); | ||||
|  | ||||
|     fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         Pin::new(&mut self.as_mut().0).poll(cx) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Watch { | ||||
|     pub(crate) fn watch<F, FN>(self, future: F, on_drain: FN) -> Watching<F, FN> | ||||
|     where | ||||
|         F: Future, | ||||
|         FN: FnOnce(Pin<&mut F>), | ||||
|     { | ||||
|         let Self { mut rx } = self; | ||||
|         let _rx = rx.clone(); | ||||
|         Watching { | ||||
|             future, | ||||
|             state: State::Watch(on_drain), | ||||
|             watch: Box::pin(async move { | ||||
|                 let _ = rx.changed().await; | ||||
|             }), | ||||
|             // Keep the receiver alive until the future completes, so that | ||||
|             // dropping it can signal that draining has completed. | ||||
|             _rx, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F, FN> Future for Watching<F, FN> | ||||
| where | ||||
|     F: Future, | ||||
|     FN: FnOnce(Pin<&mut F>), | ||||
| { | ||||
|     type Output = F::Output; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         let mut me = self.project(); | ||||
|         loop { | ||||
|             match mem::replace(me.state, State::Draining) { | ||||
|                 State::Watch(on_drain) => { | ||||
|                     match Pin::new(&mut me.watch).poll(cx) { | ||||
|                         Poll::Ready(()) => { | ||||
|                             // Drain has been triggered! | ||||
|                             on_drain(me.future.as_mut()); | ||||
|                         } | ||||
|                         Poll::Pending => { | ||||
|                             *me.state = State::Watch(on_drain); | ||||
|                             return me.future.poll(cx); | ||||
|                         } | ||||
|                     } | ||||
|                 } | ||||
|                 State::Draining => return me.future.poll(cx), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|  | ||||
|     struct TestMe { | ||||
|         draining: bool, | ||||
|         finished: bool, | ||||
|         poll_cnt: usize, | ||||
|     } | ||||
|  | ||||
|     impl Future for TestMe { | ||||
|         type Output = (); | ||||
|  | ||||
|         fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|             self.poll_cnt += 1; | ||||
|             if self.finished { | ||||
|                 Poll::Ready(()) | ||||
|             } else { | ||||
|                 Poll::Pending | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn watch() { | ||||
|         let mut mock = tokio_test::task::spawn(()); | ||||
|         mock.enter(|cx, _| { | ||||
|             let (tx, rx) = channel(); | ||||
|             let fut = TestMe { | ||||
|                 draining: false, | ||||
|                 finished: false, | ||||
|                 poll_cnt: 0, | ||||
|             }; | ||||
|  | ||||
|             let mut watch = rx.watch(fut, |mut fut| { | ||||
|                 fut.draining = true; | ||||
|             }); | ||||
|  | ||||
|             assert_eq!(watch.future.poll_cnt, 0); | ||||
|  | ||||
|             // First poll should poll the inner future | ||||
|             assert!(Pin::new(&mut watch).poll(cx).is_pending()); | ||||
|             assert_eq!(watch.future.poll_cnt, 1); | ||||
|  | ||||
|             // Second poll should poll the inner future again | ||||
|             assert!(Pin::new(&mut watch).poll(cx).is_pending()); | ||||
|             assert_eq!(watch.future.poll_cnt, 2); | ||||
|  | ||||
|             let mut draining = tx.drain(); | ||||
|             // Drain signaled, but needs another poll to be noticed. | ||||
|             assert!(!watch.future.draining); | ||||
|             assert_eq!(watch.future.poll_cnt, 2); | ||||
|  | ||||
|             // Now, poll after drain has been signaled. | ||||
|             assert!(Pin::new(&mut watch).poll(cx).is_pending()); | ||||
|             assert_eq!(watch.future.poll_cnt, 3); | ||||
|             assert!(watch.future.draining); | ||||
|  | ||||
|             // Draining is not ready until watcher completes | ||||
|             assert!(Pin::new(&mut draining).poll(cx).is_pending()); | ||||
|  | ||||
|             // Finishing up the watch future | ||||
|             watch.future.finished = true; | ||||
|             assert!(Pin::new(&mut watch).poll(cx).is_ready()); | ||||
|             assert_eq!(watch.future.poll_cnt, 4); | ||||
|             drop(watch); | ||||
|  | ||||
|             assert!(Pin::new(&mut draining).poll(cx).is_ready()); | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn watch_clones() { | ||||
|         let mut mock = tokio_test::task::spawn(()); | ||||
|         mock.enter(|cx, _| { | ||||
|             let (tx, rx) = channel(); | ||||
|  | ||||
|             let fut1 = TestMe { | ||||
|                 draining: false, | ||||
|                 finished: false, | ||||
|                 poll_cnt: 0, | ||||
|             }; | ||||
|             let fut2 = TestMe { | ||||
|                 draining: false, | ||||
|                 finished: false, | ||||
|                 poll_cnt: 0, | ||||
|             }; | ||||
|  | ||||
|             let watch1 = rx.clone().watch(fut1, |mut fut| { | ||||
|                 fut.draining = true; | ||||
|             }); | ||||
|             let watch2 = rx.watch(fut2, |mut fut| { | ||||
|                 fut.draining = true; | ||||
|             }); | ||||
|  | ||||
|             let mut draining = tx.drain(); | ||||
|  | ||||
|             // Still 2 outstanding watchers | ||||
|             assert!(Pin::new(&mut draining).poll(cx).is_pending()); | ||||
|  | ||||
|             // drop 1 for whatever reason | ||||
|             drop(watch1); | ||||
|  | ||||
|             // Still not ready, 1 other watcher still pending | ||||
|             assert!(Pin::new(&mut draining).poll(cx).is_pending()); | ||||
|  | ||||
|             drop(watch2); | ||||
|  | ||||
|             // Now all watchers are gone, draining is complete | ||||
|             assert!(Pin::new(&mut draining).poll(cx).is_ready()); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
| @@ -3,28 +3,17 @@ use std::future::Future; | ||||
| use std::pin::Pin; | ||||
| use std::sync::Arc; | ||||
|  | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| use crate::body::Body; | ||||
| #[cfg(feature = "server")] | ||||
| use crate::body::HttpBody; | ||||
| #[cfg(all(feature = "http2", feature = "server"))] | ||||
| use crate::proto::h2::server::H2Stream; | ||||
| use crate::rt::Executor; | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| use crate::server::conn::spawn_all::{NewSvcTask, Watcher}; | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| use crate::service::HttpService; | ||||
|  | ||||
| #[cfg(feature = "server")] | ||||
| pub trait ConnStreamExec<F, B: HttpBody>: Clone { | ||||
|     fn execute_h2stream(&mut self, fut: H2Stream<F, B>); | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| pub trait NewSvcExec<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>>: Clone { | ||||
|     fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>); | ||||
| } | ||||
|  | ||||
| pub(crate) type BoxSendFuture = Pin<Box<dyn Future<Output = ()> + Send>>; | ||||
|  | ||||
| // Either the user provides an executor for background tasks, or we use | ||||
| @@ -44,13 +33,13 @@ impl Exec { | ||||
|     { | ||||
|         match *self { | ||||
|             Exec::Default => { | ||||
|                 #[cfg(feature = "tcp")] | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 { | ||||
|                     tokio::task::spawn(fut); | ||||
|                 } | ||||
|                 #[cfg(not(feature = "tcp"))] | ||||
|  | ||||
|                 #[cfg(not(feature = "runtime"))] | ||||
|                 { | ||||
|                     // If no runtime, we need an executor! | ||||
|                     panic!("executor must be set") | ||||
|                 } | ||||
|             } | ||||
| @@ -78,18 +67,6 @@ where | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| impl<I, N, S, E, W> NewSvcExec<I, N, S, E, W> for Exec | ||||
| where | ||||
|     NewSvcTask<I, N, S, E, W>: Future<Output = ()> + Send + 'static, | ||||
|     S: HttpService<Body>, | ||||
|     W: Watcher<I, S, E>, | ||||
| { | ||||
|     fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>) { | ||||
|         self.execute(fut) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ==== impl Executor ===== | ||||
|  | ||||
| #[cfg(feature = "server")] | ||||
| @@ -104,19 +81,6 @@ where | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| impl<I, N, S, E, W> NewSvcExec<I, N, S, E, W> for E | ||||
| where | ||||
|     E: Executor<NewSvcTask<I, N, S, E, W>> + Clone, | ||||
|     NewSvcTask<I, N, S, E, W>: Future<Output = ()>, | ||||
|     S: HttpService<Body>, | ||||
|     W: Watcher<I, S, E>, | ||||
| { | ||||
|     fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>) { | ||||
|         self.execute(fut) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // If http2 is not enable, we just have a stub here, so that the trait bounds | ||||
| // that *would* have been needed are still checked. Why? | ||||
| // | ||||
|   | ||||
| @@ -60,7 +60,7 @@ where | ||||
|                 // TODO: There should be a way to do following two lines cleaner... | ||||
|                 buf.put_slice(&prefix[..copy_len]); | ||||
|                 prefix.advance(copy_len); | ||||
|                 // Put back whats left | ||||
|                 // Put back what's left | ||||
|                 if !prefix.is_empty() { | ||||
|                     self.pre = Some(prefix); | ||||
|                 } | ||||
|   | ||||
| @@ -1,76 +0,0 @@ | ||||
| use pin_project_lite::pin_project; | ||||
|  | ||||
| use super::{task, Future, Pin, Poll}; | ||||
|  | ||||
| pub(crate) trait Started: Future { | ||||
|     fn started(&self) -> bool; | ||||
| } | ||||
|  | ||||
| pub(crate) fn lazy<F, R>(func: F) -> Lazy<F, R> | ||||
| where | ||||
|     F: FnOnce() -> R, | ||||
|     R: Future + Unpin, | ||||
| { | ||||
|     Lazy { | ||||
|         inner: Inner::Init { func }, | ||||
|     } | ||||
| } | ||||
|  | ||||
| // FIXME: allow() required due to `impl Trait` leaking types to this lint | ||||
| pin_project! { | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     pub(crate) struct Lazy<F, R> { | ||||
|         #[pin] | ||||
|         inner: Inner<F, R>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pin_project! { | ||||
|     #[project = InnerProj] | ||||
|     #[project_replace = InnerProjReplace] | ||||
|     enum Inner<F, R> { | ||||
|         Init { func: F }, | ||||
|         Fut { #[pin] fut: R }, | ||||
|         Empty, | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F, R> Started for Lazy<F, R> | ||||
| where | ||||
|     F: FnOnce() -> R, | ||||
|     R: Future, | ||||
| { | ||||
|     fn started(&self) -> bool { | ||||
|         match self.inner { | ||||
|             Inner::Init { .. } => false, | ||||
|             Inner::Fut { .. } | Inner::Empty => true, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F, R> Future for Lazy<F, R> | ||||
| where | ||||
|     F: FnOnce() -> R, | ||||
|     R: Future, | ||||
| { | ||||
|     type Output = R::Output; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         let mut this = self.project(); | ||||
|  | ||||
|         if let InnerProj::Fut { fut } = this.inner.as_mut().project() { | ||||
|             return fut.poll(cx); | ||||
|         } | ||||
|  | ||||
|         match this.inner.as_mut().project_replace(Inner::Empty) { | ||||
|             InnerProjReplace::Init { func } => { | ||||
|                 this.inner.set(Inner::Fut { fut: func() }); | ||||
|                 if let InnerProj::Fut { fut } = this.inner.project() { | ||||
|                     return fut.poll(cx); | ||||
|                 } | ||||
|                 unreachable!() | ||||
|             } | ||||
|             _ => unreachable!("lazy state wrong"), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -10,24 +10,13 @@ macro_rules! ready { | ||||
| pub(crate) mod buf; | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| pub(crate) mod date; | ||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||
| pub(crate) mod drain; | ||||
| #[cfg(any(feature = "http1", feature = "http2", feature = "server"))] | ||||
| pub(crate) mod exec; | ||||
| pub(crate) mod io; | ||||
| #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||
| mod lazy; | ||||
| mod never; | ||||
| #[cfg(any( | ||||
|     feature = "stream", | ||||
|     all(feature = "client", any(feature = "http1", feature = "http2")) | ||||
| ))] | ||||
| pub(crate) mod sync_wrapper; | ||||
| pub(crate) mod task; | ||||
| pub(crate) mod watch; | ||||
|  | ||||
| #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||
| pub(crate) use self::lazy::{lazy, Started as Lazy}; | ||||
| #[cfg(any(feature = "http1", feature = "http2", feature = "runtime"))] | ||||
| pub(crate) use self::never::Never; | ||||
| pub(crate) use self::task::Poll; | ||||
|   | ||||
| @@ -1,110 +0,0 @@ | ||||
| /* | ||||
|  * This is a copy of the sync_wrapper crate. | ||||
|  */ | ||||
|  | ||||
| /// A mutual exclusion primitive that relies on static type information only | ||||
| /// | ||||
| /// In some cases synchronization can be proven statically: whenever you hold an exclusive `&mut` | ||||
| /// reference, the Rust type system ensures that no other part of the program can hold another | ||||
| /// reference to the data. Therefore it is safe to access it even if the current thread obtained | ||||
| /// this reference via a channel. Whenever this is the case, the overhead of allocating and locking | ||||
| /// a [`Mutex`] can be avoided by using this static version. | ||||
| /// | ||||
| /// One example where this is often applicable is [`Future`], which requires an exclusive reference | ||||
| /// for its [`poll`] method: While a given `Future` implementation may not be safe to access by | ||||
| /// multiple threads concurrently, the executor can only run the `Future` on one thread at any | ||||
| /// given time, making it [`Sync`] in practice as long as the implementation is `Send`. You can | ||||
| /// therefore use the sync wrapper to prove that your data structure is `Sync` even though it | ||||
| /// contains such a `Future`. | ||||
| /// | ||||
| /// # Example | ||||
| /// | ||||
| /// ```ignore | ||||
| /// use hyper::common::sync_wrapper::SyncWrapper; | ||||
| /// use std::future::Future; | ||||
| /// | ||||
| /// struct MyThing { | ||||
| ///     future: SyncWrapper<Box<dyn Future<Output = String> + Send>>, | ||||
| /// } | ||||
| /// | ||||
| /// impl MyThing { | ||||
| ///     // all accesses to `self.future` now require an exclusive reference or ownership | ||||
| /// } | ||||
| /// | ||||
| /// fn assert_sync<T: Sync>() {} | ||||
| /// | ||||
| /// assert_sync::<MyThing>(); | ||||
| /// ``` | ||||
| /// | ||||
| /// [`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html | ||||
| /// [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html | ||||
| /// [`poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#method.poll | ||||
| /// [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html | ||||
| #[repr(transparent)] | ||||
| pub(crate) struct SyncWrapper<T>(T); | ||||
|  | ||||
| impl<T> SyncWrapper<T> { | ||||
|     /// Creates a new SyncWrapper containing the given value. | ||||
|     /// | ||||
|     /// # Examples | ||||
|     /// | ||||
|     /// ```ignore | ||||
|     /// use hyper::common::sync_wrapper::SyncWrapper; | ||||
|     /// | ||||
|     /// let wrapped = SyncWrapper::new(42); | ||||
|     /// ``` | ||||
|     pub(crate) fn new(value: T) -> Self { | ||||
|         Self(value) | ||||
|     } | ||||
|  | ||||
|     /// Acquires a reference to the protected value. | ||||
|     /// | ||||
|     /// This is safe because it requires an exclusive reference to the wrapper. Therefore this method | ||||
|     /// neither panics nor does it return an error. This is in contrast to [`Mutex::get_mut`] which | ||||
|     /// returns an error if another thread panicked while holding the lock. It is not recommended | ||||
|     /// to send an exclusive reference to a potentially damaged value to another thread for further | ||||
|     /// processing. | ||||
|     /// | ||||
|     /// [`Mutex::get_mut`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.get_mut | ||||
|     /// | ||||
|     /// # Examples | ||||
|     /// | ||||
|     /// ```ignore | ||||
|     /// use hyper::common::sync_wrapper::SyncWrapper; | ||||
|     /// | ||||
|     /// let mut wrapped = SyncWrapper::new(42); | ||||
|     /// let value = wrapped.get_mut(); | ||||
|     /// *value = 0; | ||||
|     /// assert_eq!(*wrapped.get_mut(), 0); | ||||
|     /// ``` | ||||
|     pub(crate) fn get_mut(&mut self) -> &mut T { | ||||
|         &mut self.0 | ||||
|     } | ||||
|  | ||||
|     /// Consumes this wrapper, returning the underlying data. | ||||
|     /// | ||||
|     /// This is safe because it requires ownership of the wrapper, aherefore this method will neither | ||||
|     /// panic nor does it return an error. This is in contrast to [`Mutex::into_inner`] which | ||||
|     /// returns an error if another thread panicked while holding the lock. It is not recommended | ||||
|     /// to send an exclusive reference to a potentially damaged value to another thread for further | ||||
|     /// processing. | ||||
|     /// | ||||
|     /// [`Mutex::into_inner`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.into_inner | ||||
|     /// | ||||
|     /// # Examples | ||||
|     /// | ||||
|     /// ```ignore | ||||
|     /// use hyper::common::sync_wrapper::SyncWrapper; | ||||
|     /// | ||||
|     /// let mut wrapped = SyncWrapper::new(42); | ||||
|     /// assert_eq!(wrapped.into_inner(), 42); | ||||
|     /// ``` | ||||
|     #[allow(dead_code)] | ||||
|     pub(crate) fn into_inner(self) -> T { | ||||
|         self.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| // this is safe because the only operations permitted on this data structure require exclusive | ||||
| // access or ownership | ||||
| unsafe impl<T: Send> Sync for SyncWrapper<T> {} | ||||
							
								
								
									
										86
									
								
								src/error.rs
									
									
									
									
									
								
							
							
						
						
									
										86
									
								
								src/error.rs
									
									
									
									
									
								
							| @@ -34,21 +34,14 @@ pub(super) enum Kind { | ||||
|     /// An `io::Error` that occurred while trying to read or write to a network stream. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     Io, | ||||
|     /// Error occurred while connecting. | ||||
|     #[allow(unused)] | ||||
|     Connect, | ||||
|     /// Error creating a TcpListener. | ||||
|     #[cfg(all(feature = "tcp", feature = "server"))] | ||||
|     Listen, | ||||
|     /// Error accepting on an Incoming stream. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "server")] | ||||
|     Accept, | ||||
|     /// User took too long to send headers | ||||
|     #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] | ||||
|     HeaderTimeout, | ||||
|     /// Error while reading a body from connection. | ||||
|     #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     Body, | ||||
|     /// Error while writing a body to connection. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| @@ -96,10 +89,6 @@ pub(super) enum User { | ||||
|     Body, | ||||
|     /// The user aborted writing of the outgoing body. | ||||
|     BodyWriteAborted, | ||||
|     /// Error calling user's MakeService. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "server")] | ||||
|     MakeService, | ||||
|     /// Error from future of user's Service. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     Service, | ||||
| @@ -109,22 +98,10 @@ pub(super) enum User { | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "server")] | ||||
|     UnexpectedHeader, | ||||
|     /// User tried to create a Request with bad version. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     UnsupportedVersion, | ||||
|     /// User tried to create a CONNECT Request with the Client. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     UnsupportedRequestMethod, | ||||
|     /// User tried to respond with a 1xx (not 101) response code. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg(feature = "server")] | ||||
|     UnsupportedStatusCode, | ||||
|     /// User tried to send a Request with Client with non-absolute URI. | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     AbsoluteUriRequired, | ||||
|  | ||||
|     /// User tried polling for an upgrade that doesn't exist. | ||||
|     NoUpgrade, | ||||
| @@ -181,11 +158,6 @@ impl Error { | ||||
|         matches!(self.inner.kind, Kind::ChannelClosed) | ||||
|     } | ||||
|  | ||||
|     /// Returns true if this was an error from `Connect`. | ||||
|     pub fn is_connect(&self) -> bool { | ||||
|         matches!(self.inner.kind, Kind::Connect) | ||||
|     } | ||||
|  | ||||
|     /// Returns true if the connection closed before a message could complete. | ||||
|     pub fn is_incomplete_message(&self) -> bool { | ||||
|         matches!(self.inner.kind, Kind::IncompleteMessage) | ||||
| @@ -278,23 +250,11 @@ impl Error { | ||||
|         Error::new(Kind::Listen).with(cause) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "server")] | ||||
|     pub(super) fn new_accept<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::Accept).with(cause) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     pub(super) fn new_connect<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::Connect).with(cause) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn new_closed() -> Error { | ||||
|         Error::new(Kind::ChannelClosed) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new(Kind::Body).with(cause) | ||||
|     } | ||||
| @@ -323,30 +283,12 @@ impl Error { | ||||
|         Error::new(Kind::HeaderTimeout) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     pub(super) fn new_user_unsupported_version() -> Error { | ||||
|         Error::new_user(User::UnsupportedVersion) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     pub(super) fn new_user_unsupported_request_method() -> Error { | ||||
|         Error::new_user(User::UnsupportedRequestMethod) | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg(feature = "server")] | ||||
|     pub(super) fn new_user_unsupported_status_code() -> Error { | ||||
|         Error::new_user(User::UnsupportedStatusCode) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "client")] | ||||
|     pub(super) fn new_user_absolute_uri_required() -> Error { | ||||
|         Error::new_user(User::AbsoluteUriRequired) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn new_user_no_upgrade() -> Error { | ||||
|         Error::new_user(User::NoUpgrade) | ||||
|     } | ||||
| @@ -356,12 +298,6 @@ impl Error { | ||||
|         Error::new_user(User::ManualUpgrade) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[cfg(feature = "server")] | ||||
|     pub(super) fn new_user_make_service<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new_user(User::MakeService).with(cause) | ||||
|     } | ||||
|  | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error { | ||||
|         Error::new_user(User::Service).with(cause) | ||||
| @@ -431,16 +367,12 @@ impl Error { | ||||
|             #[cfg(feature = "http1")] | ||||
|             Kind::UnexpectedMessage => "received unexpected message from connection", | ||||
|             Kind::ChannelClosed => "channel closed", | ||||
|             Kind::Connect => "error trying to connect", | ||||
|             Kind::Canceled => "operation was canceled", | ||||
|             #[cfg(all(feature = "server", feature = "tcp"))] | ||||
|             Kind::Listen => "error creating server listener", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "server")] | ||||
|             Kind::Accept => "error accepting connection", | ||||
|             #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] | ||||
|             Kind::HeaderTimeout => "read header from client timeout", | ||||
|             #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             Kind::Body => "error reading a body from connection", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             Kind::BodyWrite => "error writing a body to connection", | ||||
| @@ -455,27 +387,15 @@ impl Error { | ||||
|             Kind::User(User::Body) => "error from user's HttpBody stream", | ||||
|             Kind::User(User::BodyWriteAborted) => "user body write aborted", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "server")] | ||||
|             Kind::User(User::MakeService) => "error from user's MakeService", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             Kind::User(User::Service) => "error from user's Service", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "server")] | ||||
|             Kind::User(User::UnexpectedHeader) => "user sent unexpected header", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "client")] | ||||
|             Kind::User(User::UnsupportedVersion) => "request has unsupported HTTP version", | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "client")] | ||||
|             Kind::User(User::UnsupportedRequestMethod) => "request has unsupported HTTP method", | ||||
|             #[cfg(feature = "http1")] | ||||
|             #[cfg(feature = "server")] | ||||
|             Kind::User(User::UnsupportedStatusCode) => { | ||||
|                 "response has 1xx status code, not supported by server" | ||||
|             } | ||||
|             #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|             #[cfg(feature = "client")] | ||||
|             Kind::User(User::AbsoluteUriRequired) => "client requires absolute-form URIs", | ||||
|             Kind::User(User::NoUpgrade) => "no upgrade available", | ||||
|             #[cfg(feature = "http1")] | ||||
|             Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use", | ||||
|   | ||||
							
								
								
									
										107
									
								
								src/ext.rs
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								src/ext.rs
									
									
									
									
									
								
							| @@ -1,12 +1,21 @@ | ||||
| //! HTTP extensions. | ||||
|  | ||||
| use bytes::Bytes; | ||||
| #[cfg(any(feature = "http1", feature = "ffi"))] | ||||
| use http::header::HeaderName; | ||||
| #[cfg(feature = "http1")] | ||||
| use http::header::{HeaderName, IntoHeaderName, ValueIter}; | ||||
| use http::header::{IntoHeaderName, ValueIter}; | ||||
| use http::HeaderMap; | ||||
| #[cfg(feature = "ffi")] | ||||
| use std::collections::HashMap; | ||||
| #[cfg(feature = "http2")] | ||||
| use std::fmt; | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "ffi"))] | ||||
| mod h1_reason_phrase; | ||||
| #[cfg(any(feature = "http1", feature = "ffi"))] | ||||
| pub use h1_reason_phrase::ReasonPhrase; | ||||
|  | ||||
| #[cfg(feature = "http2")] | ||||
| /// Represents the `:protocol` pseudo-header used by | ||||
| /// the [Extended CONNECT Protocol]. | ||||
| @@ -120,3 +129,99 @@ impl HeaderCaseMap { | ||||
|         self.0.append(name, orig); | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "ffi")] | ||||
| #[derive(Clone, Debug)] | ||||
| /// Hashmap<Headername, numheaders with that name> | ||||
| pub(crate) struct OriginalHeaderOrder { | ||||
|     /// Stores how many entries a Headername maps to. This is used | ||||
|     /// for accounting. | ||||
|     num_entries: HashMap<HeaderName, usize>, | ||||
|     /// Stores the ordering of the headers. ex: `vec[i] = (headerName, idx)`, | ||||
|     /// The vector is ordered such that the ith element | ||||
|     /// represents the ith header that came in off the line. | ||||
|     /// The `HeaderName` and `idx` are then used elsewhere to index into | ||||
|     /// the multi map that stores the header values. | ||||
|     entry_order: Vec<(HeaderName, usize)>, | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "http1", feature = "ffi"))] | ||||
| impl OriginalHeaderOrder { | ||||
|     pub(crate) fn default() -> Self { | ||||
|         OriginalHeaderOrder { | ||||
|             num_entries: HashMap::new(), | ||||
|             entry_order: Vec::new(), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn insert(&mut self, name: HeaderName) { | ||||
|         if !self.num_entries.contains_key(&name) { | ||||
|             let idx = 0; | ||||
|             self.num_entries.insert(name.clone(), 1); | ||||
|             self.entry_order.push((name, idx)); | ||||
|         } | ||||
|         // Replacing an already existing element does not | ||||
|         // change ordering, so we only care if its the first | ||||
|         // header name encountered | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn append<N>(&mut self, name: N) | ||||
|     where | ||||
|         N: IntoHeaderName + Into<HeaderName> + Clone, | ||||
|     { | ||||
|         let name: HeaderName = name.into(); | ||||
|         let idx; | ||||
|         if self.num_entries.contains_key(&name) { | ||||
|             idx = self.num_entries[&name]; | ||||
|             *self.num_entries.get_mut(&name).unwrap() += 1; | ||||
|         } else { | ||||
|             idx = 0; | ||||
|             self.num_entries.insert(name.clone(), 1); | ||||
|         } | ||||
|         self.entry_order.push((name, idx)); | ||||
|     } | ||||
|  | ||||
|     // No doc test is run here because `RUSTFLAGS='--cfg hyper_unstable_ffi'` | ||||
|     // is needed to compile. Once ffi is stablized `no_run` should be removed | ||||
|     // here. | ||||
|     /// This returns an iterator that provides header names and indexes | ||||
|     /// in the original order received. | ||||
|     /// | ||||
|     /// # Examples | ||||
|     /// ```no_run | ||||
|     /// use hyper::ext::OriginalHeaderOrder; | ||||
|     /// use hyper::header::{HeaderName, HeaderValue, HeaderMap}; | ||||
|     /// | ||||
|     /// let mut h_order = OriginalHeaderOrder::default(); | ||||
|     /// let mut h_map = Headermap::new(); | ||||
|     /// | ||||
|     /// let name1 = b"Set-CookiE"; | ||||
|     /// let value1 = b"a=b"; | ||||
|     /// h_map.append(name1); | ||||
|     /// h_order.append(name1); | ||||
|     /// | ||||
|     /// let name2 = b"Content-Encoding"; | ||||
|     /// let value2 = b"gzip"; | ||||
|     /// h_map.append(name2, value2); | ||||
|     /// h_order.append(name2); | ||||
|     /// | ||||
|     /// let name3 = b"SET-COOKIE"; | ||||
|     /// let value3 = b"c=d"; | ||||
|     /// h_map.append(name3, value3); | ||||
|     /// h_order.append(name3) | ||||
|     /// | ||||
|     /// let mut iter = h_order.get_in_order() | ||||
|     /// | ||||
|     /// let (name, idx) = iter.next(); | ||||
|     /// assert_eq!(b"a=b", h_map.get_all(name).nth(idx).unwrap()); | ||||
|     /// | ||||
|     /// let (name, idx) = iter.next(); | ||||
|     /// assert_eq!(b"gzip", h_map.get_all(name).nth(idx).unwrap()); | ||||
|     /// | ||||
|     /// let (name, idx) = iter.next(); | ||||
|     /// assert_eq!(b"c=d", h_map.get_all(name).nth(idx).unwrap()); | ||||
|     /// ``` | ||||
|     pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> { | ||||
|         self.entry_order.iter() | ||||
|     } | ||||
| } | ||||
|   | ||||
							
								
								
									
										221
									
								
								src/ext/h1_reason_phrase.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										221
									
								
								src/ext/h1_reason_phrase.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,221 @@ | ||||
| use std::convert::TryFrom; | ||||
|  | ||||
| use bytes::Bytes; | ||||
|  | ||||
| /// A reason phrase in an HTTP/1 response. | ||||
| /// | ||||
| /// # Clients | ||||
| /// | ||||
| /// For clients, a `ReasonPhrase` will be present in the extensions of the `http::Response` returned | ||||
| /// for a request if the reason phrase is different from the canonical reason phrase for the | ||||
| /// response's status code. For example, if a server returns `HTTP/1.1 200 Awesome`, the | ||||
| /// `ReasonPhrase` will be present and contain `Awesome`, but if a server returns `HTTP/1.1 200 OK`, | ||||
| /// the response will not contain a `ReasonPhrase`. | ||||
| /// | ||||
| /// ```no_run | ||||
| /// # #[cfg(all(feature = "tcp", feature = "client", feature = "http1"))] | ||||
| /// # async fn fake_fetch() -> hyper::Result<()> { | ||||
| /// use hyper::{Client, Uri}; | ||||
| /// use hyper::ext::ReasonPhrase; | ||||
| /// | ||||
| /// let res = Client::new().get(Uri::from_static("http://example.com/non_canonical_reason")).await?; | ||||
| /// | ||||
| /// // Print out the non-canonical reason phrase, if it has one... | ||||
| /// if let Some(reason) = res.extensions().get::<ReasonPhrase>() { | ||||
| ///     println!("non-canonical reason: {}", std::str::from_utf8(reason.as_bytes()).unwrap()); | ||||
| /// } | ||||
| /// # Ok(()) | ||||
| /// # } | ||||
| /// ``` | ||||
| /// | ||||
| /// # Servers | ||||
| /// | ||||
| /// When a `ReasonPhrase` is present in the extensions of the `http::Response` written by a server, | ||||
| /// its contents will be written in place of the canonical reason phrase when responding via HTTP/1. | ||||
| #[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)] | ||||
| pub struct ReasonPhrase(Bytes); | ||||
|  | ||||
| impl ReasonPhrase { | ||||
|     /// Gets the reason phrase as bytes. | ||||
|     pub fn as_bytes(&self) -> &[u8] { | ||||
|         &self.0 | ||||
|     } | ||||
|  | ||||
|     /// Converts a static byte slice to a reason phrase. | ||||
|     pub fn from_static(reason: &'static [u8]) -> Self { | ||||
|         // TODO: this can be made const once MSRV is >= 1.57.0 | ||||
|         if find_invalid_byte(reason).is_some() { | ||||
|             panic!("invalid byte in static reason phrase"); | ||||
|         } | ||||
|         Self(Bytes::from_static(reason)) | ||||
|     } | ||||
|  | ||||
|     /// Converts a `Bytes` directly into a `ReasonPhrase` without validating. | ||||
|     /// | ||||
|     /// Use with care; invalid bytes in a reason phrase can cause serious security problems if | ||||
|     /// emitted in a response. | ||||
|     pub unsafe fn from_bytes_unchecked(reason: Bytes) -> Self { | ||||
|         Self(reason) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<&[u8]> for ReasonPhrase { | ||||
|     type Error = InvalidReasonPhrase; | ||||
|  | ||||
|     fn try_from(reason: &[u8]) -> Result<Self, Self::Error> { | ||||
|         if let Some(bad_byte) = find_invalid_byte(reason) { | ||||
|             Err(InvalidReasonPhrase { bad_byte }) | ||||
|         } else { | ||||
|             Ok(Self(Bytes::copy_from_slice(reason))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<Vec<u8>> for ReasonPhrase { | ||||
|     type Error = InvalidReasonPhrase; | ||||
|  | ||||
|     fn try_from(reason: Vec<u8>) -> Result<Self, Self::Error> { | ||||
|         if let Some(bad_byte) = find_invalid_byte(&reason) { | ||||
|             Err(InvalidReasonPhrase { bad_byte }) | ||||
|         } else { | ||||
|             Ok(Self(Bytes::from(reason))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<String> for ReasonPhrase { | ||||
|     type Error = InvalidReasonPhrase; | ||||
|  | ||||
|     fn try_from(reason: String) -> Result<Self, Self::Error> { | ||||
|         if let Some(bad_byte) = find_invalid_byte(reason.as_bytes()) { | ||||
|             Err(InvalidReasonPhrase { bad_byte }) | ||||
|         } else { | ||||
|             Ok(Self(Bytes::from(reason))) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl TryFrom<Bytes> for ReasonPhrase { | ||||
|     type Error = InvalidReasonPhrase; | ||||
|  | ||||
|     fn try_from(reason: Bytes) -> Result<Self, Self::Error> { | ||||
|         if let Some(bad_byte) = find_invalid_byte(&reason) { | ||||
|             Err(InvalidReasonPhrase { bad_byte }) | ||||
|         } else { | ||||
|             Ok(Self(reason)) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Into<Bytes> for ReasonPhrase { | ||||
|     fn into(self) -> Bytes { | ||||
|         self.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl AsRef<[u8]> for ReasonPhrase { | ||||
|     fn as_ref(&self) -> &[u8] { | ||||
|         &self.0 | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// Error indicating an invalid byte when constructing a `ReasonPhrase`. | ||||
| /// | ||||
| /// See [the spec][spec] for details on allowed bytes. | ||||
| /// | ||||
| /// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7 | ||||
| #[derive(Debug)] | ||||
| pub struct InvalidReasonPhrase { | ||||
|     bad_byte: u8, | ||||
| } | ||||
|  | ||||
| impl std::fmt::Display for InvalidReasonPhrase { | ||||
|     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { | ||||
|         write!(f, "Invalid byte in reason phrase: {}", self.bad_byte) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl std::error::Error for InvalidReasonPhrase {} | ||||
|  | ||||
| const fn is_valid_byte(b: u8) -> bool { | ||||
|     // See https://www.rfc-editor.org/rfc/rfc5234.html#appendix-B.1 | ||||
|     const fn is_vchar(b: u8) -> bool { | ||||
|         0x21 <= b && b <= 0x7E | ||||
|     } | ||||
|  | ||||
|     // See https://httpwg.org/http-core/draft-ietf-httpbis-semantics-latest.html#fields.values | ||||
|     // | ||||
|     // The 0xFF comparison is technically redundant, but it matches the text of the spec more | ||||
|     // clearly and will be optimized away. | ||||
|     #[allow(unused_comparisons)] | ||||
|     const fn is_obs_text(b: u8) -> bool { | ||||
|         0x80 <= b && b <= 0xFF | ||||
|     } | ||||
|  | ||||
|     // See https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7 | ||||
|     b == b'\t' || b == b' ' || is_vchar(b) || is_obs_text(b) | ||||
| } | ||||
|  | ||||
| const fn find_invalid_byte(bytes: &[u8]) -> Option<u8> { | ||||
|     let mut i = 0; | ||||
|     while i < bytes.len() { | ||||
|         let b = bytes[i]; | ||||
|         if !is_valid_byte(b) { | ||||
|             return Some(b); | ||||
|         } | ||||
|         i += 1; | ||||
|     } | ||||
|     None | ||||
| } | ||||
|  | ||||
| #[cfg(test)] | ||||
| mod tests { | ||||
|     use super::*; | ||||
|  | ||||
|     #[test] | ||||
|     fn basic_valid() { | ||||
|         const PHRASE: &'static [u8] = b"OK"; | ||||
|         assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE); | ||||
|         assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn empty_valid() { | ||||
|         const PHRASE: &'static [u8] = b""; | ||||
|         assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE); | ||||
|         assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn obs_text_valid() { | ||||
|         const PHRASE: &'static [u8] = b"hyp\xe9r"; | ||||
|         assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE); | ||||
|         assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE); | ||||
|     } | ||||
|  | ||||
|     const NEWLINE_PHRASE: &'static [u8] = b"hyp\ner"; | ||||
|  | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn newline_invalid_panic() { | ||||
|         ReasonPhrase::from_static(NEWLINE_PHRASE); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn newline_invalid_err() { | ||||
|         assert!(ReasonPhrase::try_from(NEWLINE_PHRASE).is_err()); | ||||
|     } | ||||
|  | ||||
|     const CR_PHRASE: &'static [u8] = b"hyp\rer"; | ||||
|  | ||||
|     #[test] | ||||
|     #[should_panic] | ||||
|     fn cr_invalid_panic() { | ||||
|         ReasonPhrase::from_static(CR_PHRASE); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn cr_invalid_err() { | ||||
|         assert!(ReasonPhrase::try_from(CR_PHRASE).is_err()); | ||||
|     } | ||||
| } | ||||
| @@ -93,8 +93,7 @@ unsafe impl AsTaskType for hyper_clientconn { | ||||
| ffi_fn! { | ||||
|     /// Creates a new set of HTTP clientconn options to be used in a handshake. | ||||
|     fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options { | ||||
|         let mut builder = conn::Builder::new(); | ||||
|         builder.http1_preserve_header_case(true); | ||||
|         let builder = conn::Builder::new(); | ||||
|  | ||||
|         Box::into_raw(Box::new(hyper_clientconn_options { | ||||
|             builder, | ||||
| @@ -103,6 +102,26 @@ ffi_fn! { | ||||
|     } ?= std::ptr::null_mut() | ||||
| } | ||||
|  | ||||
| ffi_fn! { | ||||
|     /// Set the whether or not header case is preserved. | ||||
|     /// | ||||
|     /// Pass `0` to allow lowercase normalization (default), `1` to retain original case. | ||||
|     fn hyper_clientconn_options_set_preserve_header_case(opts: *mut hyper_clientconn_options, enabled: c_int) { | ||||
|         let opts = non_null! { &mut *opts ?= () }; | ||||
|         opts.builder.http1_preserve_header_case(enabled != 0); | ||||
|     } | ||||
| } | ||||
|  | ||||
| ffi_fn! { | ||||
|     /// Set the whether or not header order is preserved. | ||||
|     /// | ||||
|     /// Pass `0` to allow reordering (default), `1` to retain original ordering. | ||||
|     fn hyper_clientconn_options_set_preserve_header_order(opts: *mut hyper_clientconn_options, enabled: c_int) { | ||||
|         let opts = non_null! { &mut *opts ?= () }; | ||||
|         opts.builder.http1_preserve_header_order(enabled != 0); | ||||
|     } | ||||
| } | ||||
|  | ||||
| ffi_fn! { | ||||
|     /// Free a `hyper_clientconn_options *`. | ||||
|     fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) { | ||||
| @@ -160,3 +179,16 @@ ffi_fn! { | ||||
|         hyper_code::HYPERE_OK | ||||
|     } | ||||
| } | ||||
|  | ||||
| ffi_fn! { | ||||
|     /// Set whether HTTP/1 connections will accept obsolete line folding for header values. | ||||
|     /// Newline codepoints (\r and \n) will be transformed to spaces when parsing. | ||||
|     /// | ||||
|     /// Pass `0` to disable, `1` to enable. | ||||
|     /// | ||||
|     fn hyper_clientconn_options_http1_allow_multiline_headers(opts: *mut hyper_clientconn_options, enabled: c_int) -> hyper_code { | ||||
|         let opts = non_null! { &mut *opts ?= hyper_code::HYPERE_INVALID_ARG }; | ||||
|         opts.builder.http1_allow_obsolete_multiline_headers_in_responses(enabled != 0); | ||||
|         hyper_code::HYPERE_OK | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,7 +6,7 @@ use super::body::{hyper_body, hyper_buf}; | ||||
| use super::error::hyper_code; | ||||
| use super::task::{hyper_task_return_type, AsTaskType}; | ||||
| use super::{UserDataPointer, HYPER_ITER_CONTINUE}; | ||||
| use crate::ext::HeaderCaseMap; | ||||
| use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase}; | ||||
| use crate::header::{HeaderName, HeaderValue}; | ||||
| use crate::{Body, HeaderMap, Method, Request, Response, Uri}; | ||||
|  | ||||
| @@ -22,11 +22,9 @@ pub struct hyper_response(pub(super) Response<Body>); | ||||
| pub struct hyper_headers { | ||||
|     pub(super) headers: HeaderMap, | ||||
|     orig_casing: HeaderCaseMap, | ||||
|     orig_order: OriginalHeaderOrder, | ||||
| } | ||||
|  | ||||
| #[derive(Debug)] | ||||
| pub(crate) struct ReasonPhrase(pub(crate) Bytes); | ||||
|  | ||||
| pub(crate) struct RawHeaders(pub(crate) hyper_buf); | ||||
|  | ||||
| pub(crate) struct OnInformational { | ||||
| @@ -233,6 +231,7 @@ impl hyper_request { | ||||
|         if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() { | ||||
|             *self.0.headers_mut() = headers.headers; | ||||
|             self.0.extensions_mut().insert(headers.orig_casing); | ||||
|             self.0.extensions_mut().insert(headers.orig_order); | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -348,9 +347,14 @@ impl hyper_response { | ||||
|             .extensions_mut() | ||||
|             .remove::<HeaderCaseMap>() | ||||
|             .unwrap_or_else(HeaderCaseMap::default); | ||||
|         let orig_order = resp | ||||
|             .extensions_mut() | ||||
|             .remove::<OriginalHeaderOrder>() | ||||
|             .unwrap_or_else(OriginalHeaderOrder::default); | ||||
|         resp.extensions_mut().insert(hyper_headers { | ||||
|             headers, | ||||
|             orig_casing, | ||||
|             orig_order, | ||||
|         }); | ||||
|  | ||||
|         hyper_response(resp) | ||||
| @@ -358,7 +362,7 @@ impl hyper_response { | ||||
|  | ||||
|     fn reason_phrase(&self) -> &[u8] { | ||||
|         if let Some(reason) = self.0.extensions().get::<ReasonPhrase>() { | ||||
|             return &reason.0; | ||||
|             return reason.as_bytes(); | ||||
|         } | ||||
|  | ||||
|         if let Some(reason) = self.0.status().canonical_reason() { | ||||
| @@ -404,26 +408,54 @@ ffi_fn! { | ||||
|         // and for each one, try to pair the originally cased name with the value. | ||||
|         // | ||||
|         // TODO: consider adding http::HeaderMap::entries() iterator | ||||
|         for name in headers.headers.keys() { | ||||
|             let mut names = headers.orig_casing.get_all(name); | ||||
|  | ||||
|             for value in headers.headers.get_all(name) { | ||||
|                 let (name_ptr, name_len) = if let Some(orig_name) = names.next() { | ||||
|         let mut ordered_iter =  headers.orig_order.get_in_order().peekable(); | ||||
|         if ordered_iter.peek().is_some() { | ||||
|             for (name, idx) in ordered_iter { | ||||
|                 let (name_ptr, name_len) = if let Some(orig_name) = headers.orig_casing.get_all(name).nth(*idx) { | ||||
|                     (orig_name.as_ref().as_ptr(), orig_name.as_ref().len()) | ||||
|                 } else { | ||||
|                     ( | ||||
|                         name.as_str().as_bytes().as_ptr(), | ||||
|                         name.as_str().as_bytes().len(), | ||||
|                     name.as_str().as_bytes().as_ptr(), | ||||
|                     name.as_str().as_bytes().len(), | ||||
|                     ) | ||||
|                 }; | ||||
|  | ||||
|                 let val_ptr = value.as_bytes().as_ptr(); | ||||
|                 let val_len = value.as_bytes().len(); | ||||
|                 let val_ptr; | ||||
|                 let val_len; | ||||
|                 if let Some(value) = headers.headers.get_all(name).iter().nth(*idx) { | ||||
|                     val_ptr = value.as_bytes().as_ptr(); | ||||
|                     val_len = value.as_bytes().len(); | ||||
|                 } else { | ||||
|                     // Stop iterating, something has gone wrong. | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         } else { | ||||
|             for name in headers.headers.keys() { | ||||
|                 let mut names = headers.orig_casing.get_all(name); | ||||
|  | ||||
|                 for value in headers.headers.get_all(name) { | ||||
|                     let (name_ptr, name_len) = if let Some(orig_name) = names.next() { | ||||
|                         (orig_name.as_ref().as_ptr(), orig_name.as_ref().len()) | ||||
|                     } else { | ||||
|                         ( | ||||
|                             name.as_str().as_bytes().as_ptr(), | ||||
|                             name.as_str().as_bytes().len(), | ||||
|                         ) | ||||
|                     }; | ||||
|  | ||||
|                     let val_ptr = value.as_bytes().as_ptr(); | ||||
|                     let val_len = value.as_bytes().len(); | ||||
|  | ||||
|                     if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) { | ||||
|                         return; | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -437,7 +469,8 @@ ffi_fn! { | ||||
|         match unsafe { raw_name_value(name, name_len, value, value_len) } { | ||||
|             Ok((name, value, orig_name)) => { | ||||
|                 headers.headers.insert(&name, value); | ||||
|                 headers.orig_casing.insert(name, orig_name); | ||||
|                 headers.orig_casing.insert(name.clone(), orig_name.clone()); | ||||
|                 headers.orig_order.insert(name); | ||||
|                 hyper_code::HYPERE_OK | ||||
|             } | ||||
|             Err(code) => code, | ||||
| @@ -456,7 +489,8 @@ ffi_fn! { | ||||
|         match unsafe { raw_name_value(name, name_len, value, value_len) } { | ||||
|             Ok((name, value, orig_name)) => { | ||||
|                 headers.headers.append(&name, value); | ||||
|                 headers.orig_casing.append(name, orig_name); | ||||
|                 headers.orig_casing.append(&name, orig_name.clone()); | ||||
|                 headers.orig_order.append(name); | ||||
|                 hyper_code::HYPERE_OK | ||||
|             } | ||||
|             Err(code) => code, | ||||
| @@ -469,6 +503,7 @@ impl Default for hyper_headers { | ||||
|         Self { | ||||
|             headers: Default::default(), | ||||
|             orig_casing: HeaderCaseMap::default(), | ||||
|             orig_order: OriginalHeaderOrder::default(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -555,4 +590,68 @@ mod tests { | ||||
|             HYPER_ITER_CONTINUE | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(all(feature = "http1", feature = "ffi"))] | ||||
|     #[test] | ||||
|     fn test_headers_foreach_order_preserved() { | ||||
|         let mut headers = hyper_headers::default(); | ||||
|  | ||||
|         let name1 = b"Set-CookiE"; | ||||
|         let value1 = b"a=b"; | ||||
|         hyper_headers_add( | ||||
|             &mut headers, | ||||
|             name1.as_ptr(), | ||||
|             name1.len(), | ||||
|             value1.as_ptr(), | ||||
|             value1.len(), | ||||
|         ); | ||||
|  | ||||
|         let name2 = b"Content-Encoding"; | ||||
|         let value2 = b"gzip"; | ||||
|         hyper_headers_add( | ||||
|             &mut headers, | ||||
|             name2.as_ptr(), | ||||
|             name2.len(), | ||||
|             value2.as_ptr(), | ||||
|             value2.len(), | ||||
|         ); | ||||
|  | ||||
|         let name3 = b"SET-COOKIE"; | ||||
|         let value3 = b"c=d"; | ||||
|         hyper_headers_add( | ||||
|             &mut headers, | ||||
|             name3.as_ptr(), | ||||
|             name3.len(), | ||||
|             value3.as_ptr(), | ||||
|             value3.len(), | ||||
|         ); | ||||
|  | ||||
|         let mut vec = Vec::<u8>::new(); | ||||
|         hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void); | ||||
|  | ||||
|         println!("{}", std::str::from_utf8(&vec).unwrap()); | ||||
|         assert_eq!( | ||||
|             vec, | ||||
|             b"Set-CookiE: a=b\r\nContent-Encoding: gzip\r\nSET-COOKIE: c=d\r\n" | ||||
|         ); | ||||
|  | ||||
|         extern "C" fn concat( | ||||
|             vec: *mut c_void, | ||||
|             name: *const u8, | ||||
|             name_len: usize, | ||||
|             value: *const u8, | ||||
|             value_len: usize, | ||||
|         ) -> c_int { | ||||
|             unsafe { | ||||
|                 let vec = &mut *(vec as *mut Vec<u8>); | ||||
|                 let name = std::slice::from_raw_parts(name, name_len); | ||||
|                 let value = std::slice::from_raw_parts(value, value_len); | ||||
|                 vec.extend(name); | ||||
|                 vec.extend(b": "); | ||||
|                 vec.extend(value); | ||||
|                 vec.extend(b"\r\n"); | ||||
|             } | ||||
|             HYPER_ITER_CONTINUE | ||||
|         } | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -32,7 +32,7 @@ pub struct hyper_executor { | ||||
|     /// The executor of all task futures. | ||||
|     /// | ||||
|     /// There should never be contention on the mutex, as it is only locked | ||||
|     /// to drive the futures. However, we cannot gaurantee proper usage from | ||||
|     /// to drive the futures. However, we cannot guarantee proper usage from | ||||
|     /// `hyper_executor_poll()`, which in C could potentially be called inside | ||||
|     /// one of the stored futures. The mutex isn't re-entrant, so doing so | ||||
|     /// would result in a deadlock, but that's better than data corruption. | ||||
|   | ||||
| @@ -2,7 +2,7 @@ | ||||
| #![deny(missing_debug_implementations)] | ||||
| #![cfg_attr(test, deny(rust_2018_idioms))] | ||||
| #![cfg_attr(all(test, feature = "full"), deny(unreachable_pub))] | ||||
| #![cfg_attr(test, deny(warnings))] | ||||
| #![cfg_attr(all(test, feature = "full"), deny(warnings))] | ||||
| #![cfg_attr(all(test, feature = "nightly"), feature(test))] | ||||
| #![cfg_attr(docsrs, feature(doc_cfg))] | ||||
|  | ||||
| @@ -51,8 +51,6 @@ | ||||
| //! - `server`: Enables the HTTP `server`. | ||||
| //! - `runtime`: Enables convenient integration with `tokio`, providing | ||||
| //!   connectors and acceptors for TCP, and a default executor. | ||||
| //! - `tcp`: Enables convenient implementations over TCP (using tokio). | ||||
| //! - `stream`: Provides `futures::Stream` capabilities. | ||||
| //! | ||||
| //! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section | ||||
|  | ||||
| @@ -95,15 +93,10 @@ cfg_feature! { | ||||
|     #![feature = "client"] | ||||
|  | ||||
|     pub mod client; | ||||
|     #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|     #[doc(no_inline)] | ||||
|     pub use crate::client::Client; | ||||
| } | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![feature = "server"] | ||||
|  | ||||
|     pub mod server; | ||||
|     #[doc(no_inline)] | ||||
|     pub use crate::server::Server; | ||||
| } | ||||
|   | ||||
| @@ -58,6 +58,8 @@ where | ||||
|                 #[cfg(all(feature = "server", feature = "runtime"))] | ||||
|                 h1_header_read_timeout_running: false, | ||||
|                 preserve_header_case: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 preserve_header_order: false, | ||||
|                 title_case_headers: false, | ||||
|                 h09_responses: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
| @@ -111,6 +113,11 @@ where | ||||
|         self.state.preserve_header_case = true; | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "ffi")] | ||||
|     pub(crate) fn set_preserve_header_order(&mut self) { | ||||
|         self.state.preserve_header_order = true; | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "client")] | ||||
|     pub(crate) fn set_h09_responses(&mut self) { | ||||
|         self.state.h09_responses = true; | ||||
| @@ -148,19 +155,15 @@ where | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn can_read_head(&self) -> bool { | ||||
|         match self.state.reading { | ||||
|             Reading::Init => { | ||||
|                 if T::should_read_first() { | ||||
|                     true | ||||
|                 } else { | ||||
|                     match self.state.writing { | ||||
|                         Writing::Init => false, | ||||
|                         _ => true, | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|             _ => false, | ||||
|         if !matches!(self.state.reading, Reading::Init) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         if T::should_read_first() { | ||||
|             return true; | ||||
|         } | ||||
|  | ||||
|         !matches!(self.state.writing, Writing::Init) | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn can_read_body(&self) -> bool { | ||||
| @@ -200,6 +203,8 @@ where | ||||
|                 #[cfg(all(feature = "server", feature = "runtime"))] | ||||
|                 h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running, | ||||
|                 preserve_header_case: self.state.preserve_header_case, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 preserve_header_order: self.state.preserve_header_order, | ||||
|                 h09_responses: self.state.h09_responses, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 on_informational: &mut self.state.on_informational, | ||||
| @@ -358,10 +363,10 @@ where | ||||
|     } | ||||
|  | ||||
|     fn is_mid_message(&self) -> bool { | ||||
|         match (&self.state.reading, &self.state.writing) { | ||||
|             (&Reading::Init, &Writing::Init) => false, | ||||
|             _ => true, | ||||
|         } | ||||
|         !matches!( | ||||
|             (&self.state.reading, &self.state.writing), | ||||
|             (&Reading::Init, &Writing::Init) | ||||
|         ) | ||||
|     } | ||||
|  | ||||
|     // This will check to make sure the io object read is empty. | ||||
| @@ -484,11 +489,10 @@ where | ||||
|     } | ||||
|  | ||||
|     pub(crate) fn can_write_head(&self) -> bool { | ||||
|         if !T::should_read_first() { | ||||
|             if let Reading::Closed = self.state.reading { | ||||
|                 return false; | ||||
|             } | ||||
|         if !T::should_read_first() && matches!(self.state.reading, Reading::Closed) { | ||||
|             return false; | ||||
|         } | ||||
|  | ||||
|         match self.state.writing { | ||||
|             Writing::Init => self.io.can_headers_buf(), | ||||
|             _ => false, | ||||
| @@ -582,7 +586,7 @@ where | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // Fix keep-alives when Connection: keep-alive header is not present | ||||
|     // Fix keep-alive when Connection: keep-alive header is not present | ||||
|     fn fix_keep_alive(&mut self, head: &mut MessageHead<T::Outgoing>) { | ||||
|         let outgoing_is_keep_alive = head | ||||
|             .headers | ||||
| @@ -632,15 +636,15 @@ where | ||||
|             Writing::Body(ref mut encoder) => { | ||||
|                 self.io.buffer(encoder.encode(chunk)); | ||||
|  | ||||
|                 if encoder.is_eof() { | ||||
|                     if encoder.is_last() { | ||||
|                         Writing::Closed | ||||
|                     } else { | ||||
|                         Writing::KeepAlive | ||||
|                     } | ||||
|                 } else { | ||||
|                 if !encoder.is_eof() { | ||||
|                     return; | ||||
|                 } | ||||
|  | ||||
|                 if encoder.is_last() { | ||||
|                     Writing::Closed | ||||
|                 } else { | ||||
|                     Writing::KeepAlive | ||||
|                 } | ||||
|             } | ||||
|             _ => unreachable!("write_body invalid state: {:?}", self.state.writing), | ||||
|         }; | ||||
| @@ -671,32 +675,31 @@ where | ||||
|     pub(crate) fn end_body(&mut self) -> crate::Result<()> { | ||||
|         debug_assert!(self.can_write_body()); | ||||
|  | ||||
|         let mut res = Ok(()); | ||||
|         let state = match self.state.writing { | ||||
|             Writing::Body(ref mut encoder) => { | ||||
|                 // end of stream, that means we should try to eof | ||||
|                 match encoder.end() { | ||||
|                     Ok(end) => { | ||||
|                         if let Some(end) = end { | ||||
|                             self.io.buffer(end); | ||||
|                         } | ||||
|                         if encoder.is_last() || encoder.is_close_delimited() { | ||||
|                             Writing::Closed | ||||
|                         } else { | ||||
|                             Writing::KeepAlive | ||||
|                         } | ||||
|                     } | ||||
|                     Err(not_eof) => { | ||||
|                         res = Err(crate::Error::new_body_write_aborted().with(not_eof)); | ||||
|                         Writing::Closed | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         let encoder = match self.state.writing { | ||||
|             Writing::Body(ref mut enc) => enc, | ||||
|             _ => return Ok(()), | ||||
|         }; | ||||
|  | ||||
|         self.state.writing = state; | ||||
|         res | ||||
|         // end of stream, that means we should try to eof | ||||
|         match encoder.end() { | ||||
|             Ok(end) => { | ||||
|                 if let Some(end) = end { | ||||
|                     self.io.buffer(end); | ||||
|                 } | ||||
|  | ||||
|                 self.state.writing = if encoder.is_last() || encoder.is_close_delimited() { | ||||
|                     Writing::Closed | ||||
|                 } else { | ||||
|                     Writing::KeepAlive | ||||
|                 }; | ||||
|  | ||||
|                 Ok(()) | ||||
|             } | ||||
|             Err(not_eof) => { | ||||
|                 self.state.writing = Writing::Closed; | ||||
|                 Err(crate::Error::new_body_write_aborted().with(not_eof)) | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // When we get a parse error, depending on what side we are, we might be able | ||||
| @@ -749,10 +752,7 @@ where | ||||
|  | ||||
|         // If still in Reading::Body, just give up | ||||
|         match self.state.reading { | ||||
|             Reading::Init | Reading::KeepAlive => { | ||||
|                 trace!("body drained"); | ||||
|                 return; | ||||
|             } | ||||
|             Reading::Init | Reading::KeepAlive => trace!("body drained"), | ||||
|             _ => self.close_read(), | ||||
|         } | ||||
|     } | ||||
| @@ -824,6 +824,8 @@ struct State { | ||||
|     #[cfg(all(feature = "server", feature = "runtime"))] | ||||
|     h1_header_read_timeout_running: bool, | ||||
|     preserve_header_case: bool, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     preserve_header_order: bool, | ||||
|     title_case_headers: bool, | ||||
|     h09_responses: bool, | ||||
|     /// If set, called with each 1xx informational response received for | ||||
| @@ -1001,43 +1003,35 @@ impl State { | ||||
|  | ||||
|         self.method = None; | ||||
|         self.keep_alive.idle(); | ||||
|         if self.is_idle() { | ||||
|             self.reading = Reading::Init; | ||||
|             self.writing = Writing::Init; | ||||
|  | ||||
|             // !T::should_read_first() means Client. | ||||
|             // | ||||
|             // If Client connection has just gone idle, the Dispatcher | ||||
|             // should try the poll loop one more time, so as to poll the | ||||
|             // pending requests stream. | ||||
|             if !T::should_read_first() { | ||||
|                 self.notify_read = true; | ||||
|             } | ||||
|         } else { | ||||
|         if !self.is_idle() { | ||||
|             self.close(); | ||||
|             return; | ||||
|         } | ||||
|  | ||||
|         self.reading = Reading::Init; | ||||
|         self.writing = Writing::Init; | ||||
|  | ||||
|         // !T::should_read_first() means Client. | ||||
|         // | ||||
|         // If Client connection has just gone idle, the Dispatcher | ||||
|         // should try the poll loop one more time, so as to poll the | ||||
|         // pending requests stream. | ||||
|         if !T::should_read_first() { | ||||
|             self.notify_read = true; | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn is_idle(&self) -> bool { | ||||
|         if let KA::Idle = self.keep_alive.status() { | ||||
|             true | ||||
|         } else { | ||||
|             false | ||||
|         } | ||||
|         matches!(self.keep_alive.status(), KA::Idle) | ||||
|     } | ||||
|  | ||||
|     fn is_read_closed(&self) -> bool { | ||||
|         match self.reading { | ||||
|             Reading::Closed => true, | ||||
|             _ => false, | ||||
|         } | ||||
|         matches!(self.reading, Reading::Closed) | ||||
|     } | ||||
|  | ||||
|     fn is_write_closed(&self) -> bool { | ||||
|         match self.writing { | ||||
|             Writing::Closed => true, | ||||
|             _ => false, | ||||
|         } | ||||
|         matches!(self.writing, Writing::Closed) | ||||
|     } | ||||
|  | ||||
|     fn prepare_upgrade(&mut self) -> crate::upgrade::OnUpgrade { | ||||
|   | ||||
| @@ -1,17 +1,17 @@ | ||||
| use std::cmp; | ||||
| use std::fmt; | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use std::future::Future; | ||||
| use std::io::{self, IoSlice}; | ||||
| use std::marker::Unpin; | ||||
| use std::mem::MaybeUninit; | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use std::future::Future; | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use std::time::Duration; | ||||
|  | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use tokio::time::Instant; | ||||
| use bytes::{Buf, BufMut, Bytes, BytesMut}; | ||||
| use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use tokio::time::Instant; | ||||
| use tracing::{debug, trace}; | ||||
|  | ||||
| use super::{Http1Transaction, ParseContext, ParsedMessage}; | ||||
| @@ -194,6 +194,8 @@ where | ||||
|                     #[cfg(all(feature = "server", feature = "runtime"))] | ||||
|                     h1_header_read_timeout_running: parse_ctx.h1_header_read_timeout_running, | ||||
|                     preserve_header_case: parse_ctx.preserve_header_case, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: parse_ctx.preserve_header_order, | ||||
|                     h09_responses: parse_ctx.h09_responses, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: parse_ctx.on_informational, | ||||
| @@ -208,9 +210,13 @@ where | ||||
|                     { | ||||
|                         *parse_ctx.h1_header_read_timeout_running = false; | ||||
|  | ||||
|                         if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { | ||||
|                         if let Some(h1_header_read_timeout_fut) = | ||||
|                             parse_ctx.h1_header_read_timeout_fut | ||||
|                         { | ||||
|                             // Reset the timer in order to avoid woken up when the timeout finishes | ||||
|                             h1_header_read_timeout_fut.as_mut().reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60)); | ||||
|                             h1_header_read_timeout_fut | ||||
|                                 .as_mut() | ||||
|                                 .reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60)); | ||||
|                         } | ||||
|                     } | ||||
|                     return Poll::Ready(Ok(msg)); | ||||
| @@ -224,12 +230,14 @@ where | ||||
|  | ||||
|                     #[cfg(all(feature = "server", feature = "runtime"))] | ||||
|                     if *parse_ctx.h1_header_read_timeout_running { | ||||
|                         if let Some(h1_header_read_timeout_fut) = parse_ctx.h1_header_read_timeout_fut { | ||||
|                             if Pin::new( h1_header_read_timeout_fut).poll(cx).is_ready() { | ||||
|                         if let Some(h1_header_read_timeout_fut) = | ||||
|                             parse_ctx.h1_header_read_timeout_fut | ||||
|                         { | ||||
|                             if Pin::new(h1_header_read_timeout_fut).poll(cx).is_ready() { | ||||
|                                 *parse_ctx.h1_header_read_timeout_running = false; | ||||
|  | ||||
|                                 tracing::warn!("read header from client timeout"); | ||||
|                                 return Poll::Ready(Err(crate::Error::new_header_timeout())) | ||||
|                                 return Poll::Ready(Err(crate::Error::new_header_timeout())); | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
| @@ -727,10 +735,15 @@ mod tests { | ||||
|                 cached_headers: &mut None, | ||||
|                 req_method: &mut None, | ||||
|                 h1_parser_config: Default::default(), | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout: None, | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout_fut: &mut None, | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout_running: &mut false, | ||||
|                 preserve_header_case: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 preserve_header_order: false, | ||||
|                 h09_responses: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 on_informational: &mut None, | ||||
| @@ -894,9 +907,7 @@ mod tests { | ||||
|     async fn write_buf_flatten() { | ||||
|         let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|         let mock = Mock::new() | ||||
|             .write(b"hello world, it's hyper!") | ||||
|             .build(); | ||||
|         let mock = Mock::new().write(b"hello world, it's hyper!").build(); | ||||
|  | ||||
|         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||
|         buffered.write_buf.set_strategy(WriteStrategy::Flatten); | ||||
|   | ||||
| @@ -14,7 +14,7 @@ pub(crate) use self::conn::Conn; | ||||
| pub(crate) use self::decode::Decoder; | ||||
| pub(crate) use self::dispatch::Dispatcher; | ||||
| pub(crate) use self::encode::{EncodedBuf, Encoder}; | ||||
|  //TODO: move out of h1::io | ||||
| //TODO: move out of h1::io | ||||
| pub(crate) use self::io::MINIMUM_MAX_BUFFER_SIZE; | ||||
|  | ||||
| mod conn; | ||||
| @@ -24,7 +24,6 @@ mod encode; | ||||
| mod io; | ||||
| mod role; | ||||
|  | ||||
|  | ||||
| cfg_client! { | ||||
|     pub(crate) type ClientTransaction = role::Client; | ||||
| } | ||||
| @@ -84,6 +83,8 @@ pub(crate) struct ParseContext<'a> { | ||||
|     #[cfg(all(feature = "server", feature = "runtime"))] | ||||
|     h1_header_read_timeout_running: &'a mut bool, | ||||
|     preserve_header_case: bool, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     preserve_header_order: bool, | ||||
|     h09_responses: bool, | ||||
|     #[cfg(feature = "ffi")] | ||||
|     on_informational: &'a mut Option<crate::ffi::OnInformational>, | ||||
|   | ||||
| @@ -1,15 +1,14 @@ | ||||
| use std::fmt::{self, Write}; | ||||
| use std::mem::MaybeUninit; | ||||
|  | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use tokio::time::Instant; | ||||
| #[cfg(any(test, feature = "server", feature = "ffi"))] | ||||
| use bytes::Bytes; | ||||
| use bytes::BytesMut; | ||||
| #[cfg(feature = "server")] | ||||
| use http::header::ValueIter; | ||||
| use http::header::{self, Entry, HeaderName, HeaderValue}; | ||||
| use http::{HeaderMap, Method, StatusCode, Version}; | ||||
| #[cfg(all(feature = "server", feature = "runtime"))] | ||||
| use tokio::time::Instant; | ||||
| use tracing::{debug, error, trace, trace_span, warn}; | ||||
|  | ||||
| use crate::body::DecodedLength; | ||||
| @@ -17,6 +16,8 @@ use crate::body::DecodedLength; | ||||
| use crate::common::date; | ||||
| use crate::error::Parse; | ||||
| use crate::ext::HeaderCaseMap; | ||||
| #[cfg(feature = "ffi")] | ||||
| use crate::ext::OriginalHeaderOrder; | ||||
| use crate::headers; | ||||
| use crate::proto::h1::{ | ||||
|     Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage, | ||||
| @@ -78,7 +79,7 @@ where | ||||
|     if !*ctx.h1_header_read_timeout_running { | ||||
|         if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { | ||||
|             let deadline = Instant::now() + h1_header_read_timeout; | ||||
|  | ||||
|             *ctx.h1_header_read_timeout_running = true; | ||||
|             match ctx.h1_header_read_timeout_fut { | ||||
|                 Some(h1_header_read_timeout_fut) => { | ||||
|                     debug!("resetting h1 header read timeout timer"); | ||||
| @@ -214,6 +215,13 @@ impl Http1Transaction for Server { | ||||
|             None | ||||
|         }; | ||||
|  | ||||
|         #[cfg(feature = "ffi")] | ||||
|         let mut header_order = if ctx.preserve_header_order { | ||||
|             Some(OriginalHeaderOrder::default()) | ||||
|         } else { | ||||
|             None | ||||
|         }; | ||||
|  | ||||
|         let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new); | ||||
|  | ||||
|         headers.reserve(headers_len); | ||||
| @@ -290,6 +298,11 @@ impl Http1Transaction for Server { | ||||
|                 header_case_map.append(&name, slice.slice(header.name.0..header.name.1)); | ||||
|             } | ||||
|  | ||||
|             #[cfg(feature = "ffi")] | ||||
|             if let Some(ref mut header_order) = header_order { | ||||
|                 header_order.append(&name); | ||||
|             } | ||||
|  | ||||
|             headers.append(name, value); | ||||
|         } | ||||
|  | ||||
| @@ -304,6 +317,11 @@ impl Http1Transaction for Server { | ||||
|             extensions.insert(header_case_map); | ||||
|         } | ||||
|  | ||||
|         #[cfg(feature = "ffi")] | ||||
|         if let Some(header_order) = header_order { | ||||
|             extensions.insert(header_order); | ||||
|         } | ||||
|  | ||||
|         *ctx.req_method = Some(subject.0.clone()); | ||||
|  | ||||
|         Ok(Some(ParsedMessage { | ||||
| @@ -358,7 +376,13 @@ impl Http1Transaction for Server { | ||||
|  | ||||
|         let init_cap = 30 + msg.head.headers.len() * AVERAGE_HEADER_SIZE; | ||||
|         dst.reserve(init_cap); | ||||
|         if msg.head.version == Version::HTTP_11 && msg.head.subject == StatusCode::OK { | ||||
|  | ||||
|         let custom_reason_phrase = msg.head.extensions.get::<crate::ext::ReasonPhrase>(); | ||||
|  | ||||
|         if msg.head.version == Version::HTTP_11 | ||||
|             && msg.head.subject == StatusCode::OK | ||||
|             && custom_reason_phrase.is_none() | ||||
|         { | ||||
|             extend(dst, b"HTTP/1.1 200 OK\r\n"); | ||||
|         } else { | ||||
|             match msg.head.version { | ||||
| @@ -373,15 +397,21 @@ impl Http1Transaction for Server { | ||||
|  | ||||
|             extend(dst, msg.head.subject.as_str().as_bytes()); | ||||
|             extend(dst, b" "); | ||||
|             // a reason MUST be written, as many parsers will expect it. | ||||
|             extend( | ||||
|                 dst, | ||||
|                 msg.head | ||||
|                     .subject | ||||
|                     .canonical_reason() | ||||
|                     .unwrap_or("<none>") | ||||
|                     .as_bytes(), | ||||
|             ); | ||||
|  | ||||
|             if let Some(reason) = custom_reason_phrase { | ||||
|                 extend(dst, reason.as_bytes()); | ||||
|             } else { | ||||
|                 // a reason MUST be written, as many parsers will expect it. | ||||
|                 extend( | ||||
|                     dst, | ||||
|                     msg.head | ||||
|                         .subject | ||||
|                         .canonical_reason() | ||||
|                         .unwrap_or("<none>") | ||||
|                         .as_bytes(), | ||||
|                 ); | ||||
|             } | ||||
|  | ||||
|             extend(dst, b"\r\n"); | ||||
|         } | ||||
|  | ||||
| @@ -468,6 +498,10 @@ impl Server { | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     fn can_have_implicit_zero_content_length(method: &Option<Method>, status: StatusCode) -> bool { | ||||
|         Server::can_have_content_length(method, status) && method != &Some(Method::HEAD) | ||||
|     } | ||||
|  | ||||
|     fn encode_headers_with_lower_case( | ||||
|         msg: Encode<'_, StatusCode>, | ||||
|         dst: &mut Vec<u8>, | ||||
| @@ -820,7 +854,10 @@ impl Server { | ||||
|                     } | ||||
|                 } | ||||
|                 None | Some(BodyLength::Known(0)) => { | ||||
|                     if Server::can_have_content_length(msg.req_method, msg.head.subject) { | ||||
|                     if Server::can_have_implicit_zero_content_length( | ||||
|                         msg.req_method, | ||||
|                         msg.head.subject, | ||||
|                     ) { | ||||
|                         header_name_writer.write_full_header_line( | ||||
|                             dst, | ||||
|                             "content-length: 0\r\n", | ||||
| @@ -918,12 +955,9 @@ impl Http1Transaction for Client { | ||||
|                         trace!("Response.parse Complete({})", len); | ||||
|                         let status = StatusCode::from_u16(res.code.unwrap())?; | ||||
|  | ||||
|                         #[cfg(not(feature = "ffi"))] | ||||
|                         let reason = (); | ||||
|                         #[cfg(feature = "ffi")] | ||||
|                         let reason = { | ||||
|                             let reason = res.reason.unwrap(); | ||||
|                             // Only save the reason phrase if it isnt the canonical reason | ||||
|                             // Only save the reason phrase if it isn't the canonical reason | ||||
|                             if Some(reason) != status.canonical_reason() { | ||||
|                                 Some(Bytes::copy_from_slice(reason.as_bytes())) | ||||
|                             } else { | ||||
| @@ -944,12 +978,7 @@ impl Http1Transaction for Client { | ||||
|                     Err(httparse::Error::Version) if ctx.h09_responses => { | ||||
|                         trace!("Response.parse accepted HTTP/0.9 response"); | ||||
|  | ||||
|                         #[cfg(not(feature = "ffi"))] | ||||
|                         let reason = (); | ||||
|                         #[cfg(feature = "ffi")] | ||||
|                         let reason = None; | ||||
|  | ||||
|                         (0, StatusCode::OK, reason, Version::HTTP_09, 0) | ||||
|                         (0, StatusCode::OK, None, Version::HTTP_09, 0) | ||||
|                     } | ||||
|                     Err(e) => return Err(e.into()), | ||||
|                 } | ||||
| @@ -957,15 +986,15 @@ impl Http1Transaction for Client { | ||||
|  | ||||
|             let mut slice = buf.split_to(len); | ||||
|  | ||||
|             if ctx.h1_parser_config.obsolete_multiline_headers_in_responses_are_allowed() { | ||||
|                 for header in &headers_indices[..headers_len] { | ||||
|             if ctx | ||||
|                 .h1_parser_config | ||||
|                 .obsolete_multiline_headers_in_responses_are_allowed() | ||||
|             { | ||||
|                 for header in &mut headers_indices[..headers_len] { | ||||
|                     // SAFETY: array is valid up to `headers_len` | ||||
|                     let header = unsafe { &*header.as_ptr() }; | ||||
|                     for b in &mut slice[header.value.0..header.value.1] { | ||||
|                         if *b == b'\r' || *b == b'\n' { | ||||
|                             *b = b' '; | ||||
|                         } | ||||
|                     } | ||||
|                     let header = unsafe { &mut *header.as_mut_ptr() }; | ||||
|                     Client::obs_fold_line(&mut slice, header); | ||||
|  | ||||
|                 } | ||||
|             } | ||||
|  | ||||
| @@ -981,6 +1010,13 @@ impl Http1Transaction for Client { | ||||
|                 None | ||||
|             }; | ||||
|  | ||||
|             #[cfg(feature = "ffi")] | ||||
|             let mut header_order = if ctx.preserve_header_order { | ||||
|                 Some(OriginalHeaderOrder::default()) | ||||
|             } else { | ||||
|                 None | ||||
|             }; | ||||
|  | ||||
|             headers.reserve(headers_len); | ||||
|             for header in &headers_indices[..headers_len] { | ||||
|                 // SAFETY: array is valid up to `headers_len` | ||||
| @@ -1003,6 +1039,11 @@ impl Http1Transaction for Client { | ||||
|                     header_case_map.append(&name, slice.slice(header.name.0..header.name.1)); | ||||
|                 } | ||||
|  | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 if let Some(ref mut header_order) = header_order { | ||||
|                     header_order.append(&name); | ||||
|                 } | ||||
|  | ||||
|                 headers.append(name, value); | ||||
|             } | ||||
|  | ||||
| @@ -1013,11 +1054,16 @@ impl Http1Transaction for Client { | ||||
|             } | ||||
|  | ||||
|             #[cfg(feature = "ffi")] | ||||
|             if let Some(reason) = reason { | ||||
|                 extensions.insert(crate::ffi::ReasonPhrase(reason)); | ||||
|             if let Some(header_order) = header_order { | ||||
|                 extensions.insert(header_order); | ||||
|             } | ||||
|  | ||||
|             if let Some(reason) = reason { | ||||
|                 // Safety: httparse ensures that only valid reason phrase bytes are present in this | ||||
|                 // field. | ||||
|                 let reason = unsafe { crate::ext::ReasonPhrase::from_bytes_unchecked(reason) }; | ||||
|                 extensions.insert(reason); | ||||
|             } | ||||
|             #[cfg(not(feature = "ffi"))] | ||||
|             drop(reason); | ||||
|  | ||||
|             #[cfg(feature = "ffi")] | ||||
|             if ctx.raw_headers { | ||||
| @@ -1295,6 +1341,65 @@ impl Client { | ||||
|  | ||||
|         set_content_length(headers, len) | ||||
|     } | ||||
|  | ||||
|     fn obs_fold_line(all: &mut [u8], idx: &mut HeaderIndices) { | ||||
|         // If the value has obs-folded text, then in-place shift the bytes out | ||||
|         // of here. | ||||
|         // | ||||
|         // https://httpwg.org/specs/rfc9112.html#line.folding | ||||
|         // | ||||
|         // > A user agent that receives an obs-fold MUST replace each received | ||||
|         // > obs-fold with one or more SP octets prior to interpreting the | ||||
|         // > field value. | ||||
|         // | ||||
|         // This means strings like "\r\n\t foo" must replace the "\r\n\t " with | ||||
|         // a single space. | ||||
|  | ||||
|         let buf = &mut all[idx.value.0..idx.value.1]; | ||||
|  | ||||
|         // look for a newline, otherwise bail out | ||||
|         let first_nl = match buf.iter().position(|b| *b == b'\n') { | ||||
|             Some(i) => i, | ||||
|             None => return, | ||||
|         }; | ||||
|  | ||||
|         // not on standard slices because whatever, sigh | ||||
|         fn trim_start(mut s: &[u8]) -> &[u8] { | ||||
|             while let [first, rest @ ..] = s { | ||||
|                 if first.is_ascii_whitespace() { | ||||
|                     s = rest; | ||||
|                 } else { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             s | ||||
|         } | ||||
|  | ||||
|         fn trim_end(mut s: &[u8]) -> &[u8] { | ||||
|             while let [rest @ .., last] = s { | ||||
|                 if last.is_ascii_whitespace() { | ||||
|                     s = rest; | ||||
|                 } else { | ||||
|                     break; | ||||
|                 } | ||||
|             } | ||||
|             s | ||||
|         } | ||||
|  | ||||
|         fn trim(s: &[u8]) -> &[u8] { | ||||
|             trim_start(trim_end(s)) | ||||
|         } | ||||
|  | ||||
|         // TODO(perf): we could do the moves in-place, but this is so uncommon | ||||
|         // that it shouldn't matter. | ||||
|         let mut unfolded = trim_end(&buf[..first_nl]).to_vec(); | ||||
|         for line in buf[first_nl + 1..].split(|b| *b == b'\n') { | ||||
|             unfolded.push(b' '); | ||||
|             unfolded.extend_from_slice(trim(line)); | ||||
|         } | ||||
|         buf[..unfolded.len()].copy_from_slice(&unfolded); | ||||
|         idx.value.1 = idx.value.0 + unfolded.len(); | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn set_content_length(headers: &mut HeaderMap, len: u64) -> Encoder { | ||||
| @@ -1474,10 +1579,15 @@ mod tests { | ||||
|                 cached_headers: &mut None, | ||||
|                 req_method: &mut method, | ||||
|                 h1_parser_config: Default::default(), | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout: None, | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout_fut: &mut None, | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout_running: &mut false, | ||||
|                 preserve_header_case: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 preserve_header_order: false, | ||||
|                 h09_responses: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 on_informational: &mut None, | ||||
| @@ -1504,10 +1614,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             h1_parser_config: Default::default(), | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1529,10 +1644,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut None, | ||||
|             h1_parser_config: Default::default(), | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1552,10 +1672,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             h1_parser_config: Default::default(), | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: true, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1577,10 +1702,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             h1_parser_config: Default::default(), | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1606,10 +1736,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             h1_parser_config, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1632,10 +1767,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut Some(crate::Method::GET), | ||||
|             h1_parser_config: Default::default(), | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1653,10 +1793,15 @@ mod tests { | ||||
|             cached_headers: &mut None, | ||||
|             req_method: &mut None, | ||||
|             h1_parser_config: Default::default(), | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout: None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_fut: &mut None, | ||||
|             #[cfg(feature = "runtime")] | ||||
|             h1_header_read_timeout_running: &mut false, | ||||
|             preserve_header_case: true, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             preserve_header_order: false, | ||||
|             h09_responses: false, | ||||
|             #[cfg(feature = "ffi")] | ||||
|             on_informational: &mut None, | ||||
| @@ -1695,10 +1840,15 @@ mod tests { | ||||
|                     cached_headers: &mut None, | ||||
|                     req_method: &mut None, | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
| @@ -1718,10 +1868,15 @@ mod tests { | ||||
|                     cached_headers: &mut None, | ||||
|                     req_method: &mut None, | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
| @@ -1950,10 +2105,15 @@ mod tests { | ||||
|                     cached_headers: &mut None, | ||||
|                     req_method: &mut Some(Method::GET), | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
| @@ -1973,10 +2133,15 @@ mod tests { | ||||
|                     cached_headers: &mut None, | ||||
|                     req_method: &mut Some(m), | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
| @@ -1996,10 +2161,15 @@ mod tests { | ||||
|                     cached_headers: &mut None, | ||||
|                     req_method: &mut Some(Method::GET), | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
| @@ -2270,6 +2440,30 @@ mod tests { | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[cfg(feature = "client")] | ||||
|     #[test] | ||||
|     fn test_client_obs_fold_line() { | ||||
|         fn unfold(src: &str) -> String { | ||||
|             let mut buf = src.as_bytes().to_vec(); | ||||
|             let mut idx = HeaderIndices { | ||||
|                 name: (0, 0), | ||||
|                 value: (0, buf.len()), | ||||
|             }; | ||||
|             Client::obs_fold_line(&mut buf, &mut idx); | ||||
|             String::from_utf8(buf[idx.value.0 .. idx.value.1].to_vec()).unwrap() | ||||
|         } | ||||
|  | ||||
|         assert_eq!( | ||||
|             unfold("a normal line"), | ||||
|             "a normal line", | ||||
|         ); | ||||
|  | ||||
|         assert_eq!( | ||||
|             unfold("obs\r\n fold\r\n\t line"), | ||||
|             "obs fold line", | ||||
|         ); | ||||
|     } | ||||
|  | ||||
|     #[test] | ||||
|     fn test_client_request_encode_title_case() { | ||||
|         use crate::proto::BodyLength; | ||||
| @@ -2496,10 +2690,15 @@ mod tests { | ||||
|                 cached_headers: &mut None, | ||||
|                 req_method: &mut Some(Method::GET), | ||||
|                 h1_parser_config: Default::default(), | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout: None, | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout_fut: &mut None, | ||||
|                 #[cfg(feature = "runtime")] | ||||
|                 h1_header_read_timeout_running: &mut false, | ||||
|                 preserve_header_case: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 preserve_header_order: false, | ||||
|                 h09_responses: false, | ||||
|                 #[cfg(feature = "ffi")] | ||||
|                 on_informational: &mut None, | ||||
| @@ -2583,10 +2782,15 @@ mod tests { | ||||
|                     cached_headers: &mut headers, | ||||
|                     req_method: &mut None, | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
| @@ -2626,10 +2830,15 @@ mod tests { | ||||
|                     cached_headers: &mut headers, | ||||
|                     req_method: &mut None, | ||||
|                     h1_parser_config: Default::default(), | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout: None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_fut: &mut None, | ||||
|                     #[cfg(feature = "runtime")] | ||||
|                     h1_header_read_timeout_running: &mut false, | ||||
|                     preserve_header_case: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     preserve_header_order: false, | ||||
|                     h09_responses: false, | ||||
|                     #[cfg(feature = "ffi")] | ||||
|                     on_informational: &mut None, | ||||
|   | ||||
| @@ -35,6 +35,8 @@ const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024; // 1mb | ||||
| const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb | ||||
| const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb | ||||
| const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 400; // 400kb | ||||
| // 16 MB "sane default" taken from golang http2 | ||||
| const DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: u32 = 16 << 20; | ||||
|  | ||||
| #[derive(Clone, Debug)] | ||||
| pub(crate) struct Config { | ||||
| @@ -49,6 +51,7 @@ pub(crate) struct Config { | ||||
|     #[cfg(feature = "runtime")] | ||||
|     pub(crate) keep_alive_timeout: Duration, | ||||
|     pub(crate) max_send_buffer_size: usize, | ||||
|     pub(crate) max_header_list_size: u32, | ||||
| } | ||||
|  | ||||
| impl Default for Config { | ||||
| @@ -65,6 +68,7 @@ impl Default for Config { | ||||
|             #[cfg(feature = "runtime")] | ||||
|             keep_alive_timeout: Duration::from_secs(20), | ||||
|             max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE, | ||||
|             max_header_list_size: DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE, | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -116,6 +120,7 @@ where | ||||
|             .initial_window_size(config.initial_stream_window_size) | ||||
|             .initial_connection_window_size(config.initial_conn_window_size) | ||||
|             .max_frame_size(config.max_frame_size) | ||||
|             .max_header_list_size(config.max_header_list_size) | ||||
|             .max_send_buffer_size(config.max_send_buffer_size); | ||||
|         if let Some(max) = config.max_concurrent_streams { | ||||
|             builder.max_concurrent_streams(max); | ||||
| @@ -138,7 +143,7 @@ where | ||||
|             #[cfg(feature = "runtime")] | ||||
|             keep_alive_timeout: config.keep_alive_timeout, | ||||
|             // If keep-alive is enabled for servers, always enabled while | ||||
|             // idle, so it can more aggresively close dead connections. | ||||
|             // idle, so it can more aggressively close dead connections. | ||||
|             #[cfg(feature = "runtime")] | ||||
|             keep_alive_while_idle: true, | ||||
|         }; | ||||
| @@ -259,7 +264,7 @@ where | ||||
|                         let reason = err.h2_reason(); | ||||
|                         if reason == Reason::NO_ERROR { | ||||
|                             // NO_ERROR is only used for graceful shutdowns... | ||||
|                             trace!("interpretting NO_ERROR user error as graceful_shutdown"); | ||||
|                             trace!("interpreting NO_ERROR user error as graceful_shutdown"); | ||||
|                             self.conn.graceful_shutdown(); | ||||
|                         } else { | ||||
|                             trace!("abruptly shutting down with {:?}", reason); | ||||
|   | ||||
| @@ -1,111 +0,0 @@ | ||||
| //! The `Accept` trait and supporting types. | ||||
| //! | ||||
| //! This module contains: | ||||
| //! | ||||
| //! - The [`Accept`](Accept) trait used to asynchronously accept incoming | ||||
| //!   connections. | ||||
| //! - Utilities like `poll_fn` to ease creating a custom `Accept`. | ||||
|  | ||||
| #[cfg(feature = "stream")] | ||||
| use futures_core::Stream; | ||||
| #[cfg(feature = "stream")] | ||||
| use pin_project_lite::pin_project; | ||||
|  | ||||
| use crate::common::{ | ||||
|     task::{self, Poll}, | ||||
|     Pin, | ||||
| }; | ||||
|  | ||||
| /// Asynchronously accept incoming connections. | ||||
| pub trait Accept { | ||||
|     /// The connection type that can be accepted. | ||||
|     type Conn; | ||||
|     /// The error type that can occur when accepting a connection. | ||||
|     type Error; | ||||
|  | ||||
|     /// Poll to accept the next connection. | ||||
|     fn poll_accept( | ||||
|         self: Pin<&mut Self>, | ||||
|         cx: &mut task::Context<'_>, | ||||
|     ) -> Poll<Option<Result<Self::Conn, Self::Error>>>; | ||||
| } | ||||
|  | ||||
| /// Create an `Accept` with a polling function. | ||||
| /// | ||||
| /// # Example | ||||
| /// | ||||
| /// ``` | ||||
| /// use std::task::Poll; | ||||
| /// use hyper::server::{accept, Server}; | ||||
| /// | ||||
| /// # let mock_conn = (); | ||||
| /// // If we created some mocked connection... | ||||
| /// let mut conn = Some(mock_conn); | ||||
| /// | ||||
| /// // And accept just the mocked conn once... | ||||
| /// let once = accept::poll_fn(move |cx| { | ||||
| ///     Poll::Ready(conn.take().map(Ok::<_, ()>)) | ||||
| /// }); | ||||
| /// | ||||
| /// let builder = Server::builder(once); | ||||
| /// ``` | ||||
| pub fn poll_fn<F, IO, E>(func: F) -> impl Accept<Conn = IO, Error = E> | ||||
| where | ||||
|     F: FnMut(&mut task::Context<'_>) -> Poll<Option<Result<IO, E>>>, | ||||
| { | ||||
|     struct PollFn<F>(F); | ||||
|  | ||||
|     // The closure `F` is never pinned | ||||
|     impl<F> Unpin for PollFn<F> {} | ||||
|  | ||||
|     impl<F, IO, E> Accept for PollFn<F> | ||||
|     where | ||||
|         F: FnMut(&mut task::Context<'_>) -> Poll<Option<Result<IO, E>>>, | ||||
|     { | ||||
|         type Conn = IO; | ||||
|         type Error = E; | ||||
|         fn poll_accept( | ||||
|             self: Pin<&mut Self>, | ||||
|             cx: &mut task::Context<'_>, | ||||
|         ) -> Poll<Option<Result<Self::Conn, Self::Error>>> { | ||||
|             (self.get_mut().0)(cx) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     PollFn(func) | ||||
| } | ||||
|  | ||||
| /// Adapt a `Stream` of incoming connections into an `Accept`. | ||||
| /// | ||||
| /// # Optional | ||||
| /// | ||||
| /// This function requires enabling the `stream` feature in your | ||||
| /// `Cargo.toml`. | ||||
| #[cfg(feature = "stream")] | ||||
| pub fn from_stream<S, IO, E>(stream: S) -> impl Accept<Conn = IO, Error = E> | ||||
| where | ||||
|     S: Stream<Item = Result<IO, E>>, | ||||
| { | ||||
|     pin_project! { | ||||
|         struct FromStream<S> { | ||||
|             #[pin] | ||||
|             stream: S, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl<S, IO, E> Accept for FromStream<S> | ||||
|     where | ||||
|         S: Stream<Item = Result<IO, E>>, | ||||
|     { | ||||
|         type Conn = IO; | ||||
|         type Error = E; | ||||
|         fn poll_accept( | ||||
|             self: Pin<&mut Self>, | ||||
|             cx: &mut task::Context<'_>, | ||||
|         ) -> Poll<Option<Result<Self::Conn, Self::Error>>> { | ||||
|             self.project().stream.poll_next(cx) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     FromStream { stream } | ||||
| } | ||||
| @@ -5,9 +5,6 @@ | ||||
| //! are not handled at this level. This module provides the building blocks to | ||||
| //! customize those things externally. | ||||
| //! | ||||
| //! If you don't have need to manage connections yourself, consider using the | ||||
| //! higher-level [Server](super) API. | ||||
| //! | ||||
| //! ## Example | ||||
| //! A simple example that uses the `Http` struct to talk HTTP over a Tokio TCP stream | ||||
| //! ```no_run | ||||
| @@ -48,8 +45,7 @@ | ||||
|     not(all(feature = "http1", feature = "http2")) | ||||
| ))] | ||||
| use std::marker::PhantomData; | ||||
| #[cfg(feature = "tcp")] | ||||
| use std::net::SocketAddr; | ||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "runtime"))] | ||||
| use std::time::Duration; | ||||
|  | ||||
| #[cfg(feature = "http2")] | ||||
| @@ -70,34 +66,25 @@ cfg_feature! { | ||||
|     use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|     use tracing::trace; | ||||
|  | ||||
|     use super::accept::Accept; | ||||
|     use crate::body::{Body, HttpBody}; | ||||
|     use crate::common::{task, Future, Pin, Poll, Unpin}; | ||||
|     #[cfg(not(all(feature = "http1", feature = "http2")))] | ||||
|     use crate::common::Never; | ||||
|     use crate::common::exec::{ConnStreamExec, Exec, NewSvcExec}; | ||||
|     use crate::common::exec::{ConnStreamExec, Exec}; | ||||
|     use crate::proto; | ||||
|     use crate::service::{HttpService, MakeServiceRef}; | ||||
|     use self::spawn_all::NewSvcTask; | ||||
|     use crate::service::HttpService; | ||||
|  | ||||
|     pub(super) use self::spawn_all::{NoopWatcher, Watcher}; | ||||
|     pub(super) use self::upgrades::UpgradeableConnection; | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "tcp")] | ||||
| pub use super::tcp::{AddrIncoming, AddrStream}; | ||||
|  | ||||
| /// A lower-level configuration of the HTTP protocol. | ||||
| /// | ||||
| /// This structure is used to configure options for an HTTP server connection. | ||||
| /// | ||||
| /// If you don't have need to manage connections yourself, consider using the | ||||
| /// higher-level [Server](super) API. | ||||
| #[derive(Clone, Debug)] | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
| pub struct Http<E = Exec> { | ||||
|     exec: E, | ||||
|     pub(crate) exec: E, | ||||
|     h1_half_close: bool, | ||||
|     h1_keep_alive: bool, | ||||
|     h1_title_case_headers: bool, | ||||
| @@ -127,51 +114,6 @@ enum ConnectionMode { | ||||
|     Fallback, | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pin_project! { | ||||
|     /// A stream mapping incoming IOs to new services. | ||||
|     /// | ||||
|     /// Yields `Connecting`s that are futures that should be put on a reactor. | ||||
|     #[must_use = "streams do nothing unless polled"] | ||||
|     #[derive(Debug)] | ||||
|     pub(super) struct Serve<I, S, E = Exec> { | ||||
|         #[pin] | ||||
|         incoming: I, | ||||
|         make_service: S, | ||||
|         protocol: Http<E>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pin_project! { | ||||
|     /// A future building a new `Service` to a `Connection`. | ||||
|     /// | ||||
|     /// Wraps the future returned from `MakeService` into one that returns | ||||
|     /// a `Connection`. | ||||
|     #[must_use = "futures do nothing unless polled"] | ||||
|     #[derive(Debug)] | ||||
|     #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
|     pub struct Connecting<I, F, E = Exec> { | ||||
|         #[pin] | ||||
|         future: F, | ||||
|         io: Option<I>, | ||||
|         protocol: Http<E>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pin_project! { | ||||
|     #[must_use = "futures do nothing unless polled"] | ||||
|     #[derive(Debug)] | ||||
|     pub(super) struct SpawnAll<I, S, E> { | ||||
|         // TODO: re-add `pub(super)` once rustdoc can handle this. | ||||
|         // | ||||
|         // See https://github.com/rust-lang/rust/issues/64705 | ||||
|         #[pin] | ||||
|         pub(super) serve: Serve<I, S, E>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pin_project! { | ||||
|     /// A future binding a connection with a Service. | ||||
| @@ -375,7 +317,7 @@ impl<E> Http<E> { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set a timeout for reading client request headers. If a client does not  | ||||
|     /// Set a timeout for reading client request headers. If a client does not | ||||
|     /// transmit the entire header within this time, the connection is closed. | ||||
|     /// | ||||
|     /// Default is None. | ||||
| @@ -567,6 +509,16 @@ impl<E> Http<E> { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the max size of received header frames. | ||||
|     /// | ||||
|     /// Default is currently ~16MB, but may change. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_header_list_size(&mut self, max: u32) -> &mut Self { | ||||
|         self.h2_builder.max_header_list_size = max; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the maximum buffer size for the connection. | ||||
|     /// | ||||
|     /// Default is ~400kb. | ||||
| @@ -719,23 +671,6 @@ impl<E> Http<E> { | ||||
|             fallback: PhantomData, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pub(super) fn serve<I, IO, IE, S, Bd>(&self, incoming: I, make_service: S) -> Serve<I, S, E> | ||||
|     where | ||||
|         I: Accept<Conn = IO, Error = IE>, | ||||
|         IE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         IO: AsyncRead + AsyncWrite + Unpin, | ||||
|         S: MakeServiceRef<IO, Body, ResBody = Bd>, | ||||
|         S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         Bd: HttpBody, | ||||
|         E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, Bd>, | ||||
|     { | ||||
|         Serve { | ||||
|             incoming, | ||||
|             make_service, | ||||
|             protocol: self.clone(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Connection ===== | ||||
| @@ -987,141 +922,6 @@ impl Default for ConnectionMode { | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Serve ===== | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| impl<I, S, E> Serve<I, S, E> { | ||||
|     /// Get a reference to the incoming stream. | ||||
|     #[inline] | ||||
|     pub(super) fn incoming_ref(&self) -> &I { | ||||
|         &self.incoming | ||||
|     } | ||||
|  | ||||
|     /* | ||||
|     /// Get a mutable reference to the incoming stream. | ||||
|     #[inline] | ||||
|     pub fn incoming_mut(&mut self) -> &mut I { | ||||
|         &mut self.incoming | ||||
|     } | ||||
|     */ | ||||
|  | ||||
|     /// Spawn all incoming connections onto the executor in `Http`. | ||||
|     pub(super) fn spawn_all(self) -> SpawnAll<I, S, E> { | ||||
|         SpawnAll { serve: self } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| impl<I, IO, IE, S, B, E> Serve<I, S, E> | ||||
| where | ||||
|     I: Accept<Conn = IO, Error = IE>, | ||||
|     IO: AsyncRead + AsyncWrite + Unpin, | ||||
|     IE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     S: MakeServiceRef<IO, Body, ResBody = B>, | ||||
|     B: HttpBody, | ||||
|     E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>, | ||||
| { | ||||
|     fn poll_next_( | ||||
|         self: Pin<&mut Self>, | ||||
|         cx: &mut task::Context<'_>, | ||||
|     ) -> Poll<Option<crate::Result<Connecting<IO, S::Future, E>>>> { | ||||
|         let me = self.project(); | ||||
|         match ready!(me.make_service.poll_ready_ref(cx)) { | ||||
|             Ok(()) => (), | ||||
|             Err(e) => { | ||||
|                 trace!("make_service closed"); | ||||
|                 return Poll::Ready(Some(Err(crate::Error::new_user_make_service(e)))); | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         if let Some(item) = ready!(me.incoming.poll_accept(cx)) { | ||||
|             let io = item.map_err(crate::Error::new_accept)?; | ||||
|             let new_fut = me.make_service.make_service_ref(&io); | ||||
|             Poll::Ready(Some(Ok(Connecting { | ||||
|                 future: new_fut, | ||||
|                 io: Some(io), | ||||
|                 protocol: me.protocol.clone(), | ||||
|             }))) | ||||
|         } else { | ||||
|             Poll::Ready(None) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Connecting ===== | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| impl<I, F, S, FE, E, B> Future for Connecting<I, F, E> | ||||
| where | ||||
|     I: AsyncRead + AsyncWrite + Unpin, | ||||
|     F: Future<Output = Result<S, FE>>, | ||||
|     S: HttpService<Body, ResBody = B>, | ||||
|     B: HttpBody + 'static, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     E: ConnStreamExec<S::Future, B>, | ||||
| { | ||||
|     type Output = Result<Connection<I, S, E>, FE>; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         let mut me = self.project(); | ||||
|         let service = ready!(me.future.poll(cx))?; | ||||
|         let io = Option::take(&mut me.io).expect("polled after complete"); | ||||
|         Poll::Ready(Ok(me.protocol.serve_connection(io, service))) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl SpawnAll ===== | ||||
|  | ||||
| #[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))] | ||||
| impl<S, E> SpawnAll<AddrIncoming, S, E> { | ||||
|     pub(super) fn local_addr(&self) -> SocketAddr { | ||||
|         self.serve.incoming.local_addr() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| impl<I, S, E> SpawnAll<I, S, E> { | ||||
|     pub(super) fn incoming_ref(&self) -> &I { | ||||
|         self.serve.incoming_ref() | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| impl<I, IO, IE, S, B, E> SpawnAll<I, S, E> | ||||
| where | ||||
|     I: Accept<Conn = IO, Error = IE>, | ||||
|     IE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     S: MakeServiceRef<IO, Body, ResBody = B>, | ||||
|     B: HttpBody, | ||||
|     E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>, | ||||
| { | ||||
|     pub(super) fn poll_watch<W>( | ||||
|         self: Pin<&mut Self>, | ||||
|         cx: &mut task::Context<'_>, | ||||
|         watcher: &W, | ||||
|     ) -> Poll<crate::Result<()>> | ||||
|     where | ||||
|         E: NewSvcExec<IO, S::Future, S::Service, E, W>, | ||||
|         W: Watcher<IO, S::Service, E>, | ||||
|     { | ||||
|         let mut me = self.project(); | ||||
|         loop { | ||||
|             if let Some(connecting) = ready!(me.serve.as_mut().poll_next_(cx)?) { | ||||
|                 let fut = NewSvcTask::new(connecting, watcher.clone()); | ||||
|                 me.serve | ||||
|                     .as_mut() | ||||
|                     .project() | ||||
|                     .protocol | ||||
|                     .exec | ||||
|                     .execute_new_svc(fut); | ||||
|             } else { | ||||
|                 return Poll::Ready(Ok(())); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl ProtoServer ===== | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| @@ -1151,150 +951,6 @@ where | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pub(crate) mod spawn_all { | ||||
|     use std::error::Error as StdError; | ||||
|     use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|     use tracing::debug; | ||||
|  | ||||
|     use super::{Connecting, UpgradeableConnection}; | ||||
|     use crate::body::{Body, HttpBody}; | ||||
|     use crate::common::exec::ConnStreamExec; | ||||
|     use crate::common::{task, Future, Pin, Poll, Unpin}; | ||||
|     use crate::service::HttpService; | ||||
|     use pin_project_lite::pin_project; | ||||
|  | ||||
|     // Used by `SpawnAll` to optionally watch a `Connection` future. | ||||
|     // | ||||
|     // The regular `hyper::Server` just uses a `NoopWatcher`, which does | ||||
|     // not need to watch anything, and so returns the `Connection` untouched. | ||||
|     // | ||||
|     // The `Server::with_graceful_shutdown` needs to keep track of all active | ||||
|     // connections, and signal that they start to shutdown when prompted, so | ||||
|     // it has a `GracefulWatcher` implementation to do that. | ||||
|     pub trait Watcher<I, S: HttpService<Body>, E>: Clone { | ||||
|         type Future: Future<Output = crate::Result<()>>; | ||||
|  | ||||
|         fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future; | ||||
|     } | ||||
|  | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     #[derive(Copy, Clone)] | ||||
|     pub struct NoopWatcher; | ||||
|  | ||||
|     impl<I, S, E> Watcher<I, S, E> for NoopWatcher | ||||
|     where | ||||
|         I: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|         S: HttpService<Body>, | ||||
|         E: ConnStreamExec<S::Future, S::ResBody>, | ||||
|         S::ResBody: 'static, | ||||
|         <S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     { | ||||
|         type Future = UpgradeableConnection<I, S, E>; | ||||
|  | ||||
|         fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future { | ||||
|             conn | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     // This is a `Future<Item=(), Error=()>` spawned to an `Executor` inside | ||||
|     // the `SpawnAll`. By being a nameable type, we can be generic over the | ||||
|     // user's `Service::Future`, and thus an `Executor` can execute it. | ||||
|     // | ||||
|     // Doing this allows for the server to conditionally require `Send` futures, | ||||
|     // depending on the `Executor` configured. | ||||
|     // | ||||
|     // Users cannot import this type, nor the associated `NewSvcExec`. Instead, | ||||
|     // a blanket implementation for `Executor<impl Future>` is sufficient. | ||||
|  | ||||
|     pin_project! { | ||||
|         #[allow(missing_debug_implementations)] | ||||
|         pub struct NewSvcTask<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> { | ||||
|             #[pin] | ||||
|             state: State<I, N, S, E, W>, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     pin_project! { | ||||
|         #[project = StateProj] | ||||
|         pub(super) enum State<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> { | ||||
|             Connecting { | ||||
|                 #[pin] | ||||
|                 connecting: Connecting<I, N, E>, | ||||
|                 watcher: W, | ||||
|             }, | ||||
|             Connected { | ||||
|                 #[pin] | ||||
|                 future: W::Future, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> NewSvcTask<I, N, S, E, W> { | ||||
|         pub(super) fn new(connecting: Connecting<I, N, E>, watcher: W) -> Self { | ||||
|             NewSvcTask { | ||||
|                 state: State::Connecting { | ||||
|                     connecting, | ||||
|                     watcher, | ||||
|                 }, | ||||
|             } | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl<I, N, S, NE, B, E, W> Future for NewSvcTask<I, N, S, E, W> | ||||
|     where | ||||
|         I: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|         N: Future<Output = Result<S, NE>>, | ||||
|         NE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         S: HttpService<Body, ResBody = B>, | ||||
|         B: HttpBody + 'static, | ||||
|         B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         E: ConnStreamExec<S::Future, B>, | ||||
|         W: Watcher<I, S, E>, | ||||
|     { | ||||
|         type Output = (); | ||||
|  | ||||
|         fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|             // If it weren't for needing to name this type so the `Send` bounds | ||||
|             // could be projected to the `Serve` executor, this could just be | ||||
|             // an `async fn`, and much safer. Woe is me. | ||||
|  | ||||
|             let mut me = self.project(); | ||||
|             loop { | ||||
|                 let next = { | ||||
|                     match me.state.as_mut().project() { | ||||
|                         StateProj::Connecting { | ||||
|                             connecting, | ||||
|                             watcher, | ||||
|                         } => { | ||||
|                             let res = ready!(connecting.poll(cx)); | ||||
|                             let conn = match res { | ||||
|                                 Ok(conn) => conn, | ||||
|                                 Err(err) => { | ||||
|                                     let err = crate::Error::new_user_make_service(err); | ||||
|                                     debug!("connecting error: {}", err); | ||||
|                                     return Poll::Ready(()); | ||||
|                                 } | ||||
|                             }; | ||||
|                             let future = watcher.watch(conn.with_upgrades()); | ||||
|                             State::Connected { future } | ||||
|                         } | ||||
|                         StateProj::Connected { future } => { | ||||
|                             return future.poll(cx).map(|res| { | ||||
|                                 if let Err(err) = res { | ||||
|                                     debug!("connection error: {}", err); | ||||
|                                 } | ||||
|                             }); | ||||
|                         } | ||||
|                     } | ||||
|                 }; | ||||
|  | ||||
|                 me.state.set(next); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| mod upgrades { | ||||
|     use super::*; | ||||
|   | ||||
| @@ -1,164 +1,10 @@ | ||||
| //! HTTP Server | ||||
| //! | ||||
| //! A `Server` is created to listen on a port, parse HTTP requests, and hand | ||||
| //! them off to a `Service`. | ||||
| //! A "server" is usually created by listening on a port for new connections, | ||||
| //! parse HTTP requests, and hand them off to a `Service`. | ||||
| //! | ||||
| //! There are two levels of APIs provide for constructing HTTP servers: | ||||
| //! | ||||
| //! - The higher-level [`Server`](Server) type. | ||||
| //! - The lower-level [`conn`](conn) module. | ||||
| //! | ||||
| //! # Server | ||||
| //! | ||||
| //! The [`Server`](Server) is main way to start listening for HTTP requests. | ||||
| //! It wraps a listener with a [`MakeService`](crate::service), and then should | ||||
| //! be executed to start serving requests. | ||||
| //! | ||||
| //! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default. | ||||
| //! | ||||
| //! ## Examples | ||||
| //! | ||||
| //! ```no_run | ||||
| //! use std::convert::Infallible; | ||||
| //! use std::net::SocketAddr; | ||||
| //! use hyper::{Body, Request, Response, Server}; | ||||
| //! use hyper::service::{make_service_fn, service_fn}; | ||||
| //! | ||||
| //! async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> { | ||||
| //!     Ok(Response::new(Body::from("Hello World"))) | ||||
| //! } | ||||
| //! | ||||
| //! # #[cfg(feature = "runtime")] | ||||
| //! #[tokio::main] | ||||
| //! async fn main() { | ||||
| //!     // Construct our SocketAddr to listen on... | ||||
| //!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||||
| //! | ||||
| //!     // And a MakeService to handle each connection... | ||||
| //!     let make_service = make_service_fn(|_conn| async { | ||||
| //!         Ok::<_, Infallible>(service_fn(handle)) | ||||
| //!     }); | ||||
| //! | ||||
| //!     // Then bind and serve... | ||||
| //!     let server = Server::bind(&addr).serve(make_service); | ||||
| //! | ||||
| //!     // And run forever... | ||||
| //!     if let Err(e) = server.await { | ||||
| //!         eprintln!("server error: {}", e); | ||||
| //!     } | ||||
| //! } | ||||
| //! # #[cfg(not(feature = "runtime"))] | ||||
| //! # fn main() {} | ||||
| //! ``` | ||||
| //! | ||||
| //! If you don't need the connection and your service implements `Clone` you can use | ||||
| //! [`tower::make::Shared`] instead of `make_service_fn` which is a bit simpler: | ||||
| //! | ||||
| //! ```no_run | ||||
| //! # use std::convert::Infallible; | ||||
| //! # use std::net::SocketAddr; | ||||
| //! # use hyper::{Body, Request, Response, Server}; | ||||
| //! # use hyper::service::{make_service_fn, service_fn}; | ||||
| //! # use tower::make::Shared; | ||||
| //! # async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> { | ||||
| //! #     Ok(Response::new(Body::from("Hello World"))) | ||||
| //! # } | ||||
| //! # #[cfg(feature = "runtime")] | ||||
| //! #[tokio::main] | ||||
| //! async fn main() { | ||||
| //!     // Construct our SocketAddr to listen on... | ||||
| //!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||||
| //! | ||||
| //!     // Shared is a MakeService that produces services by cloning an inner service... | ||||
| //!     let make_service = Shared::new(service_fn(handle)); | ||||
| //! | ||||
| //!     // Then bind and serve... | ||||
| //!     let server = Server::bind(&addr).serve(make_service); | ||||
| //! | ||||
| //!     // And run forever... | ||||
| //!     if let Err(e) = server.await { | ||||
| //!         eprintln!("server error: {}", e); | ||||
| //!     } | ||||
| //! } | ||||
| //! # #[cfg(not(feature = "runtime"))] | ||||
| //! # fn main() {} | ||||
| //! ``` | ||||
| //! | ||||
| //! Passing data to your request handler can be done like so: | ||||
| //! | ||||
| //! ```no_run | ||||
| //! use std::convert::Infallible; | ||||
| //! use std::net::SocketAddr; | ||||
| //! use hyper::{Body, Request, Response, Server}; | ||||
| //! use hyper::service::{make_service_fn, service_fn}; | ||||
| //! use hyper::server::conn::AddrStream; | ||||
| //! | ||||
| //! #[derive(Clone)] | ||||
| //! struct AppContext { | ||||
| //!     // Whatever data your application needs can go here | ||||
| //! } | ||||
| //! | ||||
| //! async fn handle( | ||||
| //!     context: AppContext, | ||||
| //!     addr: SocketAddr, | ||||
| //!     req: Request<Body> | ||||
| //! ) -> Result<Response<Body>, Infallible> { | ||||
| //!     Ok(Response::new(Body::from("Hello World"))) | ||||
| //! } | ||||
| //! | ||||
| //! # #[cfg(feature = "runtime")] | ||||
| //! #[tokio::main] | ||||
| //! async fn main() { | ||||
| //!     let context = AppContext { | ||||
| //!         // ... | ||||
| //!     }; | ||||
| //! | ||||
| //!     // A `MakeService` that produces a `Service` to handle each connection. | ||||
| //!     let make_service = make_service_fn(move |conn: &AddrStream| { | ||||
| //!         // We have to clone the context to share it with each invocation of | ||||
| //!         // `make_service`. If your data doesn't implement `Clone` consider using | ||||
| //!         // an `std::sync::Arc`. | ||||
| //!         let context = context.clone(); | ||||
| //! | ||||
| //!         // You can grab the address of the incoming connection like so. | ||||
| //!         let addr = conn.remote_addr(); | ||||
| //! | ||||
| //!         // Create a `Service` for responding to the request. | ||||
| //!         let service = service_fn(move |req| { | ||||
| //!             handle(context.clone(), addr, req) | ||||
| //!         }); | ||||
| //! | ||||
| //!         // Return the service to hyper. | ||||
| //!         async move { Ok::<_, Infallible>(service) } | ||||
| //!     }); | ||||
| //! | ||||
| //!     // Run the server like above... | ||||
| //!     let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); | ||||
| //! | ||||
| //!     let server = Server::bind(&addr).serve(make_service); | ||||
| //! | ||||
| //!     if let Err(e) = server.await { | ||||
| //!         eprintln!("server error: {}", e); | ||||
| //!     } | ||||
| //! } | ||||
| //! # #[cfg(not(feature = "runtime"))] | ||||
| //! # fn main() {} | ||||
| //! ``` | ||||
| //! | ||||
| //! [`tower::make::Shared`]: https://docs.rs/tower/latest/tower/make/struct.Shared.html | ||||
|  | ||||
| pub mod accept; | ||||
| //! How exactly you choose to listen for connections is not something hyper | ||||
| //! concerns itself with. After you have a connection, you can handle HTTP over | ||||
| //! it with the types in the [`conn`](conn) module. | ||||
| pub mod conn; | ||||
| mod server; | ||||
| #[cfg(feature = "tcp")] | ||||
| mod tcp; | ||||
|  | ||||
| pub use self::server::Server; | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![any(feature = "http1", feature = "http2")] | ||||
|  | ||||
|     pub use self::server::Builder; | ||||
|  | ||||
|     mod shutdown; | ||||
| } | ||||
|   | ||||
| @@ -1,560 +0,0 @@ | ||||
| use std::fmt; | ||||
| #[cfg(feature = "tcp")] | ||||
| use std::net::{SocketAddr, TcpListener as StdTcpListener}; | ||||
| #[cfg(any(feature = "tcp", feature = "http1"))] | ||||
| use std::time::Duration; | ||||
|  | ||||
| #[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))] | ||||
| use super::tcp::AddrIncoming; | ||||
| use crate::common::exec::Exec; | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![any(feature = "http1", feature = "http2")] | ||||
|  | ||||
|     use std::error::Error as StdError; | ||||
|  | ||||
|     use pin_project_lite::pin_project; | ||||
|     use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
|     use super::accept::Accept; | ||||
|     use crate::body::{Body, HttpBody}; | ||||
|     use crate::common::{task, Future, Pin, Poll, Unpin}; | ||||
|     use crate::common::exec::{ConnStreamExec, NewSvcExec}; | ||||
|     // Renamed `Http` as `Http_` for now so that people upgrading don't see an | ||||
|     // error that `hyper::server::Http` is private... | ||||
|     use super::conn::{Http as Http_, NoopWatcher, SpawnAll}; | ||||
|     use super::shutdown::{Graceful, GracefulWatcher}; | ||||
|     use crate::service::{HttpService, MakeServiceRef}; | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| pin_project! { | ||||
|     /// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default. | ||||
|     /// | ||||
|     /// `Server` is a `Future` mapping a bound listener with a set of service | ||||
|     /// handlers. It is built using the [`Builder`](Builder), and the future | ||||
|     /// completes when the server has been shutdown. It should be run by an | ||||
|     /// `Executor`. | ||||
|     pub struct Server<I, S, E = Exec> { | ||||
|         #[pin] | ||||
|         spawn_all: SpawnAll<I, S, E>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default. | ||||
| /// | ||||
| /// Needs at least one of the `http1` and `http2` features to be activated to actually be useful. | ||||
| #[cfg(not(any(feature = "http1", feature = "http2")))] | ||||
| pub struct Server<I, S, E = Exec> { | ||||
|     _marker: std::marker::PhantomData<(I, S, E)>, | ||||
| } | ||||
|  | ||||
| /// A builder for a [`Server`](Server). | ||||
| #[derive(Debug)] | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
| pub struct Builder<I, E = Exec> { | ||||
|     incoming: I, | ||||
|     protocol: Http_<E>, | ||||
| } | ||||
|  | ||||
| // ===== impl Server ===== | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
| impl<I> Server<I, ()> { | ||||
|     /// Starts a [`Builder`](Builder) with the provided incoming stream. | ||||
|     pub fn builder(incoming: I) -> Builder<I> { | ||||
|         Builder { | ||||
|             incoming, | ||||
|             protocol: Http_::new(), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![all(feature = "tcp", any(feature = "http1", feature = "http2"))] | ||||
|  | ||||
|     impl Server<AddrIncoming, ()> { | ||||
|         /// Binds to the provided address, and returns a [`Builder`](Builder). | ||||
|         /// | ||||
|         /// # Panics | ||||
|         /// | ||||
|         /// This method will panic if binding to the address fails. For a method | ||||
|         /// to bind to an address and return a `Result`, see `Server::try_bind`. | ||||
|         pub fn bind(addr: &SocketAddr) -> Builder<AddrIncoming> { | ||||
|             let incoming = AddrIncoming::new(addr).unwrap_or_else(|e| { | ||||
|                 panic!("error binding to {}: {}", addr, e); | ||||
|             }); | ||||
|             Server::builder(incoming) | ||||
|         } | ||||
|  | ||||
|         /// Tries to bind to the provided address, and returns a [`Builder`](Builder). | ||||
|         pub fn try_bind(addr: &SocketAddr) -> crate::Result<Builder<AddrIncoming>> { | ||||
|             AddrIncoming::new(addr).map(Server::builder) | ||||
|         } | ||||
|  | ||||
|         /// Create a new instance from a `std::net::TcpListener` instance. | ||||
|         pub fn from_tcp(listener: StdTcpListener) -> Result<Builder<AddrIncoming>, crate::Error> { | ||||
|             AddrIncoming::from_std(listener).map(Server::builder) | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| cfg_feature! { | ||||
|     #![all(feature = "tcp", any(feature = "http1", feature = "http2"))] | ||||
|  | ||||
|     impl<S, E> Server<AddrIncoming, S, E> { | ||||
|         /// Returns the local address that this server is bound to. | ||||
|         pub fn local_addr(&self) -> SocketAddr { | ||||
|             self.spawn_all.local_addr() | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
| impl<I, IO, IE, S, E, B> Server<I, S, E> | ||||
| where | ||||
|     I: Accept<Conn = IO, Error = IE>, | ||||
|     IE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     S: MakeServiceRef<IO, Body, ResBody = B>, | ||||
|     S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     B: HttpBody + 'static, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>, | ||||
|     E: NewSvcExec<IO, S::Future, S::Service, E, GracefulWatcher>, | ||||
| { | ||||
|     /// Prepares a server to handle graceful shutdown when the provided future | ||||
|     /// completes. | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # fn main() {} | ||||
|     /// # #[cfg(feature = "tcp")] | ||||
|     /// # async fn run() { | ||||
|     /// # use hyper::{Body, Response, Server, Error}; | ||||
|     /// # use hyper::service::{make_service_fn, service_fn}; | ||||
|     /// # let make_service = make_service_fn(|_| async { | ||||
|     /// #     Ok::<_, Error>(service_fn(|_req| async { | ||||
|     /// #         Ok::<_, Error>(Response::new(Body::from("Hello World"))) | ||||
|     /// #     })) | ||||
|     /// # }); | ||||
|     /// // Make a server from the previous examples... | ||||
|     /// let server = Server::bind(&([127, 0, 0, 1], 3000).into()) | ||||
|     ///     .serve(make_service); | ||||
|     /// | ||||
|     /// // Prepare some signal for when the server should start shutting down... | ||||
|     /// let (tx, rx) = tokio::sync::oneshot::channel::<()>(); | ||||
|     /// let graceful = server | ||||
|     ///     .with_graceful_shutdown(async { | ||||
|     ///         rx.await.ok(); | ||||
|     ///     }); | ||||
|     /// | ||||
|     /// // Await the `server` receiving the signal... | ||||
|     /// if let Err(e) = graceful.await { | ||||
|     ///     eprintln!("server error: {}", e); | ||||
|     /// } | ||||
|     /// | ||||
|     /// // And later, trigger the signal by calling `tx.send(())`. | ||||
|     /// let _ = tx.send(()); | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn with_graceful_shutdown<F>(self, signal: F) -> Graceful<I, S, F, E> | ||||
|     where | ||||
|         F: Future<Output = ()>, | ||||
|     { | ||||
|         Graceful::new(self.spawn_all, signal) | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
| impl<I, IO, IE, S, B, E> Future for Server<I, S, E> | ||||
| where | ||||
|     I: Accept<Conn = IO, Error = IE>, | ||||
|     IE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     S: MakeServiceRef<IO, Body, ResBody = B>, | ||||
|     S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     B: HttpBody + 'static, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>, | ||||
|     E: NewSvcExec<IO, S::Future, S::Service, E, NoopWatcher>, | ||||
| { | ||||
|     type Output = crate::Result<()>; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         self.project().spawn_all.poll_watch(cx, &NoopWatcher) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<I: fmt::Debug, S: fmt::Debug> fmt::Debug for Server<I, S> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         let mut st = f.debug_struct("Server"); | ||||
|         #[cfg(any(feature = "http1", feature = "http2"))] | ||||
|         st.field("listener", &self.spawn_all.incoming_ref()); | ||||
|         st.finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| // ===== impl Builder ===== | ||||
|  | ||||
| #[cfg(any(feature = "http1", feature = "http2"))] | ||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||
| impl<I, E> Builder<I, E> { | ||||
|     /// Start a new builder, wrapping an incoming stream and low-level options. | ||||
|     /// | ||||
|     /// For a more convenient constructor, see [`Server::bind`](Server::bind). | ||||
|     pub fn new(incoming: I, protocol: Http_<E>) -> Self { | ||||
|         Builder { incoming, protocol } | ||||
|     } | ||||
|  | ||||
|     /// Sets whether to use keep-alive for HTTP/1 connections. | ||||
|     /// | ||||
|     /// Default is `true`. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_keepalive(mut self, val: bool) -> Self { | ||||
|         self.protocol.http1_keep_alive(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections should support half-closures. | ||||
|     /// | ||||
|     /// Clients can chose to shutdown their write-side while waiting | ||||
|     /// for the server to respond. Setting this to `true` will | ||||
|     /// prevent closing the connection immediately if `read` | ||||
|     /// detects an EOF in the middle of a request. | ||||
|     /// | ||||
|     /// Default is `false`. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_half_close(mut self, val: bool) -> Self { | ||||
|         self.protocol.http1_half_close(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the maximum buffer size. | ||||
|     /// | ||||
|     /// Default is ~ 400kb. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_max_buf_size(mut self, val: usize) -> Self { | ||||
|         self.protocol.max_buf_size(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     // Sets whether to bunch up HTTP/1 writes until the read buffer is empty. | ||||
|     // | ||||
|     // This isn't really desirable in most cases, only really being useful in | ||||
|     // silly pipeline benchmarks. | ||||
|     #[doc(hidden)] | ||||
|     #[cfg(feature = "http1")] | ||||
|     pub fn http1_pipeline_flush(mut self, val: bool) -> Self { | ||||
|         self.protocol.pipeline_flush(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections should try to use vectored writes, | ||||
|     /// or always flatten into a single buffer. | ||||
|     /// | ||||
|     /// Note that setting this to false may mean more copies of body data, | ||||
|     /// but may also improve performance when an IO transport doesn't | ||||
|     /// support vectored writes well, such as most TLS implementations. | ||||
|     /// | ||||
|     /// Setting this to true will force hyper to use queued strategy | ||||
|     /// which may eliminate unnecessary cloning on some TLS backends | ||||
|     /// | ||||
|     /// Default is `auto`. In this mode hyper will try to guess which | ||||
|     /// mode to use | ||||
|     #[cfg(feature = "http1")] | ||||
|     pub fn http1_writev(mut self, enabled: bool) -> Self { | ||||
|         self.protocol.http1_writev(enabled); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether HTTP/1 connections will write header names as title case at | ||||
|     /// the socket level. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_title_case_headers(mut self, val: bool) -> Self { | ||||
|         self.protocol.http1_title_case_headers(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether to support preserving original header cases. | ||||
|     /// | ||||
|     /// Currently, this will record the original cases received, and store them | ||||
|     /// in a private extension on the `Request`. It will also look for and use | ||||
|     /// such an extension in any provided `Response`. | ||||
|     /// | ||||
|     /// Since the relevant extension is still private, there is no way to | ||||
|     /// interact with the original cases. The only effect this can have now is | ||||
|     /// to forward the cases in a proxy-like fashion. | ||||
|     /// | ||||
|     /// Note that this setting does not affect HTTP/2. | ||||
|     /// | ||||
|     /// Default is false. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_preserve_header_case(mut self, val: bool) -> Self { | ||||
|         self.protocol.http1_preserve_header_case(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set a timeout for reading client request headers. If a client does not  | ||||
|     /// transmit the entire header within this time, the connection is closed. | ||||
|     /// | ||||
|     /// Default is None. | ||||
|     #[cfg(all(feature = "http1", feature = "runtime"))] | ||||
|     #[cfg_attr(docsrs, doc(cfg(all(feature = "http1", feature = "runtime"))))] | ||||
|     pub fn http1_header_read_timeout(mut self, read_timeout: Duration) -> Self { | ||||
|         self.protocol.http1_header_read_timeout(read_timeout); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets whether HTTP/1 is required. | ||||
|     /// | ||||
|     /// Default is `false`. | ||||
|     #[cfg(feature = "http1")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http1")))] | ||||
|     pub fn http1_only(mut self, val: bool) -> Self { | ||||
|         self.protocol.http1_only(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets whether HTTP/2 is required. | ||||
|     /// | ||||
|     /// Default is `false`. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_only(mut self, val: bool) -> Self { | ||||
|         self.protocol.http2_only(val); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 | ||||
|     /// stream-level flow control. | ||||
|     /// | ||||
|     /// Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// If not set, hyper will use a default. | ||||
|     /// | ||||
|     /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_initial_stream_window_size(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
|         self.protocol.http2_initial_stream_window_size(sz.into()); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the max connection-level flow control for HTTP2 | ||||
|     /// | ||||
|     /// Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// If not set, hyper will use a default. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_initial_connection_window_size(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
|         self.protocol | ||||
|             .http2_initial_connection_window_size(sz.into()); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets whether to use an adaptive flow control. | ||||
|     /// | ||||
|     /// Enabling this will override the limits set in | ||||
|     /// `http2_initial_stream_window_size` and | ||||
|     /// `http2_initial_connection_window_size`. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_adaptive_window(mut self, enabled: bool) -> Self { | ||||
|         self.protocol.http2_adaptive_window(enabled); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the maximum frame size to use for HTTP2. | ||||
|     /// | ||||
|     /// Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// If not set, hyper will use a default. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||
|         self.protocol.http2_max_frame_size(sz); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2 | ||||
|     /// connections. | ||||
|     /// | ||||
|     /// Default is no limit (`std::u32::MAX`). Passing `None` will do nothing. | ||||
|     /// | ||||
|     /// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_concurrent_streams(mut self, max: impl Into<Option<u32>>) -> Self { | ||||
|         self.protocol.http2_max_concurrent_streams(max.into()); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets an interval for HTTP2 Ping frames should be sent to keep a | ||||
|     /// connection alive. | ||||
|     /// | ||||
|     /// Pass `None` to disable HTTP2 keep-alive. | ||||
|     /// | ||||
|     /// Default is currently disabled. | ||||
|     /// | ||||
|     /// # Cargo Feature | ||||
|     /// | ||||
|     /// Requires the `runtime` cargo feature to be enabled. | ||||
|     #[cfg(all(feature = "runtime", feature = "http2"))] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_keep_alive_interval(mut self, interval: impl Into<Option<Duration>>) -> Self { | ||||
|         self.protocol.http2_keep_alive_interval(interval); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets a timeout for receiving an acknowledgement of the keep-alive ping. | ||||
|     /// | ||||
|     /// If the ping is not acknowledged within the timeout, the connection will | ||||
|     /// be closed. Does nothing if `http2_keep_alive_interval` is disabled. | ||||
|     /// | ||||
|     /// Default is 20 seconds. | ||||
|     /// | ||||
|     /// # Cargo Feature | ||||
|     /// | ||||
|     /// Requires the `runtime` cargo feature to be enabled. | ||||
|     #[cfg(all(feature = "runtime", feature = "http2"))] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_keep_alive_timeout(mut self, timeout: Duration) -> Self { | ||||
|         self.protocol.http2_keep_alive_timeout(timeout); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the maximum write buffer size for each HTTP/2 stream. | ||||
|     /// | ||||
|     /// Default is currently ~400KB, but may change. | ||||
|     /// | ||||
|     /// # Panics | ||||
|     /// | ||||
|     /// The value must be no larger than `u32::MAX`. | ||||
|     #[cfg(feature = "http2")] | ||||
|     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||
|     pub fn http2_max_send_buf_size(mut self, max: usize) -> Self { | ||||
|         self.protocol.http2_max_send_buf_size(max); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Enables the [extended CONNECT protocol]. | ||||
|     /// | ||||
|     /// [extended CONNECT protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4 | ||||
|     #[cfg(feature = "http2")] | ||||
|     pub fn http2_enable_connect_protocol(mut self) -> Self { | ||||
|         self.protocol.http2_enable_connect_protocol(); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Sets the `Executor` to deal with connection tasks. | ||||
|     /// | ||||
|     /// Default is `tokio::spawn`. | ||||
|     pub fn executor<E2>(self, executor: E2) -> Builder<I, E2> { | ||||
|         Builder { | ||||
|             incoming: self.incoming, | ||||
|             protocol: self.protocol.with_executor(executor), | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     /// Consume this `Builder`, creating a [`Server`](Server). | ||||
|     /// | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// # #[cfg(feature = "tcp")] | ||||
|     /// # async fn run() { | ||||
|     /// use hyper::{Body, Error, Response, Server}; | ||||
|     /// use hyper::service::{make_service_fn, service_fn}; | ||||
|     /// | ||||
|     /// // Construct our SocketAddr to listen on... | ||||
|     /// let addr = ([127, 0, 0, 1], 3000).into(); | ||||
|     /// | ||||
|     /// // And a MakeService to handle each connection... | ||||
|     /// let make_svc = make_service_fn(|_| async { | ||||
|     ///     Ok::<_, Error>(service_fn(|_req| async { | ||||
|     ///         Ok::<_, Error>(Response::new(Body::from("Hello World"))) | ||||
|     ///     })) | ||||
|     /// }); | ||||
|     /// | ||||
|     /// // Then bind and serve... | ||||
|     /// let server = Server::bind(&addr) | ||||
|     ///     .serve(make_svc); | ||||
|     /// | ||||
|     /// // Run forever-ish... | ||||
|     /// if let Err(err) = server.await { | ||||
|     ///     eprintln!("server error: {}", err); | ||||
|     /// } | ||||
|     /// # } | ||||
|     /// ``` | ||||
|     pub fn serve<S, B>(self, new_service: S) -> Server<I, S, E> | ||||
|     where | ||||
|         I: Accept, | ||||
|         I::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         I::Conn: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|         S: MakeServiceRef<I::Conn, Body, ResBody = B>, | ||||
|         S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         B: HttpBody + 'static, | ||||
|         B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|         E: NewSvcExec<I::Conn, S::Future, S::Service, E, NoopWatcher>, | ||||
|         E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>, | ||||
|     { | ||||
|         let serve = self.protocol.serve(self.incoming, new_service); | ||||
|         let spawn_all = serve.spawn_all(); | ||||
|         Server { spawn_all } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[cfg(all(feature = "tcp", any(feature = "http1", feature = "http2")))] | ||||
| impl<E> Builder<AddrIncoming, E> { | ||||
|     /// Set whether TCP keepalive messages are enabled on accepted connections. | ||||
|     /// | ||||
|     /// If `None` is specified, keepalive is disabled, otherwise the duration | ||||
|     /// specified will be the time to remain idle before sending TCP keepalive | ||||
|     /// probes. | ||||
|     pub fn tcp_keepalive(mut self, keepalive: Option<Duration>) -> Self { | ||||
|         self.incoming.set_keepalive(keepalive); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the value of `TCP_NODELAY` option for accepted connections. | ||||
|     pub fn tcp_nodelay(mut self, enabled: bool) -> Self { | ||||
|         self.incoming.set_nodelay(enabled); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether to sleep on accept errors. | ||||
|     /// | ||||
|     /// A possible scenario is that the process has hit the max open files | ||||
|     /// allowed, and so trying to accept a new connection will fail with | ||||
|     /// EMFILE. In some cases, it's preferable to just wait for some time, if | ||||
|     /// the application will likely close some files (or connections), and try | ||||
|     /// to accept the connection again. If this option is true, the error will | ||||
|     /// be logged at the error level, since it is still a big deal, and then | ||||
|     /// the listener will sleep for 1 second. | ||||
|     /// | ||||
|     /// In other cases, hitting the max open files should be treat similarly | ||||
|     /// to being out-of-memory, and simply error (and shutdown). Setting this | ||||
|     /// option to false will allow that. | ||||
|     /// | ||||
|     /// For more details see [`AddrIncoming::set_sleep_on_errors`] | ||||
|     pub fn tcp_sleep_on_accept_errors(mut self, val: bool) -> Self { | ||||
|         self.incoming.set_sleep_on_errors(val); | ||||
|         self | ||||
|     } | ||||
| } | ||||
| @@ -1,127 +0,0 @@ | ||||
| use std::error::Error as StdError; | ||||
|  | ||||
| use pin_project_lite::pin_project; | ||||
| use tokio::io::{AsyncRead, AsyncWrite}; | ||||
| use tracing::debug; | ||||
|  | ||||
| use super::accept::Accept; | ||||
| use super::conn::{SpawnAll, UpgradeableConnection, Watcher}; | ||||
| use crate::body::{Body, HttpBody}; | ||||
| use crate::common::drain::{self, Draining, Signal, Watch, Watching}; | ||||
| use crate::common::exec::{ConnStreamExec, NewSvcExec}; | ||||
| use crate::common::{task, Future, Pin, Poll, Unpin}; | ||||
| use crate::service::{HttpService, MakeServiceRef}; | ||||
|  | ||||
| pin_project! { | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     pub struct Graceful<I, S, F, E> { | ||||
|         #[pin] | ||||
|         state: State<I, S, F, E>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pin_project! { | ||||
|     #[project = StateProj] | ||||
|     pub(super) enum State<I, S, F, E> { | ||||
|         Running { | ||||
|             drain: Option<(Signal, Watch)>, | ||||
|             #[pin] | ||||
|             spawn_all: SpawnAll<I, S, E>, | ||||
|             #[pin] | ||||
|             signal: F, | ||||
|         }, | ||||
|         Draining { draining: Draining }, | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<I, S, F, E> Graceful<I, S, F, E> { | ||||
|     pub(super) fn new(spawn_all: SpawnAll<I, S, E>, signal: F) -> Self { | ||||
|         let drain = Some(drain::channel()); | ||||
|         Graceful { | ||||
|             state: State::Running { | ||||
|                 drain, | ||||
|                 spawn_all, | ||||
|                 signal, | ||||
|             }, | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<I, IO, IE, S, B, F, E> Future for Graceful<I, S, F, E> | ||||
| where | ||||
|     I: Accept<Conn = IO, Error = IE>, | ||||
|     IE: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     IO: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     S: MakeServiceRef<IO, Body, ResBody = B>, | ||||
|     S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     B: HttpBody + 'static, | ||||
|     B::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     F: Future<Output = ()>, | ||||
|     E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>, | ||||
|     E: NewSvcExec<IO, S::Future, S::Service, E, GracefulWatcher>, | ||||
| { | ||||
|     type Output = crate::Result<()>; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         let mut me = self.project(); | ||||
|         loop { | ||||
|             let next = { | ||||
|                 match me.state.as_mut().project() { | ||||
|                     StateProj::Running { | ||||
|                         drain, | ||||
|                         spawn_all, | ||||
|                         signal, | ||||
|                     } => match signal.poll(cx) { | ||||
|                         Poll::Ready(()) => { | ||||
|                             debug!("signal received, starting graceful shutdown"); | ||||
|                             let sig = drain.take().expect("drain channel").0; | ||||
|                             State::Draining { | ||||
|                                 draining: sig.drain(), | ||||
|                             } | ||||
|                         } | ||||
|                         Poll::Pending => { | ||||
|                             let watch = drain.as_ref().expect("drain channel").1.clone(); | ||||
|                             return spawn_all.poll_watch(cx, &GracefulWatcher(watch)); | ||||
|                         } | ||||
|                     }, | ||||
|                     StateProj::Draining { ref mut draining } => { | ||||
|                         return Pin::new(draining).poll(cx).map(Ok); | ||||
|                     } | ||||
|                 } | ||||
|             }; | ||||
|             me.state.set(next); | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[allow(missing_debug_implementations)] | ||||
| #[derive(Clone)] | ||||
| pub struct GracefulWatcher(Watch); | ||||
|  | ||||
| impl<I, S, E> Watcher<I, S, E> for GracefulWatcher | ||||
| where | ||||
|     I: AsyncRead + AsyncWrite + Unpin + Send + 'static, | ||||
|     S: HttpService<Body>, | ||||
|     E: ConnStreamExec<S::Future, S::ResBody>, | ||||
|     S::ResBody: 'static, | ||||
|     <S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
| { | ||||
|     type Future = | ||||
|         Watching<UpgradeableConnection<I, S, E>, fn(Pin<&mut UpgradeableConnection<I, S, E>>)>; | ||||
|  | ||||
|     fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future { | ||||
|         self.0.clone().watch(conn, on_drain) | ||||
|     } | ||||
| } | ||||
|  | ||||
| fn on_drain<I, S, E>(conn: Pin<&mut UpgradeableConnection<I, S, E>>) | ||||
| where | ||||
|     S: HttpService<Body>, | ||||
|     S::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     I: AsyncRead + AsyncWrite + Unpin, | ||||
|     S::ResBody: HttpBody + 'static, | ||||
|     <S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     E: ConnStreamExec<S::Future, S::ResBody>, | ||||
| { | ||||
|     conn.graceful_shutdown() | ||||
| } | ||||
| @@ -1,302 +0,0 @@ | ||||
| use std::fmt; | ||||
| use std::io; | ||||
| use std::net::{SocketAddr, TcpListener as StdTcpListener}; | ||||
| use std::time::Duration; | ||||
|  | ||||
| use tokio::net::TcpListener; | ||||
| use tokio::time::Sleep; | ||||
| use tracing::{debug, error, trace}; | ||||
|  | ||||
| use crate::common::{task, Future, Pin, Poll}; | ||||
|  | ||||
| #[allow(unreachable_pub)] // https://github.com/rust-lang/rust/issues/57411 | ||||
| pub use self::addr_stream::AddrStream; | ||||
| use super::accept::Accept; | ||||
|  | ||||
| /// A stream of connections from binding to an address. | ||||
| #[must_use = "streams do nothing unless polled"] | ||||
| pub struct AddrIncoming { | ||||
|     addr: SocketAddr, | ||||
|     listener: TcpListener, | ||||
|     sleep_on_errors: bool, | ||||
|     tcp_keepalive_timeout: Option<Duration>, | ||||
|     tcp_nodelay: bool, | ||||
|     timeout: Option<Pin<Box<Sleep>>>, | ||||
| } | ||||
|  | ||||
| impl AddrIncoming { | ||||
|     pub(super) fn new(addr: &SocketAddr) -> crate::Result<Self> { | ||||
|         let std_listener = StdTcpListener::bind(addr).map_err(crate::Error::new_listen)?; | ||||
|  | ||||
|         AddrIncoming::from_std(std_listener) | ||||
|     } | ||||
|  | ||||
|     pub(super) fn from_std(std_listener: StdTcpListener) -> crate::Result<Self> { | ||||
|         // TcpListener::from_std doesn't set O_NONBLOCK | ||||
|         std_listener | ||||
|             .set_nonblocking(true) | ||||
|             .map_err(crate::Error::new_listen)?; | ||||
|         let listener = TcpListener::from_std(std_listener).map_err(crate::Error::new_listen)?; | ||||
|         AddrIncoming::from_listener(listener) | ||||
|     } | ||||
|  | ||||
|     /// Creates a new `AddrIncoming` binding to provided socket address. | ||||
|     pub fn bind(addr: &SocketAddr) -> crate::Result<Self> { | ||||
|         AddrIncoming::new(addr) | ||||
|     } | ||||
|  | ||||
|     /// Creates a new `AddrIncoming` from an existing `tokio::net::TcpListener`. | ||||
|     pub fn from_listener(listener: TcpListener) -> crate::Result<Self> { | ||||
|         let addr = listener.local_addr().map_err(crate::Error::new_listen)?; | ||||
|         Ok(AddrIncoming { | ||||
|             listener, | ||||
|             addr, | ||||
|             sleep_on_errors: true, | ||||
|             tcp_keepalive_timeout: None, | ||||
|             tcp_nodelay: false, | ||||
|             timeout: None, | ||||
|         }) | ||||
|     } | ||||
|  | ||||
|     /// Get the local address bound to this listener. | ||||
|     pub fn local_addr(&self) -> SocketAddr { | ||||
|         self.addr | ||||
|     } | ||||
|  | ||||
|     /// Set whether TCP keepalive messages are enabled on accepted connections. | ||||
|     /// | ||||
|     /// If `None` is specified, keepalive is disabled, otherwise the duration | ||||
|     /// specified will be the time to remain idle before sending TCP keepalive | ||||
|     /// probes. | ||||
|     pub fn set_keepalive(&mut self, keepalive: Option<Duration>) -> &mut Self { | ||||
|         self.tcp_keepalive_timeout = keepalive; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set the value of `TCP_NODELAY` option for accepted connections. | ||||
|     pub fn set_nodelay(&mut self, enabled: bool) -> &mut Self { | ||||
|         self.tcp_nodelay = enabled; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     /// Set whether to sleep on accept errors. | ||||
|     /// | ||||
|     /// A possible scenario is that the process has hit the max open files | ||||
|     /// allowed, and so trying to accept a new connection will fail with | ||||
|     /// `EMFILE`. In some cases, it's preferable to just wait for some time, if | ||||
|     /// the application will likely close some files (or connections), and try | ||||
|     /// to accept the connection again. If this option is `true`, the error | ||||
|     /// will be logged at the `error` level, since it is still a big deal, | ||||
|     /// and then the listener will sleep for 1 second. | ||||
|     /// | ||||
|     /// In other cases, hitting the max open files should be treat similarly | ||||
|     /// to being out-of-memory, and simply error (and shutdown). Setting | ||||
|     /// this option to `false` will allow that. | ||||
|     /// | ||||
|     /// Default is `true`. | ||||
|     pub fn set_sleep_on_errors(&mut self, val: bool) { | ||||
|         self.sleep_on_errors = val; | ||||
|     } | ||||
|  | ||||
|     fn poll_next_(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<AddrStream>> { | ||||
|         // Check if a previous timeout is active that was set by IO errors. | ||||
|         if let Some(ref mut to) = self.timeout { | ||||
|             ready!(Pin::new(to).poll(cx)); | ||||
|         } | ||||
|         self.timeout = None; | ||||
|  | ||||
|         loop { | ||||
|             match ready!(self.listener.poll_accept(cx)) { | ||||
|                 Ok((socket, addr)) => { | ||||
|                     if let Some(dur) = self.tcp_keepalive_timeout { | ||||
|                         let socket = socket2::SockRef::from(&socket); | ||||
|                         let conf = socket2::TcpKeepalive::new().with_time(dur); | ||||
|                         if let Err(e) = socket.set_tcp_keepalive(&conf) { | ||||
|                             trace!("error trying to set TCP keepalive: {}", e); | ||||
|                         } | ||||
|                     } | ||||
|                     if let Err(e) = socket.set_nodelay(self.tcp_nodelay) { | ||||
|                         trace!("error trying to set TCP nodelay: {}", e); | ||||
|                     } | ||||
|                     return Poll::Ready(Ok(AddrStream::new(socket, addr))); | ||||
|                 } | ||||
|                 Err(e) => { | ||||
|                     // Connection errors can be ignored directly, continue by | ||||
|                     // accepting the next request. | ||||
|                     if is_connection_error(&e) { | ||||
|                         debug!("accepted connection already errored: {}", e); | ||||
|                         continue; | ||||
|                     } | ||||
|  | ||||
|                     if self.sleep_on_errors { | ||||
|                         error!("accept error: {}", e); | ||||
|  | ||||
|                         // Sleep 1s. | ||||
|                         let mut timeout = Box::pin(tokio::time::sleep(Duration::from_secs(1))); | ||||
|  | ||||
|                         match timeout.as_mut().poll(cx) { | ||||
|                             Poll::Ready(()) => { | ||||
|                                 // Wow, it's been a second already? Ok then... | ||||
|                                 continue; | ||||
|                             } | ||||
|                             Poll::Pending => { | ||||
|                                 self.timeout = Some(timeout); | ||||
|                                 return Poll::Pending; | ||||
|                             } | ||||
|                         } | ||||
|                     } else { | ||||
|                         return Poll::Ready(Err(e)); | ||||
|                     } | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl Accept for AddrIncoming { | ||||
|     type Conn = AddrStream; | ||||
|     type Error = io::Error; | ||||
|  | ||||
|     fn poll_accept( | ||||
|         mut self: Pin<&mut Self>, | ||||
|         cx: &mut task::Context<'_>, | ||||
|     ) -> Poll<Option<Result<Self::Conn, Self::Error>>> { | ||||
|         let result = ready!(self.poll_next_(cx)); | ||||
|         Poll::Ready(Some(result)) | ||||
|     } | ||||
| } | ||||
|  | ||||
| /// This function defines errors that are per-connection. Which basically | ||||
| /// means that if we get this error from `accept()` system call it means | ||||
| /// next connection might be ready to be accepted. | ||||
| /// | ||||
| /// All other errors will incur a timeout before next `accept()` is performed. | ||||
| /// The timeout is useful to handle resource exhaustion errors like ENFILE | ||||
| /// and EMFILE. Otherwise, could enter into tight loop. | ||||
| fn is_connection_error(e: &io::Error) -> bool { | ||||
|     matches!(e.kind(), io::ErrorKind::ConnectionRefused | ||||
|         | io::ErrorKind::ConnectionAborted | ||||
|         | io::ErrorKind::ConnectionReset) | ||||
| } | ||||
|  | ||||
| impl fmt::Debug for AddrIncoming { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("AddrIncoming") | ||||
|             .field("addr", &self.addr) | ||||
|             .field("sleep_on_errors", &self.sleep_on_errors) | ||||
|             .field("tcp_keepalive_timeout", &self.tcp_keepalive_timeout) | ||||
|             .field("tcp_nodelay", &self.tcp_nodelay) | ||||
|             .finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| mod addr_stream { | ||||
|     use std::io; | ||||
|     use std::net::SocketAddr; | ||||
|     #[cfg(unix)] | ||||
|     use std::os::unix::io::{AsRawFd, RawFd}; | ||||
|     use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; | ||||
|     use tokio::net::TcpStream; | ||||
|  | ||||
|     use crate::common::{task, Pin, Poll}; | ||||
|  | ||||
|     pin_project_lite::pin_project! { | ||||
|         /// A transport returned yieled by `AddrIncoming`. | ||||
|         #[derive(Debug)] | ||||
|         pub struct AddrStream { | ||||
|             #[pin] | ||||
|             inner: TcpStream, | ||||
|             pub(super) remote_addr: SocketAddr, | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl AddrStream { | ||||
|         pub(super) fn new(tcp: TcpStream, addr: SocketAddr) -> AddrStream { | ||||
|             AddrStream { | ||||
|                 inner: tcp, | ||||
|                 remote_addr: addr, | ||||
|             } | ||||
|         } | ||||
|  | ||||
|         /// Returns the remote (peer) address of this connection. | ||||
|         #[inline] | ||||
|         pub fn remote_addr(&self) -> SocketAddr { | ||||
|             self.remote_addr | ||||
|         } | ||||
|  | ||||
|         /// Consumes the AddrStream and returns the underlying IO object | ||||
|         #[inline] | ||||
|         pub fn into_inner(self) -> TcpStream { | ||||
|             self.inner | ||||
|         } | ||||
|  | ||||
|         /// Attempt to receive data on the socket, without removing that data | ||||
|         /// from the queue, registering the current task for wakeup if data is | ||||
|         /// not yet available. | ||||
|         pub fn poll_peek( | ||||
|             &mut self, | ||||
|             cx: &mut task::Context<'_>, | ||||
|             buf: &mut tokio::io::ReadBuf<'_>, | ||||
|         ) -> Poll<io::Result<usize>> { | ||||
|             self.inner.poll_peek(cx, buf) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl AsyncRead for AddrStream { | ||||
|         #[inline] | ||||
|         fn poll_read( | ||||
|             self: Pin<&mut Self>, | ||||
|             cx: &mut task::Context<'_>, | ||||
|             buf: &mut ReadBuf<'_>, | ||||
|         ) -> Poll<io::Result<()>> { | ||||
|             self.project().inner.poll_read(cx, buf) | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     impl AsyncWrite for AddrStream { | ||||
|         #[inline] | ||||
|         fn poll_write( | ||||
|             self: Pin<&mut Self>, | ||||
|             cx: &mut task::Context<'_>, | ||||
|             buf: &[u8], | ||||
|         ) -> Poll<io::Result<usize>> { | ||||
|             self.project().inner.poll_write(cx, buf) | ||||
|         } | ||||
|  | ||||
|         #[inline] | ||||
|         fn poll_write_vectored( | ||||
|             self: Pin<&mut Self>, | ||||
|             cx: &mut task::Context<'_>, | ||||
|             bufs: &[io::IoSlice<'_>], | ||||
|         ) -> Poll<io::Result<usize>> { | ||||
|             self.project().inner.poll_write_vectored(cx, bufs) | ||||
|         } | ||||
|  | ||||
|         #[inline] | ||||
|         fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> { | ||||
|             // TCP flush is a noop | ||||
|             Poll::Ready(Ok(())) | ||||
|         } | ||||
|  | ||||
|         #[inline] | ||||
|         fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> { | ||||
|             self.project().inner.poll_shutdown(cx) | ||||
|         } | ||||
|  | ||||
|         #[inline] | ||||
|         fn is_write_vectored(&self) -> bool { | ||||
|             // Note that since `self.inner` is a `TcpStream`, this could | ||||
|             // *probably* be hard-coded to return `true`...but it seems more | ||||
|             // correct to ask it anyway (maybe we're on some platform without | ||||
|             // scatter-gather IO?) | ||||
|             self.inner.is_write_vectored() | ||||
|         } | ||||
|     } | ||||
|  | ||||
|     #[cfg(unix)] | ||||
|     impl AsRawFd for AddrStream { | ||||
|         fn as_raw_fd(&self) -> RawFd { | ||||
|             self.inner.as_raw_fd() | ||||
|         } | ||||
|     } | ||||
| } | ||||
| @@ -1,187 +0,0 @@ | ||||
| use std::error::Error as StdError; | ||||
| use std::fmt; | ||||
|  | ||||
| use tokio::io::{AsyncRead, AsyncWrite}; | ||||
|  | ||||
| use super::{HttpService, Service}; | ||||
| use crate::body::HttpBody; | ||||
| use crate::common::{task, Future, Poll}; | ||||
|  | ||||
| // The same "trait alias" as tower::MakeConnection, but inlined to reduce | ||||
| // dependencies. | ||||
| pub trait MakeConnection<Target>: self::sealed::Sealed<(Target,)> { | ||||
|     type Connection: AsyncRead + AsyncWrite; | ||||
|     type Error; | ||||
|     type Future: Future<Output = Result<Self::Connection, Self::Error>>; | ||||
|  | ||||
|     fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>; | ||||
|     fn make_connection(&mut self, target: Target) -> Self::Future; | ||||
| } | ||||
|  | ||||
| impl<S, Target> self::sealed::Sealed<(Target,)> for S where S: Service<Target> {} | ||||
|  | ||||
| impl<S, Target> MakeConnection<Target> for S | ||||
| where | ||||
|     S: Service<Target>, | ||||
|     S::Response: AsyncRead + AsyncWrite, | ||||
| { | ||||
|     type Connection = S::Response; | ||||
|     type Error = S::Error; | ||||
|     type Future = S::Future; | ||||
|  | ||||
|     fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||
|         Service::poll_ready(self, cx) | ||||
|     } | ||||
|  | ||||
|     fn make_connection(&mut self, target: Target) -> Self::Future { | ||||
|         Service::call(self, target) | ||||
|     } | ||||
| } | ||||
|  | ||||
| // Just a sort-of "trait alias" of `MakeService`, not to be implemented | ||||
| // by anyone, only used as bounds. | ||||
| pub trait MakeServiceRef<Target, ReqBody>: self::sealed::Sealed<(Target, ReqBody)> { | ||||
|     type ResBody: HttpBody; | ||||
|     type Error: Into<Box<dyn StdError + Send + Sync>>; | ||||
|     type Service: HttpService<ReqBody, ResBody = Self::ResBody, Error = Self::Error>; | ||||
|     type MakeError: Into<Box<dyn StdError + Send + Sync>>; | ||||
|     type Future: Future<Output = Result<Self::Service, Self::MakeError>>; | ||||
|  | ||||
|     // Acting like a #[non_exhaustive] for associated types of this trait. | ||||
|     // | ||||
|     // Basically, no one outside of hyper should be able to set this type | ||||
|     // or declare bounds on it, so it should prevent people from creating | ||||
|     // trait objects or otherwise writing code that requires using *all* | ||||
|     // of the associated types. | ||||
|     // | ||||
|     // Why? So we can add new associated types to this alias in the future, | ||||
|     // if necessary. | ||||
|     type __DontNameMe: self::sealed::CantImpl; | ||||
|  | ||||
|     fn poll_ready_ref(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::MakeError>>; | ||||
|  | ||||
|     fn make_service_ref(&mut self, target: &Target) -> Self::Future; | ||||
| } | ||||
|  | ||||
| impl<T, Target, E, ME, S, F, IB, OB> MakeServiceRef<Target, IB> for T | ||||
| where | ||||
|     T: for<'a> Service<&'a Target, Error = ME, Response = S, Future = F>, | ||||
|     E: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     ME: Into<Box<dyn StdError + Send + Sync>>, | ||||
|     S: HttpService<IB, ResBody = OB, Error = E>, | ||||
|     F: Future<Output = Result<S, ME>>, | ||||
|     IB: HttpBody, | ||||
|     OB: HttpBody, | ||||
| { | ||||
|     type Error = E; | ||||
|     type Service = S; | ||||
|     type ResBody = OB; | ||||
|     type MakeError = ME; | ||||
|     type Future = F; | ||||
|  | ||||
|     type __DontNameMe = self::sealed::CantName; | ||||
|  | ||||
|     fn poll_ready_ref(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::MakeError>> { | ||||
|         self.poll_ready(cx) | ||||
|     } | ||||
|  | ||||
|     fn make_service_ref(&mut self, target: &Target) -> Self::Future { | ||||
|         self.call(target) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<T, Target, S, B1, B2> self::sealed::Sealed<(Target, B1)> for T | ||||
| where | ||||
|     T: for<'a> Service<&'a Target, Response = S>, | ||||
|     S: HttpService<B1, ResBody = B2>, | ||||
|     B1: HttpBody, | ||||
|     B2: HttpBody, | ||||
| { | ||||
| } | ||||
|  | ||||
| /// Create a `MakeService` from a function. | ||||
| /// | ||||
| /// # Example | ||||
| /// | ||||
| /// ``` | ||||
| /// # #[cfg(feature = "runtime")] | ||||
| /// # async fn run() { | ||||
| /// use std::convert::Infallible; | ||||
| /// use hyper::{Body, Request, Response, Server}; | ||||
| /// use hyper::server::conn::AddrStream; | ||||
| /// use hyper::service::{make_service_fn, service_fn}; | ||||
| /// | ||||
| /// let addr = ([127, 0, 0, 1], 3000).into(); | ||||
| /// | ||||
| /// let make_svc = make_service_fn(|socket: &AddrStream| { | ||||
| ///     let remote_addr = socket.remote_addr(); | ||||
| ///     async move { | ||||
| ///         Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move { | ||||
| ///             Ok::<_, Infallible>( | ||||
| ///                 Response::new(Body::from(format!("Hello, {}!", remote_addr))) | ||||
| ///             ) | ||||
| ///         })) | ||||
| ///     } | ||||
| /// }); | ||||
| /// | ||||
| /// // Then bind and serve... | ||||
| /// let server = Server::bind(&addr) | ||||
| ///     .serve(make_svc); | ||||
| /// | ||||
| /// // Finally, spawn `server` onto an Executor... | ||||
| /// if let Err(e) = server.await { | ||||
| ///     eprintln!("server error: {}", e); | ||||
| /// } | ||||
| /// # } | ||||
| /// # fn main() {} | ||||
| /// ``` | ||||
| pub fn make_service_fn<F, Target, Ret>(f: F) -> MakeServiceFn<F> | ||||
| where | ||||
|     F: FnMut(&Target) -> Ret, | ||||
|     Ret: Future, | ||||
| { | ||||
|     MakeServiceFn { f } | ||||
| } | ||||
|  | ||||
| /// `MakeService` returned from [`make_service_fn`] | ||||
| #[derive(Clone, Copy)] | ||||
| pub struct MakeServiceFn<F> { | ||||
|     f: F, | ||||
| } | ||||
|  | ||||
| impl<'t, F, Ret, Target, Svc, MkErr> Service<&'t Target> for MakeServiceFn<F> | ||||
| where | ||||
|     F: FnMut(&Target) -> Ret, | ||||
|     Ret: Future<Output = Result<Svc, MkErr>>, | ||||
|     MkErr: Into<Box<dyn StdError + Send + Sync>>, | ||||
| { | ||||
|     type Error = MkErr; | ||||
|     type Response = Svc; | ||||
|     type Future = Ret; | ||||
|  | ||||
|     fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||
|         Poll::Ready(Ok(())) | ||||
|     } | ||||
|  | ||||
|     fn call(&mut self, target: &'t Target) -> Self::Future { | ||||
|         (self.f)(target) | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<F> fmt::Debug for MakeServiceFn<F> { | ||||
|     fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { | ||||
|         f.debug_struct("MakeServiceFn").finish() | ||||
|     } | ||||
| } | ||||
|  | ||||
| mod sealed { | ||||
|     pub trait Sealed<X> {} | ||||
|  | ||||
|     #[allow(unreachable_pub)] // This is intentional. | ||||
|     pub trait CantImpl {} | ||||
|  | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     pub enum CantName {} | ||||
|  | ||||
|     impl CantImpl for CantName {} | ||||
| } | ||||
| @@ -10,10 +10,6 @@ | ||||
| //! | ||||
| //! - `HttpService`: This is blanketly implemented for all types that | ||||
| //!   implement `Service<http::Request<B1>, Response = http::Response<B2>>`. | ||||
| //! - `MakeService`: When a `Service` returns a new `Service` as its "response", | ||||
| //!   we consider it a `MakeService`. Again, blanketly implemented in those cases. | ||||
| //! - `MakeConnection`: A `Service` that returns a "connection", a type that | ||||
| //!   implements `AsyncRead` and `AsyncWrite`. | ||||
| //! | ||||
| //! # HttpService | ||||
| //! | ||||
| @@ -24,32 +20,13 @@ | ||||
| //! The helper [`service_fn`](service_fn) should be sufficient for most cases, but | ||||
| //! if you need to implement `Service` for a type manually, you can follow the example | ||||
| //! in `service_struct_impl.rs`. | ||||
| //! | ||||
| //! # MakeService | ||||
| //! | ||||
| //! Since a `Service` is bound to a single connection, a [`Server`](crate::Server) | ||||
| //! needs a way to make them as it accepts connections. This is what a | ||||
| //! `MakeService` does. | ||||
| //! | ||||
| //! Resources that need to be shared by all `Service`s can be put into a | ||||
| //! `MakeService`, and then passed to individual `Service`s when `call` | ||||
| //! is called. | ||||
|  | ||||
| pub use tower_service::Service; | ||||
|  | ||||
| mod http; | ||||
| mod make; | ||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))] | ||||
| mod oneshot; | ||||
| mod util; | ||||
|  | ||||
| pub(super) use self::http::HttpService; | ||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))] | ||||
| pub(super) use self::make::MakeConnection; | ||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "server"))] | ||||
| pub(super) use self::make::MakeServiceRef; | ||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))] | ||||
| pub(super) use self::oneshot::{oneshot, Oneshot}; | ||||
| pub(super) use self::http::HttpService; | ||||
|  | ||||
| pub use self::make::make_service_fn; | ||||
| pub use self::util::service_fn; | ||||
|   | ||||
| @@ -1,73 +0,0 @@ | ||||
| // TODO: Eventually to be replaced with tower_util::Oneshot. | ||||
|  | ||||
| use pin_project_lite::pin_project; | ||||
| use tower_service::Service; | ||||
|  | ||||
| use crate::common::{task, Future, Pin, Poll}; | ||||
|  | ||||
| pub(crate) fn oneshot<S, Req>(svc: S, req: Req) -> Oneshot<S, Req> | ||||
| where | ||||
|     S: Service<Req>, | ||||
| { | ||||
|     Oneshot { | ||||
|         state: State::NotReady { svc, req }, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pin_project! { | ||||
|     // A `Future` consuming a `Service` and request, waiting until the `Service` | ||||
|     // is ready, and then calling `Service::call` with the request, and | ||||
|     // waiting for that `Future`. | ||||
|     #[allow(missing_debug_implementations)] | ||||
|     pub struct Oneshot<S: Service<Req>, Req> { | ||||
|         #[pin] | ||||
|         state: State<S, Req>, | ||||
|     } | ||||
| } | ||||
|  | ||||
| pin_project! { | ||||
|     #[project = StateProj] | ||||
|     #[project_replace = StateProjOwn] | ||||
|     enum State<S: Service<Req>, Req> { | ||||
|         NotReady { | ||||
|             svc: S, | ||||
|             req: Req, | ||||
|         }, | ||||
|         Called { | ||||
|             #[pin] | ||||
|             fut: S::Future, | ||||
|         }, | ||||
|         Tmp, | ||||
|     } | ||||
| } | ||||
|  | ||||
| impl<S, Req> Future for Oneshot<S, Req> | ||||
| where | ||||
|     S: Service<Req>, | ||||
| { | ||||
|     type Output = Result<S::Response, S::Error>; | ||||
|  | ||||
|     fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> { | ||||
|         let mut me = self.project(); | ||||
|  | ||||
|         loop { | ||||
|             match me.state.as_mut().project() { | ||||
|                 StateProj::NotReady { ref mut svc, .. } => { | ||||
|                     ready!(svc.poll_ready(cx))?; | ||||
|                     // fallthrough out of the match's borrow | ||||
|                 } | ||||
|                 StateProj::Called { fut } => { | ||||
|                     return fut.poll(cx); | ||||
|                 } | ||||
|                 StateProj::Tmp => unreachable!(), | ||||
|             } | ||||
|  | ||||
|             match me.state.as_mut().project_replace(State::Tmp) { | ||||
|                 StateProjOwn::NotReady { mut svc, req } => { | ||||
|                     me.state.set(State::Called { fut: svc.call(req) }); | ||||
|                 } | ||||
|                 _ => unreachable!(), | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| } | ||||
							
								
								
									
										1380
									
								
								tests/client.rs
									
									
									
									
									
								
							
							
						
						
									
										1380
									
								
								tests/client.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										340
									
								
								tests/server.rs
									
									
									
									
									
								
							
							
						
						
									
										340
									
								
								tests/server.rs
									
									
									
									
									
								
							| @@ -1,6 +1,7 @@ | ||||
| #![deny(warnings)] | ||||
| #![deny(rust_2018_idioms)] | ||||
|  | ||||
| use std::convert::TryInto; | ||||
| use std::future::Future; | ||||
| use std::io::{self, Read, Write}; | ||||
| use std::net::TcpListener as StdTcpListener; | ||||
| @@ -16,20 +17,18 @@ use std::time::Duration; | ||||
| use bytes::Bytes; | ||||
| use futures_channel::oneshot; | ||||
| use futures_util::future::{self, Either, FutureExt, TryFutureExt}; | ||||
| #[cfg(feature = "stream")] | ||||
| use futures_util::stream::StreamExt as _; | ||||
| use h2::client::SendRequest; | ||||
| use h2::{RecvStream, SendStream}; | ||||
| use http::header::{HeaderName, HeaderValue}; | ||||
| use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; | ||||
| use tokio::net::{TcpListener, TcpStream as TkTcpStream}; | ||||
| use http_body_util::{combinators::BoxBody, BodyExt, StreamBody}; | ||||
| use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; | ||||
| use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
| use tokio::net::{TcpListener as TkTcpListener, TcpListener, TcpStream as TkTcpStream}; | ||||
|  | ||||
| use hyper::body::HttpBody as _; | ||||
| use hyper::client::Client; | ||||
| use hyper::body::HttpBody; | ||||
| use hyper::server::conn::Http; | ||||
| use hyper::server::Server; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Request, Response, StatusCode, Version}; | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Method, Request, Response, StatusCode, Uri, Version}; | ||||
|  | ||||
| mod support; | ||||
|  | ||||
| @@ -109,8 +108,7 @@ mod response_body_lengths { | ||||
|                 b | ||||
|             } | ||||
|             Bd::Unknown(b) => { | ||||
|                 let (mut tx, body) = hyper::Body::channel(); | ||||
|                 tx.try_send_data(b.into()).expect("try_send_data"); | ||||
|                 let body = futures_util::stream::once(async move { Ok(b.into()) }); | ||||
|                 reply.body_stream(body); | ||||
|                 b | ||||
|             } | ||||
| @@ -321,15 +319,11 @@ mod response_body_lengths { | ||||
|  | ||||
|     #[tokio::test] | ||||
|     async fn http2_auto_response_with_known_length() { | ||||
|         use http_body::Body; | ||||
|  | ||||
|         let server = serve(); | ||||
|         let addr_str = format!("http://{}", server.addr()); | ||||
|         server.reply().body("Hello, World!"); | ||||
|  | ||||
|         let client = Client::builder() | ||||
|             .http2_only(true) | ||||
|             .build_http::<hyper::Body>(); | ||||
|         let client = TestClient::new().http2_only(); | ||||
|         let uri = addr_str | ||||
|             .parse::<hyper::Uri>() | ||||
|             .expect("server addr should parse"); | ||||
| @@ -341,8 +335,6 @@ mod response_body_lengths { | ||||
|  | ||||
|     #[tokio::test] | ||||
|     async fn http2_auto_response_with_conflicting_lengths() { | ||||
|         use http_body::Body; | ||||
|  | ||||
|         let server = serve(); | ||||
|         let addr_str = format!("http://{}", server.addr()); | ||||
|         server | ||||
| @@ -350,9 +342,7 @@ mod response_body_lengths { | ||||
|             .header("content-length", "10") | ||||
|             .body("Hello, World!"); | ||||
|  | ||||
|         let client = Client::builder() | ||||
|             .http2_only(true) | ||||
|             .build_http::<hyper::Body>(); | ||||
|         let client = TestClient::new().http2_only(); | ||||
|         let uri = addr_str | ||||
|             .parse::<hyper::Uri>() | ||||
|             .expect("server addr should parse"); | ||||
| @@ -364,15 +354,11 @@ mod response_body_lengths { | ||||
|  | ||||
|     #[tokio::test] | ||||
|     async fn http2_implicit_empty_size_hint() { | ||||
|         use http_body::Body; | ||||
|  | ||||
|         let server = serve(); | ||||
|         let addr_str = format!("http://{}", server.addr()); | ||||
|         server.reply(); | ||||
|  | ||||
|         let client = Client::builder() | ||||
|             .http2_only(true) | ||||
|             .build_http::<hyper::Body>(); | ||||
|         let client = TestClient::new().http2_only(); | ||||
|         let uri = addr_str | ||||
|             .parse::<hyper::Uri>() | ||||
|             .expect("server addr should parse"); | ||||
| @@ -383,6 +369,33 @@ mod response_body_lengths { | ||||
|     } | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn get_response_custom_reason_phrase() { | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let server = serve(); | ||||
|     server.reply().reason_phrase("Cool"); | ||||
|     let mut req = connect(server.addr()); | ||||
|     req.write_all( | ||||
|         b"\ | ||||
|         GET / HTTP/1.1\r\n\ | ||||
|         Host: example.domain\r\n\ | ||||
|         Connection: close\r\n\ | ||||
|         \r\n\ | ||||
|     ", | ||||
|     ) | ||||
|     .unwrap(); | ||||
|  | ||||
|     let mut response = String::new(); | ||||
|     req.read_to_string(&mut response).unwrap(); | ||||
|  | ||||
|     let mut lines = response.lines(); | ||||
|     assert_eq!(lines.next(), Some("HTTP/1.1 200 Cool")); | ||||
|  | ||||
|     let mut lines = lines.skip_while(|line| !line.is_empty()); | ||||
|     assert_eq!(lines.next(), Some("")); | ||||
|     assert_eq!(lines.next(), None); | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn get_chunked_response_with_ka() { | ||||
|     let foo_bar = b"foo bar baz"; | ||||
| @@ -1454,8 +1467,6 @@ async fn header_read_timeout_slow_writes_multiple_requests() { | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn upgrades() { | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
| @@ -1513,8 +1524,6 @@ async fn upgrades() { | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn http_connect() { | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
| @@ -1649,15 +1658,19 @@ async fn upgrades_ignored() { | ||||
|             future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty())) | ||||
|         }); | ||||
|  | ||||
|         let (socket, _) = listener.accept().await.unwrap(); | ||||
|         Http::new() | ||||
|             .serve_connection(socket, svc) | ||||
|             .with_upgrades() | ||||
|             .await | ||||
|             .expect("server task"); | ||||
|         loop { | ||||
|             let (socket, _) = listener.accept().await.unwrap(); | ||||
|             tokio::task::spawn(async move { | ||||
|                 Http::new() | ||||
|                     .serve_connection(socket, svc) | ||||
|                     .with_upgrades() | ||||
|                     .await | ||||
|                     .expect("server task"); | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let client = hyper::Client::new(); | ||||
|     let client = TestClient::new(); | ||||
|     let url = format!("http://{}/", addr); | ||||
|  | ||||
|     let make_req = || { | ||||
| @@ -1679,8 +1692,6 @@ async fn upgrades_ignored() { | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn http_connect_new() { | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
| @@ -1745,8 +1756,6 @@ async fn http_connect_new() { | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn h2_connect() { | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
| @@ -1816,7 +1825,7 @@ async fn h2_connect() { | ||||
| #[tokio::test] | ||||
| async fn h2_connect_multiplex() { | ||||
|     use futures_util::stream::FuturesUnordered; | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|     use futures_util::StreamExt; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
| @@ -1927,8 +1936,6 @@ async fn h2_connect_multiplex() { | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn h2_connect_large_body() { | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
| @@ -2004,8 +2011,6 @@ async fn h2_connect_large_body() { | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn h2_connect_empty_frames() { | ||||
|     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||
|     let addr = listener.local_addr().unwrap(); | ||||
| @@ -2164,17 +2169,17 @@ async fn max_buf_size() { | ||||
|         .expect_err("should TooLarge error"); | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "stream")] | ||||
| #[test] | ||||
| fn streaming_body() { | ||||
|     use futures_util::StreamExt; | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|     // disable keep-alive so we can use read_to_end | ||||
|     let server = serve_opts().keep_alive(false).serve(); | ||||
|  | ||||
|     static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _; | ||||
|     let b = futures_util::stream::iter(S.iter()).map(|&s| Ok::<_, hyper::Error>(s)); | ||||
|     let b = hyper::Body::wrap_stream(b); | ||||
|     static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 100] as _; | ||||
|     let b = | ||||
|         futures_util::stream::iter(S.iter()).map(|&s| Ok::<_, BoxError>(Bytes::copy_from_slice(s))); | ||||
|     server.reply().body_stream(b); | ||||
|  | ||||
|     let mut tcp = connect(server.addr()); | ||||
| @@ -2198,8 +2203,8 @@ fn http1_response_with_http2_version() { | ||||
|  | ||||
|     server.reply().version(hyper::Version::HTTP_2); | ||||
|  | ||||
|     let client = TestClient::new(); | ||||
|     rt.block_on({ | ||||
|         let client = Client::new(); | ||||
|         let uri = addr_str.parse().expect("server addr should parse"); | ||||
|         client.get(uri) | ||||
|     }) | ||||
| @@ -2213,10 +2218,8 @@ fn try_h2() { | ||||
|  | ||||
|     let rt = support::runtime(); | ||||
|  | ||||
|     let client = TestClient::new().http2_only(); | ||||
|     rt.block_on({ | ||||
|         let client = Client::builder() | ||||
|             .http2_only(true) | ||||
|             .build_http::<hyper::Body>(); | ||||
|         let uri = addr_str.parse().expect("server addr should parse"); | ||||
|  | ||||
|         client.get(uri).map_ok(|_| ()).map_err(|_e| ()) | ||||
| @@ -2233,10 +2236,8 @@ fn http1_only() { | ||||
|  | ||||
|     let rt = support::runtime(); | ||||
|  | ||||
|     let client = TestClient::new().http2_only(); | ||||
|     rt.block_on({ | ||||
|         let client = Client::builder() | ||||
|             .http2_only(true) | ||||
|             .build_http::<hyper::Body>(); | ||||
|         let uri = addr_str.parse().expect("server addr should parse"); | ||||
|         client.get(uri) | ||||
|     }) | ||||
| @@ -2256,9 +2257,8 @@ async fn http2_service_error_sends_reset_reason() { | ||||
|  | ||||
|     let uri = addr_str.parse().expect("server addr should parse"); | ||||
|     dbg!("start"); | ||||
|     let err = dbg!(Client::builder() | ||||
|         .http2_only(true) | ||||
|         .build_http::<hyper::Body>() | ||||
|     let err = dbg!(TestClient::new() | ||||
|         .http2_only() | ||||
|         .get(uri) | ||||
|         .await | ||||
|         .expect_err("client.get")); | ||||
| @@ -2272,32 +2272,28 @@ async fn http2_service_error_sends_reset_reason() { | ||||
|     assert_eq!(h2_err.reason(), Some(h2::Reason::INADEQUATE_SECURITY)); | ||||
| } | ||||
|  | ||||
| #[cfg(feature = "stream")] | ||||
| #[test] | ||||
| fn http2_body_user_error_sends_reset_reason() { | ||||
|     use std::error::Error; | ||||
|     let server = serve(); | ||||
|     let addr_str = format!("http://{}", server.addr()); | ||||
|  | ||||
|     let b = futures_util::stream::once(future::err::<String, _>(h2::Error::from( | ||||
|     let b = futures_util::stream::once(future::err::<Bytes, BoxError>(Box::new(h2::Error::from( | ||||
|         h2::Reason::INADEQUATE_SECURITY, | ||||
|     ))); | ||||
|     let b = hyper::Body::wrap_stream(b); | ||||
|  | ||||
|     )))); | ||||
|     server.reply().body_stream(b); | ||||
|  | ||||
|     let rt = support::runtime(); | ||||
|  | ||||
|     let err: hyper::Error = rt | ||||
|         .block_on(async move { | ||||
|             let client = Client::builder() | ||||
|                 .http2_only(true) | ||||
|                 .build_http::<hyper::Body>(); | ||||
|             let client = TestClient::new().http2_only(); | ||||
|  | ||||
|             let uri = addr_str.parse().expect("server addr should parse"); | ||||
|  | ||||
|             let mut res = client.get(uri).await?; | ||||
|  | ||||
|             while let Some(chunk) = res.body_mut().next().await { | ||||
|             while let Some(chunk) = res.body_mut().data().await { | ||||
|                 chunk?; | ||||
|             } | ||||
|             Ok(()) | ||||
| @@ -2339,22 +2335,33 @@ async fn http2_service_poll_ready_error_sends_goaway() { | ||||
|  | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
|  | ||||
|     let server = hyper::Server::bind(&([127, 0, 0, 1], 0).into()) | ||||
|         .http2_only(true) | ||||
|         .serve(make_service_fn(|_| async move { | ||||
|             Ok::<_, BoxError>(Http2ReadyErrorSvc) | ||||
|         })); | ||||
|     let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|     let addr_str = format!("http://{}", server.local_addr()); | ||||
|     let addr_str = format!("http://{}", listener.local_addr().unwrap()); | ||||
|  | ||||
|     tokio::task::spawn(async move { | ||||
|         server.await.expect("server"); | ||||
|         loop { | ||||
|             tokio::select! { | ||||
|                 res = listener.accept() => { | ||||
|                     let (stream, _) = res.unwrap(); | ||||
|  | ||||
|                     tokio::task::spawn(async move { | ||||
|                         let mut http = Http::new(); | ||||
|                         http.http2_only(true); | ||||
|  | ||||
|                         let service = Http2ReadyErrorSvc; | ||||
|                         http.serve_connection(stream, service).await.unwrap(); | ||||
|                     }); | ||||
|                 } | ||||
|             } | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let uri = addr_str.parse().expect("server addr should parse"); | ||||
|     let err = dbg!(Client::builder() | ||||
|         .http2_only(true) | ||||
|         .build_http::<hyper::Body>() | ||||
|     let err = dbg!(TestClient::new() | ||||
|         .http2_only() | ||||
|         .get(uri) | ||||
|         .await | ||||
|         .expect_err("client.get should fail")); | ||||
| @@ -2421,6 +2428,26 @@ fn skips_content_length_and_body_for_304_responses() { | ||||
|     assert_eq!(lines.next(), None); | ||||
| } | ||||
|  | ||||
| #[test] | ||||
| fn no_implicit_zero_content_length_for_head_responses() { | ||||
|     let server = serve(); | ||||
|     server.reply().status(hyper::StatusCode::OK).body([]); | ||||
|     let mut req = connect(server.addr()); | ||||
|     req.write_all( | ||||
|         b"\ | ||||
|         HEAD / HTTP/1.1\r\n\ | ||||
|         Host: example.domain\r\n\ | ||||
|         Connection: close\r\n\ | ||||
|         \r\n\ | ||||
|     ", | ||||
|     ) | ||||
|     .unwrap(); | ||||
|  | ||||
|     let mut response = String::new(); | ||||
|     req.read_to_string(&mut response).unwrap(); | ||||
|     assert!(!response.contains("content-length:")); | ||||
| } | ||||
|  | ||||
| #[tokio::test] | ||||
| async fn http2_keep_alive_detects_unresponsive_client() { | ||||
|     let _ = pretty_env_logger::try_init(); | ||||
| @@ -2639,7 +2666,7 @@ impl Serve { | ||||
| } | ||||
|  | ||||
| type BoxError = Box<dyn std::error::Error + Send + Sync>; | ||||
| type BoxFuture = Pin<Box<dyn Future<Output = Result<Response<Body>, BoxError>> + Send>>; | ||||
| type BoxFuture = Pin<Box<dyn Future<Output = Result<Response<ReplyBody>, BoxError>> + Send>>; | ||||
|  | ||||
| struct ReplyBuilder<'a> { | ||||
|     tx: &'a Mutex<spmc::Sender<Reply>>, | ||||
| @@ -2651,6 +2678,17 @@ impl<'a> ReplyBuilder<'a> { | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     fn reason_phrase(self, reason: &str) -> Self { | ||||
|         self.tx | ||||
|             .lock() | ||||
|             .unwrap() | ||||
|             .send(Reply::ReasonPhrase( | ||||
|                 reason.as_bytes().try_into().expect("reason phrase"), | ||||
|             )) | ||||
|             .unwrap(); | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     fn version(self, version: hyper::Version) -> Self { | ||||
|         self.tx | ||||
|             .lock() | ||||
| @@ -2672,14 +2710,16 @@ impl<'a> ReplyBuilder<'a> { | ||||
|     } | ||||
|  | ||||
|     fn body<T: AsRef<[u8]>>(self, body: T) { | ||||
|         self.tx | ||||
|             .lock() | ||||
|             .unwrap() | ||||
|             .send(Reply::Body(body.as_ref().to_vec().into())) | ||||
|             .unwrap(); | ||||
|         let chunk = Bytes::copy_from_slice(body.as_ref()); | ||||
|         let body = BodyExt::boxed(http_body_util::Full::new(chunk).map_err(|e| match e {})); | ||||
|         self.tx.lock().unwrap().send(Reply::Body(body)).unwrap(); | ||||
|     } | ||||
|  | ||||
|     fn body_stream(self, body: Body) { | ||||
|     fn body_stream<S>(self, stream: S) | ||||
|     where | ||||
|         S: futures_util::Stream<Item = Result<Bytes, BoxError>> + Send + Sync + 'static, | ||||
|     { | ||||
|         let body = BodyExt::boxed(StreamBody::new(stream)); | ||||
|         self.tx.lock().unwrap().send(Reply::Body(body)).unwrap(); | ||||
|     } | ||||
|  | ||||
| @@ -2721,12 +2761,15 @@ struct TestService { | ||||
|     reply: spmc::Receiver<Reply>, | ||||
| } | ||||
|  | ||||
| type ReplyBody = BoxBody<Bytes, BoxError>; | ||||
|  | ||||
| #[derive(Debug)] | ||||
| enum Reply { | ||||
|     Status(hyper::StatusCode), | ||||
|     ReasonPhrase(hyper::ext::ReasonPhrase), | ||||
|     Version(hyper::Version), | ||||
|     Header(HeaderName, HeaderValue), | ||||
|     Body(hyper::Body), | ||||
|     Body(ReplyBody), | ||||
|     Error(BoxError), | ||||
|     End, | ||||
| } | ||||
| @@ -2739,7 +2782,7 @@ enum Msg { | ||||
| } | ||||
|  | ||||
| impl tower_service::Service<Request<Body>> for TestService { | ||||
|     type Response = Response<Body>; | ||||
|     type Response = Response<ReplyBody>; | ||||
|     type Error = BoxError; | ||||
|     type Future = BoxFuture; | ||||
|  | ||||
| @@ -2772,13 +2815,18 @@ impl tower_service::Service<Request<Body>> for TestService { | ||||
| } | ||||
|  | ||||
| impl TestService { | ||||
|     fn build_reply(replies: spmc::Receiver<Reply>) -> Result<Response<Body>, BoxError> { | ||||
|         let mut res = Response::new(Body::empty()); | ||||
|     fn build_reply(replies: spmc::Receiver<Reply>) -> Result<Response<ReplyBody>, BoxError> { | ||||
|         let empty = | ||||
|             BodyExt::boxed(http_body_util::Empty::new().map_err(|e| -> BoxError { match e {} })); | ||||
|         let mut res = Response::new(empty); | ||||
|         while let Ok(reply) = replies.try_recv() { | ||||
|             match reply { | ||||
|                 Reply::Status(s) => { | ||||
|                     *res.status_mut() = s; | ||||
|                 } | ||||
|                 Reply::ReasonPhrase(reason) => { | ||||
|                     res.extensions_mut().insert(reason); | ||||
|                 } | ||||
|                 Reply::Version(v) => { | ||||
|                     *res.version_mut() = v; | ||||
|                 } | ||||
| @@ -2817,7 +2865,7 @@ impl tower_service::Service<Request<Body>> for HelloWorld { | ||||
|  | ||||
| fn unreachable_service() -> impl tower_service::Service< | ||||
|     http::Request<hyper::Body>, | ||||
|     Response = http::Response<hyper::Body>, | ||||
|     Response = http::Response<ReplyBody>, | ||||
|     Error = BoxError, | ||||
|     Future = BoxFuture, | ||||
| > { | ||||
| @@ -2883,9 +2931,9 @@ impl ServeOptions { | ||||
|         let (addr_tx, addr_rx) = mpsc::channel(); | ||||
|         let (msg_tx, msg_rx) = mpsc::channel(); | ||||
|         let (reply_tx, reply_rx) = spmc::channel(); | ||||
|         let (shutdown_tx, shutdown_rx) = oneshot::channel(); | ||||
|         let (shutdown_tx, mut shutdown_rx) = oneshot::channel(); | ||||
|  | ||||
|         let addr = ([127, 0, 0, 1], 0).into(); | ||||
|         let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); | ||||
|  | ||||
|         let thread_name = format!( | ||||
|             "test-server-{}", | ||||
| @@ -2896,36 +2944,46 @@ impl ServeOptions { | ||||
|         let thread = thread::Builder::new() | ||||
|             .name(thread_name) | ||||
|             .spawn(move || { | ||||
|                 support::runtime() | ||||
|                     .block_on(async move { | ||||
|                         let service = make_service_fn(|_| { | ||||
|                             let msg_tx = msg_tx.clone(); | ||||
|                             let reply_rx = reply_rx.clone(); | ||||
|                             future::ok::<_, BoxError>(TestService { | ||||
|                                 tx: msg_tx, | ||||
|                                 reply: reply_rx, | ||||
|                             }) | ||||
|                         }); | ||||
|                 support::runtime().block_on(async move { | ||||
|                     let listener = TkTcpListener::bind(addr).await.unwrap(); | ||||
|  | ||||
|                         let builder = Server::bind(&addr); | ||||
|                     addr_tx | ||||
|                         .send(listener.local_addr().unwrap()) | ||||
|                         .expect("server addr tx"); | ||||
|  | ||||
|                         #[cfg(feature = "http1")] | ||||
|                         let builder = builder | ||||
|                             .http1_only(_options.http1_only) | ||||
|                             .http1_keepalive(_options.keep_alive) | ||||
|                             .http1_pipeline_flush(_options.pipeline); | ||||
|                     loop { | ||||
|                         let msg_tx = msg_tx.clone(); | ||||
|                         let reply_rx = reply_rx.clone(); | ||||
|  | ||||
|                         let server = builder.serve(service); | ||||
|                         tokio::select! { | ||||
|                             res = listener.accept() => { | ||||
|                                 let (stream, _) = res.unwrap(); | ||||
|  | ||||
|                         addr_tx.send(server.local_addr()).expect("server addr tx"); | ||||
|                                 tokio::task::spawn(async move { | ||||
|                                     let mut http = Http::new(); | ||||
|  | ||||
|                         server | ||||
|                             .with_graceful_shutdown(async { | ||||
|                                 let _ = shutdown_rx.await; | ||||
|                             }) | ||||
|                             .await | ||||
|                     }) | ||||
|                     .expect("serve()"); | ||||
|                                     #[cfg(feature = "http1")] | ||||
|                                     let http = http | ||||
|                                         .http1_only(_options.http1_only) | ||||
|                                         .http1_keep_alive(_options.keep_alive) | ||||
|                                         .pipeline_flush(_options.pipeline); | ||||
|  | ||||
|                                     let msg_tx = msg_tx.clone(); | ||||
|                                     let reply_rx = reply_rx.clone(); | ||||
|                                     let service = TestService { | ||||
|                                         tx: msg_tx, | ||||
|                                         reply: reply_rx, | ||||
|                                     }; | ||||
|  | ||||
|                                     http.serve_connection(stream, service).await.unwrap(); | ||||
|                                 }); | ||||
|                             } | ||||
|                             _ = &mut shutdown_rx => { | ||||
|                                 break; | ||||
|                             } | ||||
|                         } | ||||
|                     } | ||||
|                 }) | ||||
|             }) | ||||
|             .expect("thread spawn"); | ||||
|  | ||||
| @@ -3054,3 +3112,49 @@ impl Drop for Dropped { | ||||
|         self.0.store(true, Ordering::SeqCst); | ||||
|     } | ||||
| } | ||||
|  | ||||
| struct TestClient { | ||||
|     http2_only: bool, | ||||
| } | ||||
|  | ||||
| impl TestClient { | ||||
|     fn new() -> Self { | ||||
|         Self { http2_only: false } | ||||
|     } | ||||
|  | ||||
|     fn http2_only(mut self) -> Self { | ||||
|         self.http2_only = true; | ||||
|         self | ||||
|     } | ||||
|  | ||||
|     async fn get(&self, uri: Uri) -> Result<Response<Body>, hyper::Error> { | ||||
|         self.request( | ||||
|             Request::builder() | ||||
|                 .uri(uri) | ||||
|                 .method(Method::GET) | ||||
|                 .body(Body::empty()) | ||||
|                 .unwrap(), | ||||
|         ) | ||||
|         .await | ||||
|     } | ||||
|  | ||||
|     async fn request(&self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||
|         let host = req.uri().host().expect("uri has no host"); | ||||
|         let port = req.uri().port_u16().expect("uri has no port"); | ||||
|  | ||||
|         let mut builder = hyper::client::conn::Builder::new(); | ||||
|         builder.http2_only(self.http2_only); | ||||
|  | ||||
|         let stream = TkTcpStream::connect(format!("{}:{}", host, port)) | ||||
|             .await | ||||
|             .unwrap(); | ||||
|  | ||||
|         let (mut sender, conn) = builder.handshake(stream).await.unwrap(); | ||||
|  | ||||
|         tokio::task::spawn(async move { | ||||
|             conn.await.unwrap(); | ||||
|         }); | ||||
|  | ||||
|         sender.send_request(req).await | ||||
|     } | ||||
| } | ||||
|   | ||||
| @@ -6,9 +6,12 @@ use std::sync::{ | ||||
|     Arc, Mutex, | ||||
| }; | ||||
|  | ||||
| use hyper::client::HttpConnector; | ||||
| use hyper::service::{make_service_fn, service_fn}; | ||||
| use hyper::{Body, Client, Request, Response, Server, Version}; | ||||
| use hyper::client::conn::Builder; | ||||
| use hyper::server::conn::Http; | ||||
| use tokio::net::{TcpListener, TcpStream}; | ||||
|  | ||||
| use hyper::service::service_fn; | ||||
| use hyper::{Body, Request, Response, Version}; | ||||
|  | ||||
| pub use futures_util::{ | ||||
|     future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _, | ||||
| @@ -326,16 +329,20 @@ async fn async_test(cfg: __TestConfig) { | ||||
|         Version::HTTP_11 | ||||
|     }; | ||||
|  | ||||
|     let connector = HttpConnector::new(); | ||||
|     let client = Client::builder() | ||||
|         .http2_only(cfg.client_version == 2) | ||||
|         .build::<_, Body>(connector); | ||||
|     let http2_only = cfg.server_version == 2; | ||||
|  | ||||
|     let serve_handles = Arc::new(Mutex::new(cfg.server_msgs)); | ||||
|  | ||||
|     let listener = TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|     let mut addr = listener.local_addr().unwrap(); | ||||
|  | ||||
|     let expected_connections = cfg.connections; | ||||
|     let mut cnt = 0; | ||||
|     let new_service = make_service_fn(move |_| { | ||||
|     tokio::task::spawn(async move { | ||||
|         let mut cnt = 0; | ||||
|  | ||||
|         cnt += 1; | ||||
|         assert!( | ||||
|             cnt <= expected_connections, | ||||
| @@ -344,98 +351,108 @@ async fn async_test(cfg: __TestConfig) { | ||||
|             cnt | ||||
|         ); | ||||
|  | ||||
|         // Move a clone into the service_fn | ||||
|         let serve_handles = serve_handles.clone(); | ||||
|         future::ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| { | ||||
|             let (sreq, sres) = serve_handles.lock().unwrap().remove(0); | ||||
|         loop { | ||||
|             let (stream, _) = listener.accept().await.expect("server error"); | ||||
|  | ||||
|             assert_eq!(req.uri().path(), sreq.uri, "client path"); | ||||
|             assert_eq!(req.method(), &sreq.method, "client method"); | ||||
|             assert_eq!(req.version(), version, "client version"); | ||||
|             for func in &sreq.headers { | ||||
|                 func(&req.headers()); | ||||
|             } | ||||
|             let sbody = sreq.body; | ||||
|             hyper::body::to_bytes(req).map_ok(move |body| { | ||||
|                 assert_eq!(body.as_ref(), sbody.as_slice(), "client body"); | ||||
|             // Move a clone into the service_fn | ||||
|             let serve_handles = serve_handles.clone(); | ||||
|             let service = service_fn(move |req: Request<Body>| { | ||||
|                 let (sreq, sres) = serve_handles.lock().unwrap().remove(0); | ||||
|  | ||||
|                 let mut res = Response::builder() | ||||
|                     .status(sres.status) | ||||
|                     .body(Body::from(sres.body)) | ||||
|                     .expect("Response::build"); | ||||
|                 *res.headers_mut() = sres.headers; | ||||
|                 res | ||||
|             }) | ||||
|         })) | ||||
|                 assert_eq!(req.uri().path(), sreq.uri, "client path"); | ||||
|                 assert_eq!(req.method(), &sreq.method, "client method"); | ||||
|                 assert_eq!(req.version(), version, "client version"); | ||||
|                 for func in &sreq.headers { | ||||
|                     func(&req.headers()); | ||||
|                 } | ||||
|                 let sbody = sreq.body; | ||||
|                 hyper::body::to_bytes(req).map_ok(move |body| { | ||||
|                     assert_eq!(body.as_ref(), sbody.as_slice(), "client body"); | ||||
|  | ||||
|                     let mut res = Response::builder() | ||||
|                         .status(sres.status) | ||||
|                         .body(Body::from(sres.body)) | ||||
|                         .expect("Response::build"); | ||||
|                     *res.headers_mut() = sres.headers; | ||||
|                     res | ||||
|                 }) | ||||
|             }); | ||||
|  | ||||
|             tokio::task::spawn(async move { | ||||
|                 Http::new() | ||||
|                     .http2_only(http2_only) | ||||
|                     .serve_connection(stream, service) | ||||
|                     .await | ||||
|                     .expect("server error"); | ||||
|             }); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let server = hyper::Server::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) | ||||
|         .http2_only(cfg.server_version == 2) | ||||
|         .serve(new_service); | ||||
|  | ||||
|     let mut addr = server.local_addr(); | ||||
|  | ||||
|     tokio::task::spawn(server.map(|result| { | ||||
|         result.expect("server error"); | ||||
|     })); | ||||
|  | ||||
|     if cfg.proxy { | ||||
|         let (proxy_addr, proxy) = naive_proxy(ProxyConfig { | ||||
|             connections: cfg.connections, | ||||
|             dst: addr, | ||||
|             version: cfg.server_version, | ||||
|         }); | ||||
|         }) | ||||
|         .await; | ||||
|         tokio::task::spawn(proxy); | ||||
|         addr = proxy_addr; | ||||
|     } | ||||
|  | ||||
|     let make_request = Arc::new( | ||||
|         move |client: &Client<HttpConnector>, creq: __CReq, cres: __CRes| { | ||||
|             let uri = format!("http://{}{}", addr, creq.uri); | ||||
|             let mut req = Request::builder() | ||||
|                 .method(creq.method) | ||||
|                 .uri(uri) | ||||
|                 //.headers(creq.headers) | ||||
|                 .body(creq.body.into()) | ||||
|                 .expect("Request::build"); | ||||
|             *req.headers_mut() = creq.headers; | ||||
|             let cstatus = cres.status; | ||||
|             let cheaders = cres.headers; | ||||
|             let cbody = cres.body; | ||||
|     let make_request = Arc::new(move |creq: __CReq, cres: __CRes| { | ||||
|         let uri = format!("http://{}{}", addr, creq.uri); | ||||
|         let mut req = Request::builder() | ||||
|             .method(creq.method) | ||||
|             .uri(uri) | ||||
|             //.headers(creq.headers) | ||||
|             .body(creq.body.into()) | ||||
|             .expect("Request::build"); | ||||
|         *req.headers_mut() = creq.headers; | ||||
|         let cstatus = cres.status; | ||||
|         let cheaders = cres.headers; | ||||
|         let cbody = cres.body; | ||||
|  | ||||
|             client | ||||
|                 .request(req) | ||||
|                 .and_then(move |res| { | ||||
|                     assert_eq!(res.status(), cstatus, "server status"); | ||||
|                     assert_eq!(res.version(), version, "server version"); | ||||
|                     for func in &cheaders { | ||||
|                         func(&res.headers()); | ||||
|                     } | ||||
|                     hyper::body::to_bytes(res) | ||||
|                 }) | ||||
|                 .map_ok(move |body| { | ||||
|                     assert_eq!(body.as_ref(), cbody.as_slice(), "server body"); | ||||
|                 }) | ||||
|                 .map(|res| res.expect("client error")) | ||||
|         }, | ||||
|     ); | ||||
|         async move { | ||||
|             let stream = TcpStream::connect(addr).await.unwrap(); | ||||
|  | ||||
|             let (mut sender, conn) = hyper::client::conn::Builder::new() | ||||
|                 .http2_only(http2_only) | ||||
|                 .handshake::<TcpStream, Body>(stream) | ||||
|                 .await | ||||
|                 .unwrap(); | ||||
|  | ||||
|             tokio::task::spawn(async move { | ||||
|                 if let Err(err) = conn.await { | ||||
|                     panic!("{:?}", err); | ||||
|                 } | ||||
|             }); | ||||
|  | ||||
|             let res = sender.send_request(req).await.unwrap(); | ||||
|  | ||||
|             assert_eq!(res.status(), cstatus, "server status"); | ||||
|             assert_eq!(res.version(), version, "server version"); | ||||
|             for func in &cheaders { | ||||
|                 func(&res.headers()); | ||||
|             } | ||||
|  | ||||
|             let body = hyper::body::to_bytes(res).await.unwrap(); | ||||
|  | ||||
|             assert_eq!(body.as_ref(), cbody.as_slice(), "server body"); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     let client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = if cfg.parallel { | ||||
|         let mut client_futures = vec![]; | ||||
|         for (creq, cres) in cfg.client_msgs { | ||||
|             client_futures.push(make_request(&client, creq, cres)); | ||||
|             client_futures.push(make_request(creq, cres)); | ||||
|         } | ||||
|         drop(client); | ||||
|         Box::pin(future::join_all(client_futures).map(|_| ())) | ||||
|     } else { | ||||
|         let mut client_futures: Pin<Box<dyn Future<Output = Client<HttpConnector>> + Send>> = | ||||
|             Box::pin(future::ready(client)); | ||||
|         let mut client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = | ||||
|             Box::pin(future::ready(())); | ||||
|         for (creq, cres) in cfg.client_msgs { | ||||
|             let mk_request = make_request.clone(); | ||||
|             client_futures = Box::pin(client_futures.then(move |client| { | ||||
|                 let fut = mk_request(&client, creq, cres); | ||||
|                 fut.map(move |()| client) | ||||
|             })); | ||||
|             client_futures = Box::pin(client_futures.then(move |_| mk_request(creq, cres))); | ||||
|         } | ||||
|         Box::pin(client_futures.map(|_| ())) | ||||
|     }; | ||||
| @@ -449,27 +466,75 @@ struct ProxyConfig { | ||||
|     version: usize, | ||||
| } | ||||
|  | ||||
| fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) { | ||||
|     let client = Client::builder() | ||||
|         .http2_only(cfg.version == 2) | ||||
|         .build_http::<Body>(); | ||||
|  | ||||
| async fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) { | ||||
|     let dst_addr = cfg.dst; | ||||
|     let max_connections = cfg.connections; | ||||
|     let counter = AtomicUsize::new(0); | ||||
|     let http2_only = cfg.version == 2; | ||||
|  | ||||
|     let srv = Server::bind(&([127, 0, 0, 1], 0).into()).serve(make_service_fn(move |_| { | ||||
|         let prev = counter.fetch_add(1, Ordering::Relaxed); | ||||
|         assert!(max_connections > prev, "proxy max connections"); | ||||
|         let client = client.clone(); | ||||
|         future::ok::<_, hyper::Error>(service_fn(move |mut req| { | ||||
|             let uri = format!("http://{}{}", dst_addr, req.uri().path()) | ||||
|                 .parse() | ||||
|                 .expect("proxy new uri parse"); | ||||
|             *req.uri_mut() = uri; | ||||
|             client.request(req) | ||||
|         })) | ||||
|     })); | ||||
|     let proxy_addr = srv.local_addr(); | ||||
|     (proxy_addr, srv.map(|res| res.expect("proxy error"))) | ||||
|     let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) | ||||
|         .await | ||||
|         .unwrap(); | ||||
|  | ||||
|     let proxy_addr = listener.local_addr().unwrap(); | ||||
|  | ||||
|     let fut = async move { | ||||
|         tokio::task::spawn(async move { | ||||
|             let prev = counter.fetch_add(1, Ordering::Relaxed); | ||||
|             assert!(max_connections > prev, "proxy max connections"); | ||||
|  | ||||
|             loop { | ||||
|                 let (stream, _) = listener.accept().await.unwrap(); | ||||
|  | ||||
|                 let service = service_fn(move |mut req| { | ||||
|                     async move { | ||||
|                         let uri = format!("http://{}{}", dst_addr, req.uri().path()) | ||||
|                             .parse() | ||||
|                             .expect("proxy new uri parse"); | ||||
|                         *req.uri_mut() = uri; | ||||
|  | ||||
|                         // Make the client request | ||||
|                         let uri = req.uri().host().expect("uri has no host"); | ||||
|                         let port = req.uri().port_u16().expect("uri has no port"); | ||||
|  | ||||
|                         let stream = TcpStream::connect(format!("{}:{}", uri, port)) | ||||
|                             .await | ||||
|                             .unwrap(); | ||||
|  | ||||
|                         let mut builder = Builder::new(); | ||||
|                         builder.http2_only(http2_only); | ||||
|                         let (mut sender, conn) = builder.handshake(stream).await.unwrap(); | ||||
|  | ||||
|                         tokio::task::spawn(async move { | ||||
|                             if let Err(err) = conn.await { | ||||
|                                 panic!("{:?}", err); | ||||
|                             } | ||||
|                         }); | ||||
|  | ||||
|                         let resp = sender.send_request(req).await?; | ||||
|  | ||||
|                         let (mut parts, body) = resp.into_parts(); | ||||
|  | ||||
|                         // Remove the Connection header for HTTP/1.1 proxy connections. | ||||
|                         if !http2_only { | ||||
|                             parts.headers.remove("Connection"); | ||||
|                         } | ||||
|  | ||||
|                         let mut builder = Response::builder().status(parts.status); | ||||
|                         *builder.headers_mut().unwrap() = parts.headers; | ||||
|  | ||||
|                         Result::<Response<Body>, hyper::Error>::Ok(builder.body(body).unwrap()) | ||||
|                     } | ||||
|                 }); | ||||
|  | ||||
|                 Http::new() | ||||
|                     .http2_only(http2_only) | ||||
|                     .serve_connection(stream, service) | ||||
|                     .await | ||||
|                     .unwrap(); | ||||
|             } | ||||
|         }); | ||||
|     }; | ||||
|  | ||||
|     (proxy_addr, fut) | ||||
| } | ||||
|   | ||||
		Reference in New Issue
	
	Block a user