Compare commits
	
		
			2 Commits
		
	
	
		
			5e20688398
			...
			v0.14.18-p
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|  | abf28d875e | ||
|  | db6c602667 | 
							
								
								
									
										24
									
								
								.github/workflows/CI.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										24
									
								
								.github/workflows/CI.yml
									
									
									
									
										vendored
									
									
								
							| @@ -16,7 +16,6 @@ jobs: | |||||||
|       - style |       - style | ||||||
|       - test |       - test | ||||||
|       - msrv |       - msrv | ||||||
|       - miri |  | ||||||
|       - features |       - features | ||||||
|       - ffi |       - ffi | ||||||
|       - ffi-header |       - ffi-header | ||||||
| @@ -101,7 +100,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         rust: |         rust: | ||||||
|           - 1.56 # keep in sync with MSRV.md dev doc |           - 1.49 # keep in sync with MSRV.md dev doc | ||||||
|  |  | ||||||
|         os: |         os: | ||||||
|           - ubuntu-latest |           - ubuntu-latest | ||||||
| @@ -125,27 +124,6 @@ jobs: | |||||||
|           command: check |           command: check | ||||||
|           args: --features full |           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: |   features: | ||||||
|     name: features |     name: features | ||||||
|     needs: [style] |     needs: [style] | ||||||
|   | |||||||
							
								
								
									
										6
									
								
								.github/workflows/bench.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/workflows/bench.yml
									
									
									
									
										vendored
									
									
								
							| @@ -11,9 +11,9 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       matrix: |       matrix: | ||||||
|         bench: |         bench: | ||||||
|           #- connect |           - connect | ||||||
|           #- end_to_end |           - end_to_end | ||||||
|           #- pipeline |           - pipeline | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v2 |       - uses: actions/checkout@v2 | ||||||
|  |  | ||||||
|   | |||||||
							
								
								
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										1
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -1,2 +1,3 @@ | |||||||
| target | target | ||||||
| Cargo.lock | Cargo.lock | ||||||
|  | .history | ||||||
							
								
								
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
								
							
							
						
						
									
										23
									
								
								CHANGELOG.md
									
									
									
									
									
								
							| @@ -1,26 +1,3 @@ | |||||||
| ### 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) | ### v0.14.18 (2022-03-22) | ||||||
|  |  | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,7 +5,6 @@ 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 | [easy tag]: https://github.com/hyperium/hyper/issues?q=label%3AE-easy+is%3Aopen | ||||||
|  |  | ||||||
|  |  | ||||||
| ## [Pull Requests](./docs/PULL_REQUESTS.md) | ## Pull Requests | ||||||
|  |  | ||||||
| - [Submitting a Pull Request](./docs/PULL_REQUESTS.md#submitting-a-pull-request) |  | ||||||
| - [Commit Guidelines](./docs/COMMITS.md) | - [Commit Guidelines](./docs/COMMITS.md) | ||||||
|   | |||||||
							
								
								
									
										24
									
								
								Cargo.toml
									
									
									
									
									
								
							
							
						
						
									
										24
									
								
								Cargo.toml
									
									
									
									
									
								
							| @@ -1,6 +1,6 @@ | |||||||
| [package] | [package] | ||||||
| name = "hyper" | name = "hyper" | ||||||
| version = "1.0.0-dev.0" | version = "0.14.18" | ||||||
| description = "A fast and correct HTTP library." | description = "A fast and correct HTTP library." | ||||||
| readme = "README.md" | readme = "README.md" | ||||||
| homepage = "https://hyper.rs" | homepage = "https://hyper.rs" | ||||||
| @@ -12,8 +12,6 @@ keywords = ["http", "hyper", "hyperium"] | |||||||
| categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"] | categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"] | ||||||
| edition = "2018" | edition = "2018" | ||||||
|  |  | ||||||
| publish = false # no accidents while in dev |  | ||||||
|  |  | ||||||
| include = [ | include = [ | ||||||
|   "Cargo.toml", |   "Cargo.toml", | ||||||
|   "LICENSE", |   "LICENSE", | ||||||
| @@ -27,8 +25,7 @@ futures-core = { version = "0.3", default-features = false } | |||||||
| futures-channel = "0.3" | futures-channel = "0.3" | ||||||
| futures-util = { version = "0.3", default-features = false } | futures-util = { version = "0.3", default-features = false } | ||||||
| http = "0.2" | http = "0.2" | ||||||
| http-body = { git = "https://github.com/hyperium/http-body", branch = "master" } | http-body = "0.4" | ||||||
| http-body-util = { git = "https://github.com/hyperium/http-body", branch = "master" } |  | ||||||
| httpdate = "1.0" | httpdate = "1.0" | ||||||
| httparse = "1.6" | httparse = "1.6" | ||||||
| h2 = { version = "0.3.9", optional = true } | h2 = { version = "0.3.9", optional = true } | ||||||
| @@ -64,7 +61,7 @@ tokio = { version = "1", features = [ | |||||||
|     "test-util", |     "test-util", | ||||||
| ] } | ] } | ||||||
| tokio-test = "0.4" | tokio-test = "0.4" | ||||||
| tokio-util = { version = "0.7", features = ["codec"] } | tokio-util = { version = "0.6", features = ["codec"] } | ||||||
| tower = { version = "0.4", features = ["make", "util"] } | tower = { version = "0.4", features = ["make", "util"] } | ||||||
| url = "2.2" | url = "2.2" | ||||||
|  |  | ||||||
| @@ -81,6 +78,7 @@ full = [ | |||||||
|     "http1", |     "http1", | ||||||
|     "http2", |     "http2", | ||||||
|     "server", |     "server", | ||||||
|  |     "stream", | ||||||
|     "runtime", |     "runtime", | ||||||
| ] | ] | ||||||
|  |  | ||||||
| @@ -92,8 +90,17 @@ http2 = ["h2"] | |||||||
| client = [] | client = [] | ||||||
| server = [] | server = [] | ||||||
|  |  | ||||||
|  | # `impl Stream` for things | ||||||
|  | stream = [] | ||||||
|  |  | ||||||
| # Tokio support | # Tokio support | ||||||
| runtime = [ | runtime = [ | ||||||
|  |     "tcp", | ||||||
|  |     "tokio/rt", | ||||||
|  |     "tokio/time", | ||||||
|  | ] | ||||||
|  | tcp = [ | ||||||
|  |     "socket2", | ||||||
|     "tokio/net", |     "tokio/net", | ||||||
|     "tokio/rt", |     "tokio/rt", | ||||||
|     "tokio/time", |     "tokio/time", | ||||||
| @@ -181,6 +188,11 @@ name = "state" | |||||||
| path = "examples/state.rs" | path = "examples/state.rs" | ||||||
| required-features = ["full"] | required-features = ["full"] | ||||||
|  |  | ||||||
|  | [[example]] | ||||||
|  | name = "tower_client" | ||||||
|  | path = "examples/tower_client.rs" | ||||||
|  | required-features = ["full"] | ||||||
|  |  | ||||||
| [[example]] | [[example]] | ||||||
| name = "tower_server" | name = "tower_server" | ||||||
| path = "examples/tower_server.rs" | path = "examples/tower_server.rs" | ||||||
|   | |||||||
| @@ -8,10 +8,6 @@ | |||||||
|  |  | ||||||
| A **fast** and **correct** HTTP implementation for Rust. | 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 | - HTTP/1 and HTTP/2 | ||||||
| - Asynchronous design | - Asynchronous design | ||||||
| - Leading in performance | - Leading in performance | ||||||
|   | |||||||
| @@ -6,7 +6,7 @@ extern crate test; | |||||||
| use bytes::Buf; | use bytes::Buf; | ||||||
| use futures_util::stream; | use futures_util::stream; | ||||||
| use futures_util::StreamExt; | use futures_util::StreamExt; | ||||||
| use http_body_util::StreamBody; | use hyper::body::Body; | ||||||
|  |  | ||||||
| macro_rules! bench_stream { | macro_rules! bench_stream { | ||||||
|     ($bencher:ident, bytes: $bytes:expr, count: $count:expr, $total_ident:ident, $body_pat:pat, $block:expr) => {{ |     ($bencher:ident, bytes: $bytes:expr, count: $count:expr, $total_ident:ident, $body_pat:pat, $block:expr) => {{ | ||||||
| @@ -20,10 +20,9 @@ macro_rules! bench_stream { | |||||||
|  |  | ||||||
|         $bencher.iter(|| { |         $bencher.iter(|| { | ||||||
|             rt.block_on(async { |             rt.block_on(async { | ||||||
|                 let $body_pat = StreamBody::new( |                 let $body_pat = Body::wrap_stream( | ||||||
|                     stream::iter(__s.iter()).map(|&s| Ok::<_, std::convert::Infallible>(s)), |                     stream::iter(__s.iter()).map(|&s| Ok::<_, std::convert::Infallible>(s)), | ||||||
|                 ); |                 ); | ||||||
|  |  | ||||||
|                 $block; |                 $block; | ||||||
|             }); |             }); | ||||||
|         }); |         }); | ||||||
|   | |||||||
| @@ -3,38 +3,35 @@ | |||||||
|  |  | ||||||
| extern crate test; | extern crate test; | ||||||
|  |  | ||||||
| // TODO: Reimplement http_connector bench using hyper::client::conn | use http::Uri; | ||||||
| // (instead of removed HttpConnector). | use hyper::client::connect::HttpConnector; | ||||||
|  | use hyper::service::Service; | ||||||
|  | use std::net::SocketAddr; | ||||||
|  | use tokio::net::TcpListener; | ||||||
|  |  | ||||||
| // use http::Uri; | #[bench] | ||||||
| // use hyper::client::connect::HttpConnector; | fn http_connector(b: &mut test::Bencher) { | ||||||
| // use hyper::service::Service; |     let _ = pretty_env_logger::try_init(); | ||||||
| // use std::net::SocketAddr; |     let rt = tokio::runtime::Builder::new_current_thread() | ||||||
| // use tokio::net::TcpListener; |         .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(); | ||||||
|  |  | ||||||
| // #[bench] |     rt.spawn(async move { | ||||||
| // fn http_connector(b: &mut test::Bencher) { |         loop { | ||||||
| //     let _ = pretty_env_logger::try_init(); |             let _ = listener.accept().await; | ||||||
| //     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(); |  | ||||||
|  |  | ||||||
| //     rt.spawn(async move { |     b.iter(|| { | ||||||
| //         loop { |         rt.block_on(async { | ||||||
| //             let _ = listener.accept().await; |             connector.call(dst.clone()).await.expect("connect"); | ||||||
| //         } |         }); | ||||||
| //     }); |     }); | ||||||
|  | } | ||||||
| //     b.iter(|| { |  | ||||||
| //         rt.block_on(async { |  | ||||||
| //             connector.call(dst.clone()).await.expect("connect"); |  | ||||||
| //         }); |  | ||||||
| //     }); |  | ||||||
| // } |  | ||||||
|   | |||||||
| @@ -3,383 +3,380 @@ | |||||||
|  |  | ||||||
| extern crate test; | extern crate test; | ||||||
|  |  | ||||||
| // TODO: Reimplement Opts::bench using hyper::server::conn and hyper::client::conn | use std::net::SocketAddr; | ||||||
| // (instead of Server and HttpClient). |  | ||||||
|  |  | ||||||
| // use std::net::SocketAddr; | use futures_util::future::join_all; | ||||||
|  |  | ||||||
| // use futures_util::future::join_all; | use hyper::client::HttpConnector; | ||||||
|  | use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server}; | ||||||
|  |  | ||||||
| // use hyper::client::HttpConnector; | // HTTP1 | ||||||
| // use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server}; |  | ||||||
|  |  | ||||||
| // // HTTP1 | #[bench] | ||||||
|  | fn http1_consecutive_x1_empty(b: &mut test::Bencher) { | ||||||
|  |     opts().bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_consecutive_x1_empty(b: &mut test::Bencher) { | fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { | ||||||
| //     opts().bench(b) |     opts() | ||||||
| // } |         .method(Method::POST) | ||||||
|  |         .request_body(&[b's'; 10]) | ||||||
|  |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) { | fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { | ||||||
| //     opts() |     let body = &[b'x'; 1024 * 100]; | ||||||
| //         .method(Method::POST) |     opts() | ||||||
| //         .request_body(&[b's'; 10]) |         .method(Method::POST) | ||||||
| //         .bench(b) |         .request_body(body) | ||||||
| // } |         .response_body(body) | ||||||
|  |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) { | fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 100]; |     let body = &[b'x'; 1024 * 1024 * 10]; | ||||||
| //     opts() |     opts() | ||||||
| //         .method(Method::POST) |         .method(Method::POST) | ||||||
| //         .request_body(body) |         .request_body(body) | ||||||
| //         .response_body(body) |         .response_body(body) | ||||||
| //         .bench(b) |         .bench(b) | ||||||
| // } | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) { | fn http1_parallel_x10_empty(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; |     opts().parallel(10).bench(b) | ||||||
| //     opts() | } | ||||||
| //         .method(Method::POST) |  | ||||||
| //         .request_body(body) |  | ||||||
| //         .response_body(body) |  | ||||||
| //         .bench(b) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_parallel_x10_empty(b: &mut test::Bencher) { | fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) { | ||||||
| //     opts().parallel(10).bench(b) |     let body = &[b'x'; 1024 * 1024 * 10]; | ||||||
| // } |     opts() | ||||||
|  |         .parallel(10) | ||||||
|  |         .method(Method::POST) | ||||||
|  |         .request_body(body) | ||||||
|  |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) { | fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; |     let body = &[b'x'; 1024 * 10]; | ||||||
| //     opts() |     opts() | ||||||
| //         .parallel(10) |         .parallel(10) | ||||||
| //         .method(Method::POST) |         .method(Method::POST) | ||||||
| //         .request_body(body) |         .request_chunks(body, 100) | ||||||
| //         .bench(b) |         .bench(b) | ||||||
| // } | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 10]; |     let body = &[b'x'; 1024 * 1024 * 1]; | ||||||
| //     opts() |     opts().parallel(10).response_body(body).bench(b) | ||||||
| //         .parallel(10) | } | ||||||
| //         .method(Method::POST) |  | ||||||
| //         .request_chunks(body, 100) |  | ||||||
| //         .bench(b) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) { | fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 1024 * 1]; |     let body = &[b'x'; 1024 * 1024 * 10]; | ||||||
| //     opts().parallel(10).response_body(body).bench(b) |     opts().parallel(10).response_body(body).bench(b) | ||||||
| // } | } | ||||||
|  |  | ||||||
| // #[bench] | // HTTP2 | ||||||
| // 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) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // // HTTP2 | const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1; | ||||||
|  |  | ||||||
| // const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1; | #[bench] | ||||||
|  | fn http2_consecutive_x1_empty(b: &mut test::Bencher) { | ||||||
|  |     opts().http2().bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_consecutive_x1_empty(b: &mut test::Bencher) { | fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) { | ||||||
| //     opts().http2().bench(b) |     opts() | ||||||
| // } |         .http2() | ||||||
|  |         .method(Method::POST) | ||||||
|  |         .request_body(&[b's'; 10]) | ||||||
|  |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) { | fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) { | ||||||
| //     opts() |     let body = &[b'x'; 1024 * 100]; | ||||||
| //         .http2() |     opts() | ||||||
| //         .method(Method::POST) |         .http2() | ||||||
| //         .request_body(&[b's'; 10]) |         .method(Method::POST) | ||||||
| //         .bench(b) |         .request_body(body) | ||||||
| // } |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) { | fn http2_parallel_x10_empty(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 100]; |     opts().http2().parallel(10).bench(b) | ||||||
| //     opts() | } | ||||||
| //         .http2() |  | ||||||
| //         .method(Method::POST) |  | ||||||
| //         .request_body(body) |  | ||||||
| //         .bench(b) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_parallel_x10_empty(b: &mut test::Bencher) { | fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { | ||||||
| //     opts().http2().parallel(10).bench(b) |     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] | #[bench] | ||||||
| // fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) { | fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 1024 * 10]; |     let body = &[b'x'; 1024 * 10]; | ||||||
| //     opts() |     opts() | ||||||
| //         .http2() |         .http2() | ||||||
| //         .parallel(10) |         .parallel(10) | ||||||
| //         .method(Method::POST) |         .method(Method::POST) | ||||||
| //         .request_body(body) |         .request_chunks(body, 100) | ||||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) |         .bench(b) | ||||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) | } | ||||||
| //         .bench(b) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) { | fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 10]; |     let body = &[b'x'; 1024 * 10]; | ||||||
| //     opts() |     opts() | ||||||
| //         .http2() |         .http2() | ||||||
| //         .parallel(10) |         .parallel(10) | ||||||
| //         .method(Method::POST) |         .method(Method::POST) | ||||||
| //         .request_chunks(body, 100) |         .request_chunks(body, 100) | ||||||
| //         .bench(b) |         .http2_adaptive_window() | ||||||
| // } |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) { | fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 10]; |     let body = &[b'x'; 1024 * 10]; | ||||||
| //     opts() |     opts() | ||||||
| //         .http2() |         .http2() | ||||||
| //         .parallel(10) |         .parallel(10) | ||||||
| //         .method(Method::POST) |         .method(Method::POST) | ||||||
| //         .request_chunks(body, 100) |         .request_chunks(body, 100) | ||||||
| //         .http2_adaptive_window() |         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||||
| //         .bench(b) |         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||||
| // } |         .bench(b) | ||||||
|  | } | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) { | fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 10]; |     let body = &[b'x'; 1024 * 1024 * 1]; | ||||||
| //     opts() |     opts() | ||||||
| //         .http2() |         .http2() | ||||||
| //         .parallel(10) |         .parallel(10) | ||||||
| //         .method(Method::POST) |         .response_body(body) | ||||||
| //         .request_chunks(body, 100) |         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) |         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) |         .bench(b) | ||||||
| //         .bench(b) | } | ||||||
| // } |  | ||||||
|  |  | ||||||
| // #[bench] | #[bench] | ||||||
| // fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) { | fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) { | ||||||
| //     let body = &[b'x'; 1024 * 1024 * 1]; |     let body = &[b'x'; 1024 * 1024 * 10]; | ||||||
| //     opts() |     opts() | ||||||
| //         .http2() |         .http2() | ||||||
| //         .parallel(10) |         .parallel(10) | ||||||
| //         .response_body(body) |         .response_body(body) | ||||||
| //         .http2_stream_window(HTTP2_MAX_WINDOW) |         .http2_stream_window(HTTP2_MAX_WINDOW) | ||||||
| //         .http2_conn_window(HTTP2_MAX_WINDOW) |         .http2_conn_window(HTTP2_MAX_WINDOW) | ||||||
| //         .bench(b) |         .bench(b) | ||||||
| // } | } | ||||||
|  |  | ||||||
| // #[bench] | // ==== Benchmark Options ===== | ||||||
| // 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) |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // // ==== Benchmark Options ===== | 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], | ||||||
|  | } | ||||||
|  |  | ||||||
| // struct Opts { | fn opts() -> Opts { | ||||||
| //     http2: bool, |     Opts { | ||||||
| //     http2_stream_window: Option<u32>, |         http2: false, | ||||||
| //     http2_conn_window: Option<u32>, |         http2_stream_window: None, | ||||||
| //     http2_adaptive_window: bool, |         http2_conn_window: None, | ||||||
| //     parallel_cnt: u32, |         http2_adaptive_window: false, | ||||||
| //     request_method: Method, |         parallel_cnt: 1, | ||||||
| //     request_body: Option<&'static [u8]>, |         request_method: Method::GET, | ||||||
| //     request_chunks: usize, |         request_body: None, | ||||||
| //     response_body: &'static [u8], |         request_chunks: 0, | ||||||
| // } |         response_body: b"", | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| // fn opts() -> Opts { | impl Opts { | ||||||
| //     Opts { |     fn http2(mut self) -> Self { | ||||||
| //         http2: false, |         self.http2 = true; | ||||||
| //         http2_stream_window: None, |         self | ||||||
| //         http2_conn_window: None, |     } | ||||||
| //         http2_adaptive_window: false, |  | ||||||
| //         parallel_cnt: 1, |  | ||||||
| //         request_method: Method::GET, |  | ||||||
| //         request_body: None, |  | ||||||
| //         request_chunks: 0, |  | ||||||
| //         response_body: b"", |  | ||||||
| //     } |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // impl Opts { |     fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||||
| //     fn http2(mut self) -> Self { |         assert!(!self.http2_adaptive_window); | ||||||
| //         self.http2 = true; |         self.http2_stream_window = sz.into(); | ||||||
| //         self |         self | ||||||
| //     } |     } | ||||||
|  |  | ||||||
| //     fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self { |     fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self { | ||||||
| //         assert!(!self.http2_adaptive_window); |         assert!(!self.http2_adaptive_window); | ||||||
| //         self.http2_stream_window = sz.into(); |         self.http2_conn_window = sz.into(); | ||||||
| //         self |         self | ||||||
| //     } |     } | ||||||
|  |  | ||||||
| //     fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self { |     fn http2_adaptive_window(mut self) -> Self { | ||||||
| //         assert!(!self.http2_adaptive_window); |         assert!(self.http2_stream_window.is_none()); | ||||||
| //         self.http2_conn_window = sz.into(); |         assert!(self.http2_conn_window.is_none()); | ||||||
| //         self |         self.http2_adaptive_window = true; | ||||||
| //     } |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
| //     fn http2_adaptive_window(mut self) -> Self { |     fn method(mut self, m: Method) -> Self { | ||||||
| //         assert!(self.http2_stream_window.is_none()); |         self.request_method = m; | ||||||
| //         assert!(self.http2_conn_window.is_none()); |         self | ||||||
| //         self.http2_adaptive_window = true; |     } | ||||||
| //         self |  | ||||||
| //     } |  | ||||||
|  |  | ||||||
| //     fn method(mut self, m: Method) -> Self { |     fn request_body(mut self, body: &'static [u8]) -> Self { | ||||||
| //         self.request_method = m; |         self.request_body = Some(body); | ||||||
| //         self |         self | ||||||
| //     } |     } | ||||||
|  |  | ||||||
| //     fn request_body(mut self, body: &'static [u8]) -> Self { |     fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self { | ||||||
| //         self.request_body = Some(body); |         assert!(cnt > 0); | ||||||
| //         self |         self.request_body = Some(chunk); | ||||||
| //     } |         self.request_chunks = cnt; | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
| //     fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self { |     fn response_body(mut self, body: &'static [u8]) -> Self { | ||||||
| //         assert!(cnt > 0); |         self.response_body = body; | ||||||
| //         self.request_body = Some(chunk); |         self | ||||||
| //         self.request_chunks = cnt; |     } | ||||||
| //         self |  | ||||||
| //     } |  | ||||||
|  |  | ||||||
| //     fn response_body(mut self, body: &'static [u8]) -> Self { |     fn parallel(mut self, cnt: u32) -> Self { | ||||||
| //         self.response_body = body; |         assert!(cnt > 0, "parallel count must be larger than 0"); | ||||||
| //         self |         self.parallel_cnt = cnt; | ||||||
| //     } |         self | ||||||
|  |     } | ||||||
|  |  | ||||||
| //     fn parallel(mut self, cnt: u32) -> Self { |     fn bench(self, b: &mut test::Bencher) { | ||||||
| //         assert!(cnt > 0, "parallel count must be larger than 0"); |         use std::sync::Arc; | ||||||
| //         self.parallel_cnt = cnt; |         let _ = pretty_env_logger::try_init(); | ||||||
| //         self |         // 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 bench(self, b: &mut test::Bencher) { |         let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64; | ||||||
| //         use std::sync::Arc; |         let req_len = if self.request_chunks > 0 { | ||||||
| //         let _ = pretty_env_logger::try_init(); |             req_len * self.request_chunks as u64 | ||||||
| //         // Create a runtime of current thread. |         } else { | ||||||
| //         let rt = Arc::new( |             req_len | ||||||
| //             tokio::runtime::Builder::new_current_thread() |         }; | ||||||
| //                 .enable_all() |         let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64; | ||||||
| //                 .build() |         b.bytes = bytes_per_iter; | ||||||
| //                 .expect("rt build"), |  | ||||||
| //         ); |  | ||||||
| //         let exec = rt.clone(); |  | ||||||
|  |  | ||||||
| //         let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64; |         let addr = spawn_server(&rt, &self); | ||||||
| //         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 addr = spawn_server(&rt, &self); |         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 connector = HttpConnector::new(); |         let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap(); | ||||||
| //         let client = hyper::Client::builder() |  | ||||||
| //             .http2_only(self.http2) |  | ||||||
| //             .http2_initial_stream_window_size(self.http2_stream_window) |  | ||||||
| //             .http2_initial_connection_window_size(self.http2_conn_window) |  | ||||||
| //             .http2_adaptive_window(self.http2_adaptive_window) |  | ||||||
| //             .build::<_, Body>(connector); |  | ||||||
|  |  | ||||||
| //         let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap(); |         let 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 make_request = || { |         let send_request = |req: Request<Body>| { | ||||||
| //             let chunk_cnt = self.request_chunks; |             let fut = client.request(req); | ||||||
| //             let body = if chunk_cnt > 0 { |             async { | ||||||
| //                 let (mut tx, body) = Body::channel(); |                 let res = fut.await.expect("client wait"); | ||||||
| //                 let chunk = self |                 let mut body = res.into_body(); | ||||||
| //                     .request_body |                 while let Some(_chunk) = body.data().await {} | ||||||
| //                     .expect("request_chunks means request_body"); |             } | ||||||
| //                 exec.spawn(async move { |         }; | ||||||
| //                     for _ in 0..chunk_cnt { |  | ||||||
| //                         tx.send_data(chunk.into()).await.expect("send_data"); |  | ||||||
| //                     } |  | ||||||
| //                 }); |  | ||||||
| //                 body |  | ||||||
| //             } else { |  | ||||||
| //                 self.request_body |  | ||||||
| //                     .map(Body::from) |  | ||||||
| //                     .unwrap_or_else(Body::empty) |  | ||||||
| //             }; |  | ||||||
| //             let mut req = Request::new(body); |  | ||||||
| //             *req.method_mut() = self.request_method.clone(); |  | ||||||
| //             *req.uri_mut() = url.clone(); |  | ||||||
| //             req |  | ||||||
| //         }; |  | ||||||
|  |  | ||||||
| //         let send_request = |req: Request<Body>| { |         if self.parallel_cnt == 1 { | ||||||
| //             let fut = client.request(req); |             b.iter(|| { | ||||||
| //             async { |                 let req = make_request(); | ||||||
| //                 let res = fut.await.expect("client wait"); |                 rt.block_on(send_request(req)); | ||||||
| //                 let mut body = res.into_body(); |             }); | ||||||
| //                 while let Some(_chunk) = body.data().await {} |         } 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)); | ||||||
|  |             }); | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| //         if self.parallel_cnt == 1 { | fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { | ||||||
| //             b.iter(|| { |     use hyper::service::{make_service_fn, service_fn}; | ||||||
| //                 let req = make_request(); |     let addr = "127.0.0.1:0".parse().unwrap(); | ||||||
| //                 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)); |  | ||||||
| //             }); |  | ||||||
| //         } |  | ||||||
| //     } |  | ||||||
| // } |  | ||||||
|  |  | ||||||
| // fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr { |     let body = opts.response_body; | ||||||
| //     use hyper::service::{make_service_fn, service_fn}; |     let srv = rt.block_on(async move { | ||||||
| //     let addr = "127.0.0.1:0".parse().unwrap(); |         Server::bind(&addr) | ||||||
|  |             .http2_only(opts.http2) | ||||||
| //     let body = opts.response_body; |             .http2_initial_stream_window_size(opts.http2_stream_window) | ||||||
| //     let srv = rt.block_on(async move { |             .http2_initial_connection_window_size(opts.http2_conn_window) | ||||||
| //         Server::bind(&addr) |             .http2_adaptive_window(opts.http2_adaptive_window) | ||||||
| //             .http2_only(opts.http2) |             .serve(make_service_fn(move |_| async move { | ||||||
| //             .http2_initial_stream_window_size(opts.http2_stream_window) |                 Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move { | ||||||
| //             .http2_initial_connection_window_size(opts.http2_conn_window) |                     let mut req_body = req.into_body(); | ||||||
| //             .http2_adaptive_window(opts.http2_adaptive_window) |                     while let Some(_chunk) = req_body.data().await {} | ||||||
| //             .serve(make_service_fn(move |_| async move { |                     Ok::<_, hyper::Error>(Response::new(Body::from(body))) | ||||||
| //                 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); | ||||||
| //     let addr = srv.local_addr(); |         } | ||||||
| //     rt.spawn(async { |     }); | ||||||
| //         if let Err(err) = srv.await { |     addr | ||||||
| //             panic!("server error: {}", err); | } | ||||||
| //         } |  | ||||||
| //     }); |  | ||||||
| //     addr |  | ||||||
| // } |  | ||||||
|   | |||||||
| @@ -4,16 +4,14 @@ | |||||||
| extern crate test; | extern crate test; | ||||||
|  |  | ||||||
| use std::io::{Read, Write}; | use std::io::{Read, Write}; | ||||||
| use std::net::{SocketAddr, TcpStream}; | use std::net::TcpStream; | ||||||
| use std::sync::mpsc; | use std::sync::mpsc; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| use tokio::net::TcpListener; |  | ||||||
| use tokio::sync::oneshot; | use tokio::sync::oneshot; | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::service::service_fn; | use hyper::{Body, Response, Server}; | ||||||
| use hyper::{Body, Response}; |  | ||||||
|  |  | ||||||
| const PIPELINED_REQUESTS: usize = 16; | const PIPELINED_REQUESTS: usize = 16; | ||||||
|  |  | ||||||
| @@ -25,34 +23,35 @@ fn hello_world_16(b: &mut test::Bencher) { | |||||||
|     let addr = { |     let addr = { | ||||||
|         let (addr_tx, addr_rx) = mpsc::channel(); |         let (addr_tx, addr_rx) = mpsc::channel(); | ||||||
|         std::thread::spawn(move || { |         std::thread::spawn(move || { | ||||||
|             let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); |             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 rt = tokio::runtime::Builder::new_current_thread() |             let rt = tokio::runtime::Builder::new_current_thread() | ||||||
|                 .enable_all() |                 .enable_all() | ||||||
|                 .build() |                 .build() | ||||||
|                 .expect("rt build"); |                 .expect("rt build"); | ||||||
|  |             let srv = rt.block_on(async move { | ||||||
|             let listener = rt.block_on(TcpListener::bind(addr)).unwrap(); |                 Server::bind(&addr) | ||||||
|             let addr = listener.local_addr().unwrap(); |                     .http1_pipeline_flush(true) | ||||||
|  |                     .serve(make_svc) | ||||||
|             rt.spawn(async move { |  | ||||||
|                 loop { |  | ||||||
|                     let (stream, _addr) = listener.accept().await.expect("accept"); |  | ||||||
|  |  | ||||||
|                     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(); |             addr_tx.send(srv.local_addr()).unwrap(); | ||||||
|             rt.block_on(until_rx).ok(); |  | ||||||
|  |             let graceful = srv.with_graceful_shutdown(async { | ||||||
|  |                 until_rx.await.ok(); | ||||||
|  |             }); | ||||||
|  |  | ||||||
|  |             rt.block_on(async { | ||||||
|  |                 if let Err(e) = graceful.await { | ||||||
|  |                     panic!("server error: {}", e); | ||||||
|  |                 } | ||||||
|  |             }); | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         addr_rx.recv().unwrap() |         addr_rx.recv().unwrap() | ||||||
|   | |||||||
| @@ -4,59 +4,53 @@ | |||||||
| extern crate test; | extern crate test; | ||||||
|  |  | ||||||
| use std::io::{Read, Write}; | use std::io::{Read, Write}; | ||||||
| use std::net::{SocketAddr, TcpListener, TcpStream}; | use std::net::{TcpListener, TcpStream}; | ||||||
| use std::sync::mpsc; | use std::sync::mpsc; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| use futures_util::{stream, StreamExt}; | use futures_util::{stream, StreamExt}; | ||||||
| use http_body_util::{BodyExt, StreamBody}; |  | ||||||
| use tokio::sync::oneshot; | use tokio::sync::oneshot; | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::service::service_fn; | use hyper::{Body, Response, Server}; | ||||||
| use hyper::Response; |  | ||||||
|  |  | ||||||
| macro_rules! bench_server { | macro_rules! bench_server { | ||||||
|     ($b:ident, $header:expr, $body:expr) => {{ |     ($b:ident, $header:expr, $body:expr) => {{ | ||||||
|         let _ = pretty_env_logger::try_init(); |         let _ = pretty_env_logger::try_init(); | ||||||
|         let (_until_tx, until_rx) = oneshot::channel::<()>(); |         let (_until_tx, until_rx) = oneshot::channel::<()>(); | ||||||
|  |  | ||||||
|         let addr = { |         let addr = { | ||||||
|             let (addr_tx, addr_rx) = mpsc::channel(); |             let (addr_tx, addr_rx) = mpsc::channel(); | ||||||
|             std::thread::spawn(move || { |             std::thread::spawn(move || { | ||||||
|                 let addr: SocketAddr = "127.0.0.1:0".parse().unwrap(); |                 let addr = "127.0.0.1:0".parse().unwrap(); | ||||||
|  |                 let make_svc = make_service_fn(|_| async { | ||||||
|  |                     Ok::<_, hyper::Error>(service_fn(|_| async { | ||||||
|  |                         Ok::<_, hyper::Error>( | ||||||
|  |                             Response::builder() | ||||||
|  |                                 .header($header.0, $header.1) | ||||||
|  |                                 .header("content-type", "text/plain") | ||||||
|  |                                 .body($body()) | ||||||
|  |                                 .unwrap(), | ||||||
|  |                         ) | ||||||
|  |                     })) | ||||||
|  |                 }); | ||||||
|  |  | ||||||
|                 let rt = tokio::runtime::Builder::new_current_thread() |                 let rt = tokio::runtime::Builder::new_current_thread() | ||||||
|                     .enable_all() |                     .enable_all() | ||||||
|                     .build() |                     .build() | ||||||
|                     .expect("rt build"); |                     .expect("rt build"); | ||||||
|  |  | ||||||
|                 let listener = rt.block_on(tokio::net::TcpListener::bind(addr)).unwrap(); |                 let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) }); | ||||||
|                 let addr = listener.local_addr().unwrap(); |  | ||||||
|  |  | ||||||
|                 rt.spawn(async move { |                 addr_tx.send(srv.local_addr()).unwrap(); | ||||||
|                     loop { |  | ||||||
|                         let (stream, _) = listener.accept().await.expect("accept"); |  | ||||||
|  |  | ||||||
|                         Http::new() |                 let graceful = srv.with_graceful_shutdown(async { | ||||||
|                             .serve_connection( |                     until_rx.await.ok(); | ||||||
|                                 stream, |                 }); | ||||||
|                                 service_fn(|_| async { |                 rt.block_on(async move { | ||||||
|                                     Ok::<_, hyper::Error>( |                     if let Err(e) = graceful.await { | ||||||
|                                         Response::builder() |                         panic!("server error: {}", e); | ||||||
|                                             .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() |             addr_rx.recv().unwrap() | ||||||
| @@ -105,11 +99,9 @@ fn throughput_fixedsize_large_payload(b: &mut test::Bencher) { | |||||||
|  |  | ||||||
| #[bench] | #[bench] | ||||||
| fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { | fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) { | ||||||
|     bench_server!(b, ("content-length", "1000000"), move || { |     bench_server!(b, ("content-length", "1000000"), || { | ||||||
|         static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; |         static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; | ||||||
|         BodyExt::boxed(StreamBody::new( |         Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) | ||||||
|             stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)), |  | ||||||
|         )) |  | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -131,9 +123,7 @@ fn throughput_chunked_large_payload(b: &mut test::Bencher) { | |||||||
| fn throughput_chunked_many_chunks(b: &mut test::Bencher) { | fn throughput_chunked_many_chunks(b: &mut test::Bencher) { | ||||||
|     bench_server!(b, ("transfer-encoding", "chunked"), || { |     bench_server!(b, ("transfer-encoding", "chunked"), || { | ||||||
|         static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; |         static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _; | ||||||
|         BodyExt::boxed(StreamBody::new( |         Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s))) | ||||||
|             stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)), |  | ||||||
|         )) |  | ||||||
|     }) |     }) | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -24,42 +24,44 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b | |||||||
|     struct conn_data *conn = (struct conn_data *)userdata; |     struct conn_data *conn = (struct conn_data *)userdata; | ||||||
|     ssize_t ret = read(conn->fd, buf, buf_len); |     ssize_t ret = read(conn->fd, buf, buf_len); | ||||||
|  |  | ||||||
|     if (ret >= 0) { |     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 { | ||||||
|         return ret; |         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) { | 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; |     struct conn_data *conn = (struct conn_data *)userdata; | ||||||
|     ssize_t ret = write(conn->fd, buf, buf_len); |     ssize_t ret = write(conn->fd, buf, buf_len); | ||||||
|  |  | ||||||
|     if (ret >= 0) { |     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 { | ||||||
|         return ret; |         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) { | static void free_conn_data(struct conn_data *conn) { | ||||||
| @@ -96,9 +98,9 @@ static int connect_to(const char *host, const char *port) { | |||||||
|  |  | ||||||
|         if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { |         if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { | ||||||
|             break; |             break; | ||||||
|  |         } else { | ||||||
|  |             close(sfd); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         close(sfd); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     freeaddrinfo(result); |     freeaddrinfo(result); | ||||||
| @@ -140,17 +142,17 @@ typedef enum { | |||||||
| #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) | #define STR_ARG(XX) (uint8_t *)XX, strlen(XX) | ||||||
|  |  | ||||||
| int main(int argc, char *argv[]) { | int main(int argc, char *argv[]) { | ||||||
|     const char *host = argc > 1 ? argv[1] : "httpbin.org"; |         const char *host = argc > 1 ? argv[1] : "httpbin.org"; | ||||||
|     const char *port = argc > 2 ? argv[2] : "80"; |         const char *port = argc > 2 ? argv[2] : "80"; | ||||||
|     const char *path = argc > 3 ? argv[3] : "/"; |         const char *path = argc > 3 ? argv[3] : "/"; | ||||||
|     printf("connecting to port %s on %s...\n", port, host); |         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) { |     if (fd < 0) { | ||||||
|         return 1; |         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) { |     if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) { | ||||||
|         printf("failed to set socket to non-blocking\n"); |         printf("failed to set socket to non-blocking\n"); | ||||||
|         return 1; |         return 1; | ||||||
| @@ -166,6 +168,7 @@ int main(int argc, char *argv[]) { | |||||||
|     conn->read_waker = NULL; |     conn->read_waker = NULL; | ||||||
|     conn->write_waker = NULL; |     conn->write_waker = NULL; | ||||||
|  |  | ||||||
|  |  | ||||||
|     // Hookup the IO |     // Hookup the IO | ||||||
|     hyper_io *io = hyper_io_new(); |     hyper_io *io = hyper_io_new(); | ||||||
|     hyper_io_set_userdata(io, (void *)conn); |     hyper_io_set_userdata(io, (void *)conn); | ||||||
| @@ -312,16 +315,15 @@ int main(int argc, char *argv[]) { | |||||||
|         if (sel_ret < 0) { |         if (sel_ret < 0) { | ||||||
|             printf("select() error\n"); |             printf("select() error\n"); | ||||||
|             return 1; |             return 1; | ||||||
|         } |         } else { | ||||||
|  |             if (FD_ISSET(conn->fd, &fds_read)) { | ||||||
|         if (FD_ISSET(conn->fd, &fds_read)) { |                 hyper_waker_wake(conn->read_waker); | ||||||
|             hyper_waker_wake(conn->read_waker); |                 conn->read_waker = NULL; | ||||||
|             conn->read_waker = NULL; |             } | ||||||
|         } |             if (FD_ISSET(conn->fd, &fds_write)) { | ||||||
|  |                 hyper_waker_wake(conn->write_waker); | ||||||
|         if (FD_ISSET(conn->fd, &fds_write)) { |                 conn->write_waker = NULL; | ||||||
|             hyper_waker_wake(conn->write_waker); |             } | ||||||
|             conn->write_waker = NULL; |  | ||||||
|         } |         } | ||||||
|  |  | ||||||
|     } |     } | ||||||
|   | |||||||
| @@ -24,42 +24,44 @@ static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t b | |||||||
|     struct conn_data *conn = (struct conn_data *)userdata; |     struct conn_data *conn = (struct conn_data *)userdata; | ||||||
|     ssize_t ret = read(conn->fd, buf, buf_len); |     ssize_t ret = read(conn->fd, buf, buf_len); | ||||||
|  |  | ||||||
|     if (ret >= 0) { |     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 { | ||||||
|         return ret; |         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) { | 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; |     struct conn_data *conn = (struct conn_data *)userdata; | ||||||
|     ssize_t ret = write(conn->fd, buf, buf_len); |     ssize_t ret = write(conn->fd, buf, buf_len); | ||||||
|  |  | ||||||
|     if (ret >= 0) { |     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 { | ||||||
|         return ret; |         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) { | static void free_conn_data(struct conn_data *conn) { | ||||||
| @@ -96,9 +98,9 @@ static int connect_to(const char *host, const char *port) { | |||||||
|  |  | ||||||
|         if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { |         if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) { | ||||||
|             break; |             break; | ||||||
|  |         } else { | ||||||
|  |             close(sfd); | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         close(sfd); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     freeaddrinfo(result); |     freeaddrinfo(result); | ||||||
| @@ -124,20 +126,17 @@ static int poll_req_upload(void *userdata, | |||||||
|     struct upload_body* upload = userdata; |     struct upload_body* upload = userdata; | ||||||
|  |  | ||||||
|     ssize_t res = read(upload->fd, upload->buf, upload->len); |     ssize_t res = read(upload->fd, upload->buf, upload->len); | ||||||
|     if (res > 0) { |     if (res < 0) { | ||||||
|         *chunk = hyper_buf_copy(upload->buf, res); |         printf("error reading upload file: %d", errno); | ||||||
|         return HYPER_POLL_READY; |         return HYPER_POLL_ERROR; | ||||||
|     } |     } else if (res == 0) { | ||||||
|  |  | ||||||
|     if (res == 0) { |  | ||||||
|         // All done! |         // All done! | ||||||
|         *chunk = NULL; |         *chunk = NULL; | ||||||
|         return HYPER_POLL_READY; |         return HYPER_POLL_READY; | ||||||
|  |     } else { | ||||||
|  |         *chunk = hyper_buf_copy(upload->buf, res); | ||||||
|  |         return HYPER_POLL_READY; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Oh no! |  | ||||||
|     printf("error reading upload file: %d", errno); |  | ||||||
|     return HYPER_POLL_ERROR; |  | ||||||
| } | } | ||||||
|  |  | ||||||
| static int print_each_header(void *userdata, | static int print_each_header(void *userdata, | ||||||
| @@ -349,20 +348,20 @@ int main(int argc, char *argv[]) { | |||||||
|                     hyper_executor_push(exec, body_data); |                     hyper_executor_push(exec, body_data); | ||||||
|  |  | ||||||
|                     break; |                     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: |             case EXAMPLE_NOT_SET: | ||||||
|                 // A background task for hyper completed... |                 // A background task for hyper completed... | ||||||
|                 hyper_task_free(task); |                 hyper_task_free(task); | ||||||
| @@ -388,17 +387,17 @@ int main(int argc, char *argv[]) { | |||||||
|         if (sel_ret < 0) { |         if (sel_ret < 0) { | ||||||
|             printf("select() error\n"); |             printf("select() error\n"); | ||||||
|             return 1; |             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,22 +355,6 @@ void hyper_clientconn_free(struct hyper_clientconn *conn); | |||||||
|  */ |  */ | ||||||
| struct hyper_clientconn_options *hyper_clientconn_options_new(void); | 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 *`. |  Free a `hyper_clientconn_options *`. | ||||||
|  */ |  */ | ||||||
| @@ -402,16 +386,6 @@ enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options * | |||||||
| enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts, | enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts, | ||||||
|                                                      int enabled); |                                                      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`. |  Frees a `hyper_error`. | ||||||
|  */ |  */ | ||||||
|   | |||||||
| @@ -4,7 +4,7 @@ | |||||||
|  |  | ||||||
| - Don't be mean. | - Don't be mean. | ||||||
| - Insulting anyone is prohibited. | - Insulting anyone is prohibited. | ||||||
| - Harassment of any kind is prohibited. | - Harrassment of any kind is prohibited. | ||||||
| - If another person feels uncomfortable with your remarks, stop it. | - If another person feels uncomfortable with your remarks, stop it. | ||||||
| - If a moderator deems your comment or conduct as inappropriate, 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. | - Disagreeing is fine, but keep it to technical arguments. Never attack the person. | ||||||
|   | |||||||
| @@ -1,111 +0,0 @@ | |||||||
| # 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,68 +2,6 @@ | |||||||
|  |  | ||||||
| The [issue tracker][issues] for hyper is where we track all features, bugs, and discuss proposals. | 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 | ## Labels | ||||||
|  |  | ||||||
| Issues are organized with a set of labels. Most labels follow a system of being prefixed by a "type". | Issues are organized with a set of labels. Most labels follow a system of being prefixed by a "type". | ||||||
| @@ -109,5 +47,3 @@ 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. | - **S-refactor**: improve internal code to help readability and maintenance. | ||||||
|  |  | ||||||
| [issues]: https://github.com/hyperium/hyper/issues | [issues]: https://github.com/hyperium/hyper/issues | ||||||
| [COC]: ./CODE_OF_CONDUCT.md |  | ||||||
| [PRs]: ./PULL_REQUESTS.md |  | ||||||
|   | |||||||
| @@ -1,30 +0,0 @@ | |||||||
| # 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 | guaranteed. We try to increase the MSRV responsibly, only when a significant | ||||||
| new feature is needed. | new feature is needed. | ||||||
|  |  | ||||||
| The current MSRV is: **1.56**. | The current MSRV is: **1.49**. | ||||||
|   | |||||||
| @@ -1,49 +0,0 @@ | |||||||
| # 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
									
									
									
									
									
								
							
							
						
						
									
										404
									
								
								docs/ROADMAP.md
									
									
									
									
									
								
							| @@ -1,404 +0,0 @@ | |||||||
| # 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
									
									
									
									
									
								
							
							
						
						
									
										100
									
								
								docs/TENETS.md
									
									
									
									
									
								
							| @@ -1,100 +0,0 @@ | |||||||
| # 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
									
									
									
									
									
								
							
							
						
						
									
										230
									
								
								docs/VISION.md
									
									
									
									
									
								
							| @@ -1,230 +0,0 @@ | |||||||
| # 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. |  | ||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| Before Width: | Height: | Size: 9.7 KiB | 
| @@ -2,9 +2,8 @@ | |||||||
| #![warn(rust_2018_idioms)] | #![warn(rust_2018_idioms)] | ||||||
| use std::env; | use std::env; | ||||||
|  |  | ||||||
| use hyper::{body::HttpBody as _, Body, Request}; | use hyper::{body::HttpBody as _, Client}; | ||||||
| use tokio::io::{self, AsyncWriteExt as _}; | use tokio::io::{self, AsyncWriteExt as _}; | ||||||
| use tokio::net::TcpStream; |  | ||||||
|  |  | ||||||
| // A simple type alias so as to DRY. | // A simple type alias so as to DRY. | ||||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | ||||||
| @@ -34,20 +33,9 @@ async fn main() -> Result<()> { | |||||||
| } | } | ||||||
|  |  | ||||||
| async fn fetch_url(url: hyper::Uri) -> Result<()> { | async fn fetch_url(url: hyper::Uri) -> Result<()> { | ||||||
|     let host = url.host().expect("uri has no host"); |     let client = Client::new(); | ||||||
|     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?; |     let mut res = client.get(url).await?; | ||||||
|     tokio::task::spawn(async move { |  | ||||||
|         if let Err(err) = conn.await { |  | ||||||
|             println!("Connection failed: {:?}", err); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     let req = Request::builder().uri(url).body(Body::empty()).unwrap(); |  | ||||||
|     let mut res = sender.send_request(req).await?; |  | ||||||
|  |  | ||||||
|     println!("Response: {}", res.status()); |     println!("Response: {}", res.status()); | ||||||
|     println!("Headers: {:#?}\n", res.headers()); |     println!("Headers: {:#?}\n", res.headers()); | ||||||
|   | |||||||
| @@ -1,10 +1,9 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
| #![warn(rust_2018_idioms)] | #![warn(rust_2018_idioms)] | ||||||
|  |  | ||||||
| use hyper::Body; | use hyper::body::Buf; | ||||||
| use hyper::{body::Buf, Request}; | use hyper::Client; | ||||||
| use serde::Deserialize; | use serde::Deserialize; | ||||||
| use tokio::net::TcpStream; |  | ||||||
|  |  | ||||||
| // A simple type alias so as to DRY. | // A simple type alias so as to DRY. | ||||||
| type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | type Result<T> = std::result::Result<T, Box<dyn std::error::Error + Send + Sync>>; | ||||||
| @@ -23,22 +22,10 @@ async fn main() -> Result<()> { | |||||||
| } | } | ||||||
|  |  | ||||||
| async fn fetch_json(url: hyper::Uri) -> Result<Vec<User>> { | async fn fetch_json(url: hyper::Uri) -> Result<Vec<User>> { | ||||||
|     let host = url.host().expect("uri has no host"); |     let client = Client::new(); | ||||||
|     let port = url.port_u16().unwrap_or(80); |  | ||||||
|     let addr = format!("{}:{}", host, port); |  | ||||||
|  |  | ||||||
|     let stream = TcpStream::connect(addr).await?; |  | ||||||
|  |  | ||||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; |  | ||||||
|     tokio::task::spawn(async move { |  | ||||||
|         if let Err(err) = conn.await { |  | ||||||
|             println!("Connection failed: {:?}", err); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     // Fetch the url... |     // Fetch the url... | ||||||
|     let req = Request::builder().uri(url).body(Body::empty()).unwrap(); |     let res = client.get(url).await?; | ||||||
|     let res = sender.send_request(req).await?; |  | ||||||
|  |  | ||||||
|     // asynchronously aggregate the chunks of the body |     // asynchronously aggregate the chunks of the body | ||||||
|     let body = hyper::body::aggregate(res).await?; |     let body = hyper::body::aggregate(res).await?; | ||||||
|   | |||||||
| @@ -1,11 +1,8 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use std::net::SocketAddr; | use futures_util::TryStreamExt; | ||||||
|  | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::server::conn::Http; | use hyper::{Body, Method, Request, Response, Server, StatusCode}; | ||||||
| use hyper::service::service_fn; |  | ||||||
| use hyper::{Body, Method, Request, Response, StatusCode}; |  | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| /// This is our service handler. It receives a Request, routes on its | /// This is our service handler. It receives a Request, routes on its | ||||||
| /// path, and returns a Future of a Response. | /// path, and returns a Future of a Response. | ||||||
| @@ -19,17 +16,16 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | |||||||
|         // Simply echo the body back to the client. |         // Simply echo the body back to the client. | ||||||
|         (&Method::POST, "/echo") => Ok(Response::new(req.into_body())), |         (&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. |         // Convert to uppercase before sending back to client using a stream. | ||||||
|         // (&Method::POST, "/echo/uppercase") => { |         (&Method::POST, "/echo/uppercase") => { | ||||||
|         // let chunk_stream = req.into_body().map_ok(|chunk| { |             let chunk_stream = req.into_body().map_ok(|chunk| { | ||||||
|         //     chunk |                 chunk | ||||||
|         //         .iter() |                     .iter() | ||||||
|         //         .map(|byte| byte.to_ascii_uppercase()) |                     .map(|byte| byte.to_ascii_uppercase()) | ||||||
|         //         .collect::<Vec<u8>>() |                     .collect::<Vec<u8>>() | ||||||
|         // }); |             }); | ||||||
|         // Ok(Response::new(Body::wrap_stream(chunk_stream))) |             Ok(Response::new(Body::wrap_stream(chunk_stream))) | ||||||
|         // } |         } | ||||||
|  |  | ||||||
|         // Reverse the entire body before sending back to the client. |         // Reverse the entire body before sending back to the client. | ||||||
|         // |         // | ||||||
| @@ -55,17 +51,15 @@ async fn echo(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | |||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||||
|     let addr = SocketAddr::from(([127, 0, 0, 1], 3000)); |     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 listener = TcpListener::bind(addr).await?; |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|     loop { |  | ||||||
|         let (stream, _) = listener.accept().await?; |  | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |     server.await?; | ||||||
|             if let Err(err) = Http::new().serve_connection(stream, service_fn(echo)).await { |  | ||||||
|                 println!("Error serving connection: {:?}", err); |     Ok(()) | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,63 +1,51 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use hyper::{server::conn::Http, service::service_fn}; | use hyper::service::{make_service_fn, service_fn}; | ||||||
|  | use hyper::{Client, Error, Server}; | ||||||
| use std::net::SocketAddr; | use std::net::SocketAddr; | ||||||
| use tokio::net::{TcpListener, TcpStream}; |  | ||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | async fn main() { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let in_addr: SocketAddr = ([127, 0, 0, 1], 3001).into(); |     let in_addr = ([127, 0, 0, 1], 3001).into(); | ||||||
|     let out_addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); |     let out_addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); | ||||||
|  |  | ||||||
|  |     let client_main = Client::new(); | ||||||
|  |  | ||||||
|     let out_addr_clone = out_addr.clone(); |     let out_addr_clone = out_addr.clone(); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(in_addr).await?; |     // 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); | ||||||
|  |  | ||||||
|     println!("Listening on http://{}", in_addr); |     println!("Listening on http://{}", in_addr); | ||||||
|     println!("Proxying on http://{}", out_addr); |     println!("Proxying on http://{}", out_addr); | ||||||
|  |  | ||||||
|     loop { |     if let Err(e) = server.await { | ||||||
|         let (stream, _) = listener.accept().await?; |         eprintln!("server error: {}", e); | ||||||
|  |  | ||||||
|         // 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,12 +1,9 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use std::convert::Infallible; | use std::convert::Infallible; | ||||||
| use std::net::SocketAddr; |  | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::service::service_fn; | use hyper::{Body, Request, Response, Server}; | ||||||
| use hyper::{Body, Request, Response}; |  | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> { | async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> { | ||||||
|     Ok(Response::new(Body::from("Hello World!"))) |     Ok(Response::new(Body::from("Hello World!"))) | ||||||
| @@ -16,20 +13,22 @@ async fn hello(_: Request<Body>) -> Result<Response<Body>, Infallible> { | |||||||
| pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | pub async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); |     // 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 listener = TcpListener::bind(addr).await?; |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|     loop { |  | ||||||
|         let (stream, _) = listener.accept().await?; |  | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |     server.await?; | ||||||
|             if let Err(err) = Http::new() |  | ||||||
|                 .serve_connection(stream, service_fn(hello)) |     Ok(()) | ||||||
|                 .await |  | ||||||
|             { |  | ||||||
|                 println!("Error serving connection: {:?}", err); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
|  | use std::convert::Infallible; | ||||||
| use std::net::SocketAddr; | use std::net::SocketAddr; | ||||||
|  |  | ||||||
| use hyper::client::conn::Builder; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::server::conn::Http; |  | ||||||
| use hyper::service::service_fn; |  | ||||||
| use hyper::upgrade::Upgraded; | use hyper::upgrade::Upgraded; | ||||||
| use hyper::{Body, Method, Request, Response}; | use hyper::{Body, Client, Method, Request, Response, Server}; | ||||||
|  |  | ||||||
| use tokio::net::{TcpListener, TcpStream}; | use tokio::net::TcpStream; | ||||||
|  |  | ||||||
|  | type HttpClient = Client<hyper::client::HttpConnector>; | ||||||
|  |  | ||||||
| // To try this example: | // To try this example: | ||||||
| // 1. cargo run --example http_proxy | // 1. cargo run --example http_proxy | ||||||
| @@ -18,29 +19,32 @@ use tokio::net::{TcpListener, TcpStream}; | |||||||
| // 3. send requests | // 3. send requests | ||||||
| //    $ curl -i https://www.some_domain.com/ | //    $ curl -i https://www.some_domain.com/ | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | async fn main() { | ||||||
|     let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); |     let addr = SocketAddr::from(([127, 0, 0, 1], 8100)); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(addr).await?; |     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); | ||||||
|  |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|  |  | ||||||
|     loop { |     if let Err(e) = server.await { | ||||||
|         let (stream, _) = listener.accept().await?; |         eprintln!("server error: {}", e); | ||||||
|  |  | ||||||
|         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(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | async fn proxy(client: HttpClient, req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | ||||||
|     println!("req: {:?}", req); |     println!("req: {:?}", req); | ||||||
|  |  | ||||||
|     if Method::CONNECT == req.method() { |     if Method::CONNECT == req.method() { | ||||||
| @@ -78,24 +82,7 @@ async fn proxy(req: Request<Body>) -> Result<Response<Body>, hyper::Error> { | |||||||
|             Ok(resp) |             Ok(resp) | ||||||
|         } |         } | ||||||
|     } else { |     } else { | ||||||
|         let host = req.uri().host().expect("uri has no host"); |         client.request(req).await | ||||||
|         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,13 +1,9 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
| #![warn(rust_2018_idioms)] | #![warn(rust_2018_idioms)] | ||||||
|  |  | ||||||
| use std::net::SocketAddr; |  | ||||||
|  |  | ||||||
| use futures_util::future::join; | use futures_util::future::join; | ||||||
| use hyper::server::conn::Http; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::service::service_fn; | use hyper::{Body, Request, Response, Server}; | ||||||
| use hyper::{Body, Request, Response}; |  | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| static INDEX1: &[u8] = b"The 1st service!"; | static INDEX1: &[u8] = b"The 1st service!"; | ||||||
| static INDEX2: &[u8] = b"The 2nd service!"; | static INDEX2: &[u8] = b"The 2nd service!"; | ||||||
| @@ -24,40 +20,16 @@ async fn index2(_: Request<Body>) -> Result<Response<Body>, hyper::Error> { | |||||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr1: SocketAddr = ([127, 0, 0, 1], 1337).into(); |     let addr1 = ([127, 0, 0, 1], 1337).into(); | ||||||
|     let addr2: SocketAddr = ([127, 0, 0, 1], 1338).into(); |     let addr2 = ([127, 0, 0, 1], 1338).into(); | ||||||
|  |  | ||||||
|     let srv1 = async move { |     let srv1 = Server::bind(&addr1).serve(make_service_fn(|_| async { | ||||||
|         let listener = TcpListener::bind(addr1).await.unwrap(); |         Ok::<_, hyper::Error>(service_fn(index1)) | ||||||
|         loop { |     })); | ||||||
|             let (stream, _) = listener.accept().await.unwrap(); |  | ||||||
|  |  | ||||||
|             tokio::task::spawn(async move { |     let srv2 = Server::bind(&addr2).serve(make_service_fn(|_| async { | ||||||
|                 if let Err(err) = Http::new() |         Ok::<_, hyper::Error>(service_fn(index2)) | ||||||
|                     .serve_connection(stream, service_fn(index1)) |     })); | ||||||
|                     .await |  | ||||||
|                 { |  | ||||||
|                     println!("Error serving connection: {:?}", err); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     let srv2 = async move { |  | ||||||
|         let listener = TcpListener::bind(addr2).await.unwrap(); |  | ||||||
|         loop { |  | ||||||
|             let (stream, _) = listener.accept().await.unwrap(); |  | ||||||
|  |  | ||||||
|             tokio::task::spawn(async move { |  | ||||||
|                 if let Err(err) = Http::new() |  | ||||||
|                     .serve_connection(stream, service_fn(index2)) |  | ||||||
|                     .await |  | ||||||
|                 { |  | ||||||
|                     println!("Error serving connection: {:?}", err); |  | ||||||
|                 } |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     println!("Listening on http://{} and http://{}", addr1, addr2); |     println!("Listening on http://{} and http://{}", addr1, addr2); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,13 +1,10 @@ | |||||||
| // #![deny(warnings)]  // FIXME: https://github.com/rust-lang/rust/issues/62411 | // #![deny(warnings)]  // FIXME: https://github.com/rust-lang/rust/issues/62411 | ||||||
| #![warn(rust_2018_idioms)] | #![warn(rust_2018_idioms)] | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::service::service_fn; | use hyper::{Body, Method, Request, Response, Server, StatusCode}; | ||||||
| use hyper::{Body, Method, Request, Response, StatusCode}; |  | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| use std::collections::HashMap; | use std::collections::HashMap; | ||||||
| use std::net::SocketAddr; |  | ||||||
| use url::form_urlencoded; | use url::form_urlencoded; | ||||||
|  |  | ||||||
| static INDEX: &[u8] = b"<html><body><form action=\"post\" method=\"post\">Name: <input type=\"text\" name=\"name\"><br>Number: <input type=\"text\" name=\"number\"><br><input type=\"submit\"></body></html>"; | static INDEX: &[u8] = b"<html><body><form action=\"post\" method=\"post\">Name: <input type=\"text\" name=\"name\"><br>Number: <input type=\"text\" name=\"number\"><br><input type=\"submit\"></body></html>"; | ||||||
| @@ -105,20 +102,15 @@ async fn param_example(req: Request<Body>) -> Result<Response<Body>, hyper::Erro | |||||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 1337).into(); |     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 listener = TcpListener::bind(addr).await?; |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|     loop { |  | ||||||
|         let (stream, _) = listener.accept().await?; |  | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |     server.await?; | ||||||
|             if let Err(err) = Http::new() |  | ||||||
|                 .serve_connection(stream, service_fn(param_example)) |     Ok(()) | ||||||
|                 .await |  | ||||||
|             { |  | ||||||
|                 println!("Error serving connection: {:?}", err); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,36 +1,30 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use std::net::SocketAddr; | use tokio::fs::File; | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; | use tokio_util::codec::{BytesCodec, FramedRead}; | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| use hyper::service::service_fn; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::{Body, Method, Request, Response, Result, StatusCode}; | use hyper::{Body, Method, Request, Response, Result, Server, StatusCode}; | ||||||
|  |  | ||||||
| static INDEX: &str = "examples/send_file_index.html"; | static INDEX: &str = "examples/send_file_index.html"; | ||||||
| static NOTFOUND: &[u8] = b"Not Found"; | static NOTFOUND: &[u8] = b"Not Found"; | ||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> std::result::Result<(), Box<dyn std::error::Error>> { | async fn main() { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); |     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 listener = TcpListener::bind(addr).await?; |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|  |  | ||||||
|     loop { |     if let Err(e) = server.await { | ||||||
|         let (stream, _) = listener.accept().await?; |         eprintln!("server error: {}", e); | ||||||
|  |  | ||||||
|         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); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| @@ -54,8 +48,11 @@ fn not_found() -> Response<Body> { | |||||||
| } | } | ||||||
|  |  | ||||||
| async fn simple_file_send(filename: &str) -> Result<Response<Body>> { | async fn simple_file_send(filename: &str) -> Result<Response<Body>> { | ||||||
|     if let Ok(contents) = tokio::fs::read(filename).await { |     // Serve a file by asynchronously reading it by chunks using tokio-util crate. | ||||||
|         let body = contents.into(); |  | ||||||
|  |     if let Ok(file) = File::open(filename).await { | ||||||
|  |         let stream = FramedRead::new(file, BytesCodec::new()); | ||||||
|  |         let body = Body::wrap_stream(stream); | ||||||
|         return Ok(Response::new(body)); |         return Ok(Response::new(body)); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,10 +1,7 @@ | |||||||
| use hyper::server::conn::Http; |  | ||||||
| use hyper::service::Service; | use hyper::service::Service; | ||||||
| use hyper::{Body, Request, Response}; | use hyper::{Body, Request, Response, Server}; | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| use std::future::Future; | use std::future::Future; | ||||||
| use std::net::SocketAddr; |  | ||||||
| use std::pin::Pin; | use std::pin::Pin; | ||||||
| use std::task::{Context, Poll}; | use std::task::{Context, Poll}; | ||||||
|  |  | ||||||
| @@ -12,23 +9,13 @@ type Counter = i32; | |||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> { | ||||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); |     let addr = ([127, 0, 0, 1], 3000).into(); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(addr).await?; |     let server = Server::bind(&addr).serve(MakeSvc { counter: 81818 }); | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|  |  | ||||||
|     loop { |     server.await?; | ||||||
|         let (stream, _) = listener.accept().await?; |     Ok(()) | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |  | ||||||
|             if let Err(err) = Http::new() |  | ||||||
|                 .serve_connection(stream, Svc { counter: 81818 }) |  | ||||||
|                 .await |  | ||||||
|             { |  | ||||||
|                 println!("Failed to serve connection: {:?}", err); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|  |  | ||||||
| struct Svc { | struct Svc { | ||||||
| @@ -67,3 +54,23 @@ impl Service<Request<Body>> for Svc { | |||||||
|         Box::pin(async { res }) |         Box::pin(async { res }) | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | struct MakeSvc { | ||||||
|  |     counter: Counter, | ||||||
|  | } | ||||||
|  |  | ||||||
|  | impl<T> Service<T> for MakeSvc { | ||||||
|  |     type Response = Svc; | ||||||
|  |     type Error = hyper::Error; | ||||||
|  |     type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>; | ||||||
|  |  | ||||||
|  |     fn poll_ready(&mut self, _: &mut Context) -> Poll<Result<(), Self::Error>> { | ||||||
|  |         Poll::Ready(Ok(())) | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn call(&mut self, _: T) -> Self::Future { | ||||||
|  |         let counter = self.counter.clone(); | ||||||
|  |         let fut = async move { Ok(Svc { counter }) }; | ||||||
|  |         Box::pin(fut) | ||||||
|  |     } | ||||||
|  | } | ||||||
|   | |||||||
| @@ -1,15 +1,13 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; |  | ||||||
| use std::cell::Cell; | use std::cell::Cell; | ||||||
| use std::net::SocketAddr; |  | ||||||
| use std::rc::Rc; | use std::rc::Rc; | ||||||
| use tokio::net::TcpListener; | use tokio::sync::oneshot; | ||||||
|  |  | ||||||
| use hyper::body::{Bytes, HttpBody}; | use hyper::body::{Bytes, HttpBody}; | ||||||
| use hyper::header::{HeaderMap, HeaderValue}; | use hyper::header::{HeaderMap, HeaderValue}; | ||||||
| use hyper::service::service_fn; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::{Error, Response}; | use hyper::{Error, Response, Server}; | ||||||
| use std::marker::PhantomData; | use std::marker::PhantomData; | ||||||
| use std::pin::Pin; | use std::pin::Pin; | ||||||
| use std::task::{Context, Poll}; | use std::task::{Context, Poll}; | ||||||
| @@ -48,7 +46,7 @@ impl HttpBody for Body { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| fn main() -> Result<(), Box<dyn std::error::Error>> { | fn main() { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     // Configure a runtime that runs everything on the current thread |     // Configure a runtime that runs everything on the current thread | ||||||
| @@ -59,39 +57,43 @@ fn main() -> Result<(), Box<dyn std::error::Error>> { | |||||||
|  |  | ||||||
|     // Combine it with a `LocalSet,  which means it can spawn !Send futures... |     // Combine it with a `LocalSet,  which means it can spawn !Send futures... | ||||||
|     let local = tokio::task::LocalSet::new(); |     let local = tokio::task::LocalSet::new(); | ||||||
|     local.block_on(&rt, run()) |     local.block_on(&rt, run()); | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn run() -> Result<(), Box<dyn std::error::Error>> { | async fn run() { | ||||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); |     let addr = ([127, 0, 0, 1], 3000).into(); | ||||||
|  |  | ||||||
|     // Using a !Send request counter is fine on 1 thread... |     // Using a !Send request counter is fine on 1 thread... | ||||||
|     let counter = Rc::new(Cell::new(0)); |     let counter = Rc::new(Cell::new(0)); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(addr).await?; |     let make_service = make_service_fn(move |_| { | ||||||
|     println!("Listening on http://{}", addr); |  | ||||||
|     loop { |  | ||||||
|         let (stream, _) = listener.accept().await?; |  | ||||||
|  |  | ||||||
|         // For each connection, clone the counter to use in our service... |         // For each connection, clone the counter to use in our service... | ||||||
|         let cnt = counter.clone(); |         let cnt = counter.clone(); | ||||||
|  |  | ||||||
|         let service = service_fn(move |_| { |         async move { | ||||||
|             let prev = cnt.get(); |             Ok::<_, Error>(service_fn(move |_| { | ||||||
|             cnt.set(prev + 1); |                 let prev = cnt.get(); | ||||||
|             let value = cnt.get(); |                 cnt.set(prev + 1); | ||||||
|             async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) } |                 let value = cnt.get(); | ||||||
|         }); |                 async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", value)))) } | ||||||
|  |             })) | ||||||
|  |         } | ||||||
|  |     }); | ||||||
|  |  | ||||||
|         tokio::task::spawn_local(async move { |     let server = Server::bind(&addr).executor(LocalExec).serve(make_service); | ||||||
|             if let Err(err) = Http::new() |  | ||||||
|                 .with_executor(LocalExec) |     // Just shows that with_graceful_shutdown compiles with !Send, | ||||||
|                 .serve_connection(stream, service) |     // !Sync HttpBody. | ||||||
|                 .await |     let (_tx, rx) = oneshot::channel::<()>(); | ||||||
|             { |     let server = server.with_graceful_shutdown(async move { | ||||||
|                 println!("Error serving connection: {:?}", err); |         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); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,46 +1,52 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use std::net::SocketAddr; |  | ||||||
| use std::sync::{ | use std::sync::{ | ||||||
|     atomic::{AtomicUsize, Ordering}, |     atomic::{AtomicUsize, Ordering}, | ||||||
|     Arc, |     Arc, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| use hyper::{server::conn::Http, service::service_fn}; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::{Body, Error, Response}; | use hyper::{Body, Error, Response, Server}; | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | async fn main() { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 3000).into(); |     let addr = ([127, 0, 0, 1], 3000).into(); | ||||||
|  |  | ||||||
|     // For the most basic of state, we just share a counter, that increments |     // For the most basic of state, we just share a counter, that increments | ||||||
|     // with each request, and we send its value back in the response. |     // with each request, and we send its value back in the response. | ||||||
|     let counter = Arc::new(AtomicUsize::new(0)); |     let counter = Arc::new(AtomicUsize::new(0)); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(addr).await?; |     // The closure inside `make_service_fn` is run for each connection, | ||||||
|     println!("Listening on http://{}", addr); |     // creating a 'service' to handle requests for that specific connection. | ||||||
|     loop { |     let make_service = make_service_fn(move |_| { | ||||||
|         let (stream, _) = listener.accept().await?; |         // 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. | ||||||
|  |         // | ||||||
|         // Each connection could send multiple requests, so |         // Each connection could send multiple requests, so | ||||||
|         // the `Service` needs a clone to handle later requests. |         // the `Service` needs a clone to handle later requests. | ||||||
|         let counter = counter.clone(); |         let counter = counter.clone(); | ||||||
|  |  | ||||||
|         // This is the `Service` that will handle the connection. |         async move { | ||||||
|         // `service_fn` is a helper to convert a function that |             // This is the `Service` that will handle the connection. | ||||||
|         // returns a Response into a `Service`. |             // `service_fn` is a helper to convert a function that | ||||||
|         let service = service_fn(move |_req| { |             // returns a Response into a `Service`. | ||||||
|             // Get the current count, and also increment by 1, in a single |             Ok::<_, Error>(service_fn(move |_req| { | ||||||
|             // atomic operation. |                 // Get the current count, and also increment by 1, in a single | ||||||
|             let count = counter.fetch_add(1, Ordering::AcqRel); |                 // atomic operation. | ||||||
|             async move { Ok::<_, Error>(Response::new(Body::from(format!("Request #{}", count)))) } |                 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); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										27
									
								
								examples/tower_client.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								examples/tower_client.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,27 @@ | |||||||
|  | #![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,13 +1,10 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use std::net::SocketAddr; |  | ||||||
| use std::task::{Context, Poll}; | use std::task::{Context, Poll}; | ||||||
|  |  | ||||||
| use futures_util::future; | use futures_util::future; | ||||||
| use hyper::server::conn::Http; |  | ||||||
| use hyper::service::Service; | use hyper::service::Service; | ||||||
| use hyper::{Body, Request, Response}; | use hyper::{Body, Request, Response, Server}; | ||||||
| use tokio::net::TcpListener; |  | ||||||
|  |  | ||||||
| const ROOT: &str = "/"; | const ROOT: &str = "/"; | ||||||
|  |  | ||||||
| @@ -39,22 +36,33 @@ impl Service<Request<Body>> for Svc { | |||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | pub struct MakeSvc; | ||||||
|  |  | ||||||
|  | impl<T> Service<T> for MakeSvc { | ||||||
|  |     type Response = Svc; | ||||||
|  |     type Error = std::io::Error; | ||||||
|  |     type Future = future::Ready<Result<Self::Response, Self::Error>>; | ||||||
|  |  | ||||||
|  |     fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> { | ||||||
|  |         Ok(()).into() | ||||||
|  |     } | ||||||
|  |  | ||||||
|  |     fn call(&mut self, _: T) -> Self::Future { | ||||||
|  |         future::ok(Svc) | ||||||
|  |     } | ||||||
|  | } | ||||||
|  |  | ||||||
| #[tokio::main] | #[tokio::main] | ||||||
| async fn main() -> Result<(), Box<dyn std::error::Error>> { | async fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); |     let addr = "127.0.0.1:1337".parse().unwrap(); | ||||||
|  |  | ||||||
|  |     let server = Server::bind(&addr).serve(MakeSvc); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(addr).await?; |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|  |  | ||||||
|     loop { |     server.await?; | ||||||
|         let (stream, _) = listener.accept().await?; |  | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |     Ok(()) | ||||||
|             if let Err(err) = Http::new().serve_connection(stream, Svc).await { |  | ||||||
|                 println!("Failed to serve connection: {:?}", err); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -3,15 +3,13 @@ | |||||||
| // Note: `hyper::upgrade` docs link to this upgrade. | // Note: `hyper::upgrade` docs link to this upgrade. | ||||||
| use std::str; | use std::str; | ||||||
|  |  | ||||||
| use hyper::server::conn::Http; |  | ||||||
| use tokio::io::{AsyncReadExt, AsyncWriteExt}; | use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
| use tokio::net::{TcpListener, TcpStream}; | use tokio::sync::oneshot; | ||||||
| use tokio::sync::watch; |  | ||||||
|  |  | ||||||
| use hyper::header::{HeaderValue, UPGRADE}; | use hyper::header::{HeaderValue, UPGRADE}; | ||||||
| use hyper::service::service_fn; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use hyper::upgrade::Upgraded; | use hyper::upgrade::Upgraded; | ||||||
| use hyper::{Body, Request, Response, StatusCode}; | use hyper::{Body, Client, Request, Response, Server, StatusCode}; | ||||||
| use std::net::SocketAddr; | use std::net::SocketAddr; | ||||||
|  |  | ||||||
| // A simple type alias so as to DRY. | // A simple type alias so as to DRY. | ||||||
| @@ -94,17 +92,7 @@ async fn client_upgrade_request(addr: SocketAddr) -> Result<()> { | |||||||
|         .body(Body::empty()) |         .body(Body::empty()) | ||||||
|         .unwrap(); |         .unwrap(); | ||||||
|  |  | ||||||
|     let stream = TcpStream::connect(addr).await?; |     let res = Client::new().request(req).await?; | ||||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; |  | ||||||
|  |  | ||||||
|     tokio::task::spawn(async move { |  | ||||||
|         if let Err(err) = conn.await { |  | ||||||
|             println!("Connection failed: {:?}", err); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     let res = sender.send_request(req).await?; |  | ||||||
|  |  | ||||||
|     if res.status() != StatusCode::SWITCHING_PROTOCOLS { |     if res.status() != StatusCode::SWITCHING_PROTOCOLS { | ||||||
|         panic!("Our server didn't upgrade: {}", res.status()); |         panic!("Our server didn't upgrade: {}", res.status()); | ||||||
|     } |     } | ||||||
| @@ -126,52 +114,28 @@ async fn main() { | |||||||
|     // For this example, we just make a server and our own client to talk to |     // For this example, we just make a server and our own client to talk to | ||||||
|     // it, so the exact port isn't important. Instead, let the OS give us an |     // it, so the exact port isn't important. Instead, let the OS give us an | ||||||
|     // unused port. |     // unused port. | ||||||
|     let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); |     let addr = ([127, 0, 0, 1], 0).into(); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(addr).await.expect("failed to bind"); |     let make_service = | ||||||
|  |         make_service_fn(|_| async { Ok::<_, hyper::Error>(service_fn(server_upgrade)) }); | ||||||
|  |  | ||||||
|  |     let server = Server::bind(&addr).serve(make_service); | ||||||
|  |  | ||||||
|     // We need the assigned address for the client to send it messages. |     // We need the assigned address for the client to send it messages. | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = server.local_addr(); | ||||||
|  |  | ||||||
|     // For this example, a oneshot is used to signal that after 1 request, |     // For this example, a oneshot is used to signal that after 1 request, | ||||||
|     // the server should be shutdown. |     // the server should be shutdown. | ||||||
|     let (tx, mut rx) = watch::channel(false); |     let (tx, rx) = oneshot::channel::<()>(); | ||||||
|  |     let server = server.with_graceful_shutdown(async move { | ||||||
|  |         rx.await.ok(); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     // Spawn server on the default executor, |     // Spawn server on the default executor, | ||||||
|     // which is usually a thread-pool from tokio default runtime. |     // which is usually a thread-pool from tokio default runtime. | ||||||
|     tokio::task::spawn(async move { |     tokio::task::spawn(async move { | ||||||
|         loop { |         if let Err(e) = server.await { | ||||||
|             tokio::select! { |             eprintln!("server error: {}", e); | ||||||
|                 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; |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
| @@ -183,5 +147,5 @@ async fn main() { | |||||||
|  |  | ||||||
|     // Complete the oneshot so that the server stops |     // Complete the oneshot so that the server stops | ||||||
|     // listening and the process can close down. |     // listening and the process can close down. | ||||||
|     let _ = tx.send(true); |     let _ = tx.send(()); | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,12 +1,10 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
|  |  | ||||||
| use std::net::SocketAddr; |  | ||||||
|  |  | ||||||
| use bytes::Buf; | use bytes::Buf; | ||||||
| use hyper::server::conn::Http; | use futures_util::{stream, StreamExt}; | ||||||
| use hyper::service::service_fn; | use hyper::client::HttpConnector; | ||||||
| use hyper::{header, Body, Method, Request, Response, StatusCode}; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use tokio::net::{TcpListener, TcpStream}; | use hyper::{header, Body, Client, Method, Request, Response, Server, StatusCode}; | ||||||
|  |  | ||||||
| type GenericError = Box<dyn std::error::Error + Send + Sync>; | type GenericError = Box<dyn std::error::Error + Send + Sync>; | ||||||
| type Result<T> = std::result::Result<T, GenericError>; | type Result<T> = std::result::Result<T, GenericError>; | ||||||
| @@ -17,7 +15,7 @@ static NOTFOUND: &[u8] = b"Not Found"; | |||||||
| static POST_DATA: &str = r#"{"original": "data"}"#; | static POST_DATA: &str = r#"{"original": "data"}"#; | ||||||
| static URL: &str = "http://127.0.0.1:1337/json_api"; | static URL: &str = "http://127.0.0.1:1337/json_api"; | ||||||
|  |  | ||||||
| async fn client_request_response() -> Result<Response<Body>> { | async fn client_request_response(client: &Client<HttpConnector>) -> Result<Response<Body>> { | ||||||
|     let req = Request::builder() |     let req = Request::builder() | ||||||
|         .method(Method::POST) |         .method(Method::POST) | ||||||
|         .uri(URL) |         .uri(URL) | ||||||
| @@ -25,23 +23,19 @@ async fn client_request_response() -> Result<Response<Body>> { | |||||||
|         .body(POST_DATA.into()) |         .body(POST_DATA.into()) | ||||||
|         .unwrap(); |         .unwrap(); | ||||||
|  |  | ||||||
|     let host = req.uri().host().expect("uri has no host"); |     let web_res = client.request(req).await?; | ||||||
|     let port = req.uri().port_u16().expect("uri has no port"); |     // Compare the JSON we sent (before) with what we received (after): | ||||||
|     let stream = TcpStream::connect(format!("{}:{}", host, port)).await?; |     let before = stream::once(async { | ||||||
|  |         Ok(format!( | ||||||
|     let (mut sender, conn) = hyper::client::conn::handshake(stream).await?; |             "<b>POST request body</b>: {}<br><b>Response</b>: ", | ||||||
|  |             POST_DATA, | ||||||
|     tokio::task::spawn(async move { |         ) | ||||||
|         if let Err(err) = conn.await { |         .into()) | ||||||
|             println!("Connection error: {:?}", err); |  | ||||||
|         } |  | ||||||
|     }); |     }); | ||||||
|  |     let after = web_res.into_body(); | ||||||
|  |     let body = Body::wrap_stream(before.chain(after)); | ||||||
|  |  | ||||||
|     let web_res = sender.send_request(req).await?; |     Ok(Response::new(body)) | ||||||
|  |  | ||||||
|     let res_body = web_res.into_body(); |  | ||||||
|  |  | ||||||
|     Ok(Response::new(res_body)) |  | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn api_post_response(req: Request<Body>) -> Result<Response<Body>> { | async fn api_post_response(req: Request<Body>) -> Result<Response<Body>> { | ||||||
| @@ -75,10 +69,13 @@ async fn api_get_response() -> Result<Response<Body>> { | |||||||
|     Ok(res) |     Ok(res) | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn response_examples(req: Request<Body>) -> Result<Response<Body>> { | async fn response_examples( | ||||||
|  |     req: Request<Body>, | ||||||
|  |     client: Client<HttpConnector>, | ||||||
|  | ) -> Result<Response<Body>> { | ||||||
|     match (req.method(), req.uri().path()) { |     match (req.method(), req.uri().path()) { | ||||||
|         (&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())), |         (&Method::GET, "/") | (&Method::GET, "/index.html") => Ok(Response::new(INDEX.into())), | ||||||
|         (&Method::GET, "/test.html") => client_request_response().await, |         (&Method::GET, "/test.html") => client_request_response(&client).await, | ||||||
|         (&Method::POST, "/json_api") => api_post_response(req).await, |         (&Method::POST, "/json_api") => api_post_response(req).await, | ||||||
|         (&Method::GET, "/json_api") => api_get_response().await, |         (&Method::GET, "/json_api") => api_get_response().await, | ||||||
|         _ => { |         _ => { | ||||||
| @@ -95,19 +92,27 @@ async fn response_examples(req: Request<Body>) -> Result<Response<Body>> { | |||||||
| async fn main() -> Result<()> { | async fn main() -> Result<()> { | ||||||
|     pretty_env_logger::init(); |     pretty_env_logger::init(); | ||||||
|  |  | ||||||
|     let addr: SocketAddr = "127.0.0.1:1337".parse().unwrap(); |     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 listener = TcpListener::bind(&addr).await?; |  | ||||||
|     println!("Listening on http://{}", addr); |     println!("Listening on http://{}", addr); | ||||||
|     loop { |  | ||||||
|         let (stream, _) = listener.accept().await?; |  | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |     server.await?; | ||||||
|             let service = service_fn(move |req| response_examples(req)); |  | ||||||
|  |  | ||||||
|             if let Err(err) = Http::new().serve_connection(stream, service).await { |     Ok(()) | ||||||
|                 println!("Failed to serve connection: {:?}", err); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     } |  | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										178
									
								
								src/body/body.rs
									
									
									
									
									
								
							
							
						
						
									
										178
									
								
								src/body/body.rs
									
									
									
									
									
								
							| @@ -1,15 +1,23 @@ | |||||||
| use std::borrow::Cow; | use std::borrow::Cow; | ||||||
|  | #[cfg(feature = "stream")] | ||||||
|  | use std::error::Error as StdError; | ||||||
| use std::fmt; | use std::fmt; | ||||||
|  |  | ||||||
| use bytes::Bytes; | use bytes::Bytes; | ||||||
| use futures_channel::mpsc; | use futures_channel::mpsc; | ||||||
| use futures_channel::oneshot; | use futures_channel::oneshot; | ||||||
| use futures_core::Stream; // for mpsc::Receiver | use futures_core::Stream; // for mpsc::Receiver | ||||||
|  | #[cfg(feature = "stream")] | ||||||
|  | use futures_util::TryStreamExt; | ||||||
| use http::HeaderMap; | use http::HeaderMap; | ||||||
| use http_body::{Body as HttpBody, SizeHint}; | use http_body::{Body as HttpBody, SizeHint}; | ||||||
|  |  | ||||||
| use super::DecodedLength; | use super::DecodedLength; | ||||||
|  | #[cfg(feature = "stream")] | ||||||
|  | use crate::common::sync_wrapper::SyncWrapper; | ||||||
| use crate::common::Future; | use crate::common::Future; | ||||||
|  | #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||||
|  | use crate::common::Never; | ||||||
| use crate::common::{task, watch, Pin, Poll}; | use crate::common::{task, watch, Pin, Poll}; | ||||||
| #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | ||||||
| use crate::proto::h2::ping; | use crate::proto::h2::ping; | ||||||
| @@ -27,6 +35,9 @@ type TrailersSender = oneshot::Sender<HeaderMap>; | |||||||
| #[must_use = "streams do nothing unless polled"] | #[must_use = "streams do nothing unless polled"] | ||||||
| pub struct Body { | pub struct Body { | ||||||
|     kind: Kind, |     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 { | enum Kind { | ||||||
| @@ -45,6 +56,40 @@ enum Kind { | |||||||
|     }, |     }, | ||||||
|     #[cfg(feature = "ffi")] |     #[cfg(feature = "ffi")] | ||||||
|     Ffi(crate::ffi::UserBody), |     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()`]. | /// A sender half created through [`Body::channel()`]. | ||||||
| @@ -119,8 +164,41 @@ impl Body { | |||||||
|         (tx, rx) |         (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 { |     fn new(kind: Kind) -> Body { | ||||||
|         Body { kind } |         Body { kind, extra: None } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] |     #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | ||||||
| @@ -143,6 +221,62 @@ impl Body { | |||||||
|         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")] |     #[cfg(feature = "ffi")] | ||||||
|     pub(crate) fn as_ffi_mut(&mut self) -> &mut crate::ffi::UserBody { |     pub(crate) fn as_ffi_mut(&mut self) -> &mut crate::ffi::UserBody { | ||||||
|         match self.kind { |         match self.kind { | ||||||
| @@ -195,6 +329,12 @@ impl Body { | |||||||
|  |  | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             Kind::Ffi(ref mut body) => body.poll_data(cx), |             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), | ||||||
|  |             }, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -224,7 +364,7 @@ impl HttpBody for Body { | |||||||
|         mut self: Pin<&mut Self>, |         mut self: Pin<&mut Self>, | ||||||
|         cx: &mut task::Context<'_>, |         cx: &mut task::Context<'_>, | ||||||
|     ) -> Poll<Option<Result<Self::Data, Self::Error>>> { |     ) -> Poll<Option<Result<Self::Data, Self::Error>>> { | ||||||
|         self.poll_inner(cx) |         self.poll_eof(cx) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn poll_trailers( |     fn poll_trailers( | ||||||
| @@ -265,6 +405,8 @@ impl HttpBody for Body { | |||||||
|             Kind::H2 { recv: ref h2, .. } => h2.is_end_stream(), |             Kind::H2 { recv: ref h2, .. } => h2.is_end_stream(), | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             Kind::Ffi(..) => false, |             Kind::Ffi(..) => false, | ||||||
|  |             #[cfg(feature = "stream")] | ||||||
|  |             Kind::Wrapped(..) => false, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -284,6 +426,8 @@ impl HttpBody for Body { | |||||||
|         match self.kind { |         match self.kind { | ||||||
|             Kind::Once(Some(ref val)) => SizeHint::with_exact(val.len() as u64), |             Kind::Once(Some(ref val)) => SizeHint::with_exact(val.len() as u64), | ||||||
|             Kind::Once(None) => SizeHint::with_exact(0), |             Kind::Once(None) => SizeHint::with_exact(0), | ||||||
|  |             #[cfg(feature = "stream")] | ||||||
|  |             Kind::Wrapped(..) => SizeHint::default(), | ||||||
|             Kind::Chan { content_length, .. } => opt_len!(content_length), |             Kind::Chan { content_length, .. } => opt_len!(content_length), | ||||||
|             #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] |             #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] | ||||||
|             Kind::H2 { content_length, .. } => opt_len!(content_length), |             Kind::H2 { content_length, .. } => opt_len!(content_length), | ||||||
| @@ -313,6 +457,33 @@ 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 { | impl From<Bytes> for Body { | ||||||
|     #[inline] |     #[inline] | ||||||
|     fn from(chunk: Bytes) -> Body { |     fn from(chunk: Bytes) -> Body { | ||||||
| @@ -519,7 +690,6 @@ mod tests { | |||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(not(miri))] |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn channel_abort() { |     async fn channel_abort() { | ||||||
|         let (tx, mut rx) = Body::channel(); |         let (tx, mut rx) = Body::channel(); | ||||||
| @@ -530,7 +700,6 @@ mod tests { | |||||||
|         assert!(err.is_body_write_aborted(), "{:?}", err); |         assert!(err.is_body_write_aborted(), "{:?}", err); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(not(miri))] |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn channel_abort_when_buffer_is_full() { |     async fn channel_abort_when_buffer_is_full() { | ||||||
|         let (mut tx, mut rx) = Body::channel(); |         let (mut tx, mut rx) = Body::channel(); | ||||||
| @@ -557,7 +726,6 @@ mod tests { | |||||||
|         assert_eq!(chunk2, "chunk 2"); |         assert_eq!(chunk2, "chunk 2"); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(not(miri))] |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn channel_empty() { |     async fn channel_empty() { | ||||||
|         let (_, mut rx) = Body::channel(); |         let (_, mut rx) = Body::channel(); | ||||||
|   | |||||||
| @@ -17,11 +17,17 @@ use super::HttpBody; | |||||||
| /// # Example | /// # Example | ||||||
| /// | /// | ||||||
| /// ``` | /// ``` | ||||||
|  | /// # #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||||
| /// # async fn doc() -> hyper::Result<()> { | /// # async fn doc() -> hyper::Result<()> { | ||||||
| /// # use hyper::{Body, Response}; | /// use hyper::{body::HttpBody}; | ||||||
| /// # use hyper::body::HttpBody; | /// | ||||||
| /// # | /// # let request = hyper::Request::builder() | ||||||
| /// let response = Response::new(Body::from("response body")); | /// #        .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?; | ||||||
| /// | /// | ||||||
| /// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024; | /// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024; | ||||||
| /// | /// | ||||||
|   | |||||||
							
								
								
									
										1503
									
								
								src/client/client.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1503
									
								
								src/client/client.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @@ -63,7 +63,7 @@ use std::sync::Arc; | |||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
| 
 | 
 | ||||||
| use bytes::Bytes; | use bytes::Bytes; | ||||||
| use futures_util::future; | use futures_util::future::{self, Either, FutureExt as _}; | ||||||
| use httparse::ParserConfig; | use httparse::ParserConfig; | ||||||
| use pin_project_lite::pin_project; | use pin_project_lite::pin_project; | ||||||
| use tokio::io::{AsyncRead, AsyncWrite}; | use tokio::io::{AsyncRead, AsyncWrite}; | ||||||
| @@ -84,11 +84,6 @@ use crate::rt::Executor; | |||||||
| use crate::upgrade::Upgraded; | use crate::upgrade::Upgraded; | ||||||
| use crate::{Body, Request, Response}; | use crate::{Body, Request, Response}; | ||||||
| 
 | 
 | ||||||
| #[cfg(feature = "http1")] |  | ||||||
| pub mod http1; |  | ||||||
| #[cfg(feature = "http2")] |  | ||||||
| pub mod http2; |  | ||||||
| 
 |  | ||||||
| #[cfg(feature = "http1")] | #[cfg(feature = "http1")] | ||||||
| type Http1Dispatcher<T, B> = | type Http1Dispatcher<T, B> = | ||||||
|     proto::dispatch::Dispatcher<proto::dispatch::Client<B>, B, T, proto::h1::ClientTransaction>; |     proto::dispatch::Dispatcher<proto::dispatch::Client<B>, B, T, proto::h1::ClientTransaction>; | ||||||
| @@ -161,8 +156,6 @@ pub struct Builder { | |||||||
|     h1_writev: Option<bool>, |     h1_writev: Option<bool>, | ||||||
|     h1_title_case_headers: bool, |     h1_title_case_headers: bool, | ||||||
|     h1_preserve_header_case: bool, |     h1_preserve_header_case: bool, | ||||||
|     #[cfg(feature = "ffi")] |  | ||||||
|     h1_preserve_header_order: bool, |  | ||||||
|     h1_read_buf_exact_size: Option<usize>, |     h1_read_buf_exact_size: Option<usize>, | ||||||
|     h1_max_buf_size: Option<usize>, |     h1_max_buf_size: Option<usize>, | ||||||
|     #[cfg(feature = "ffi")] |     #[cfg(feature = "ffi")] | ||||||
| @@ -214,6 +207,16 @@ pub struct Parts<T> { | |||||||
|     _inner: (), |     _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 SendRequest
 | ||||||
| 
 | 
 | ||||||
| impl<B> SendRequest<B> { | impl<B> SendRequest<B> { | ||||||
| @@ -223,6 +226,30 @@ impl<B> SendRequest<B> { | |||||||
|     pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> { |     pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> { | ||||||
|         self.dispatch.poll_ready(cx) |         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> | impl<B> SendRequest<B> | ||||||
| @@ -282,6 +309,32 @@ where | |||||||
| 
 | 
 | ||||||
|         ResponseFuture { inner } |         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> | impl<B> Service<Request<B>> for SendRequest<B> | ||||||
| @@ -307,6 +360,67 @@ 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 Connection
 | ||||||
| 
 | 
 | ||||||
| impl<T, B> Connection<T, B> | impl<T, B> Connection<T, B> | ||||||
| @@ -378,7 +492,7 @@ where | |||||||
|     ///
 |     ///
 | ||||||
|     /// This setting is configured by the server peer by sending the
 |     /// This setting is configured by the server peer by sending the
 | ||||||
|     /// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
 |     /// [`SETTINGS_ENABLE_CONNECT_PROTOCOL` parameter][2] in a `SETTINGS` frame.
 | ||||||
|     /// This method returns the currently acknowledged value received from the
 |     /// This method returns the currently acknowledged value recieved from the
 | ||||||
|     /// remote.
 |     /// remote.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
 |     /// [1]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
 | ||||||
| @@ -444,8 +558,6 @@ impl Builder { | |||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             h1_title_case_headers: false, |             h1_title_case_headers: false, | ||||||
|             h1_preserve_header_case: false, |             h1_preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             h1_preserve_header_order: false, |  | ||||||
|             h1_max_buf_size: None, |             h1_max_buf_size: None, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             h1_headers_raw: false, |             h1_headers_raw: false, | ||||||
| @@ -592,21 +704,6 @@ impl Builder { | |||||||
|         self |         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.
 |     /// Sets the exact size of the read buffer to *always* use.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Note that setting this option unsets the `http1_max_buf_size` option.
 |     /// Note that setting this option unsets the `http1_max_buf_size` option.
 | ||||||
| @@ -715,14 +812,10 @@ impl Builder { | |||||||
|     /// Sets the maximum frame size to use for HTTP2.
 |     /// Sets the maximum frame size to use for HTTP2.
 | ||||||
|     ///
 |     ///
 | ||||||
|     /// Passing `None` will do nothing.
 |     /// Passing `None` will do nothing.
 | ||||||
|     ///
 |  | ||||||
|     /// If not set, hyper will use a default.
 |  | ||||||
|     #[cfg(feature = "http2")] |     #[cfg(feature = "http2")] | ||||||
|     #[cfg_attr(docsrs, doc(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 { |     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.into(); | ||||||
|             self.h2_builder.max_frame_size = sz; |  | ||||||
|         } |  | ||||||
|         self |         self | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @@ -815,6 +908,46 @@ impl Builder { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /// Sets the maximum concurrent streams to use for HTTP2.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Passing `None` will do nothing.
 | ||||||
|  |     #[cfg(feature = "http2")] | ||||||
|  |     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||||
|  |     pub fn http2_max_concurrent_streams(&mut self, sz: impl Into<Option<u32>>) -> &mut Self { | ||||||
|  |         self.h2_builder.max_concurrent_streams = sz.into(); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Sets the max header list size to use for HTTP2.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Passing `None` will do nothing.
 | ||||||
|  |     #[cfg(feature = "http2")] | ||||||
|  |     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||||
|  |     pub fn http2_max_header_list_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self { | ||||||
|  |         self.h2_builder.max_header_list_size = sz.into(); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Enables and disables the push feature for HTTP2.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Passing `None` will do nothing.
 | ||||||
|  |     #[cfg(feature = "http2")] | ||||||
|  |     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||||
|  |     pub fn http2_enable_push(&mut self, sz: impl Into<Option<bool>>) -> &mut Self { | ||||||
|  |         self.h2_builder.enable_push = sz.into(); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|  |     /// Sets the header table size to use for HTTP2.
 | ||||||
|  |     ///
 | ||||||
|  |     /// Passing `None` will do nothing.
 | ||||||
|  |     #[cfg(feature = "http2")] | ||||||
|  |     #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] | ||||||
|  |     pub fn http2_header_table_size(&mut self, sz: impl Into<Option<u32>>) -> &mut Self { | ||||||
|  |         self.h2_builder.header_table_size = sz.into(); | ||||||
|  |         self | ||||||
|  |     } | ||||||
|  | 
 | ||||||
|     /// Constructs a connection with the configured options and IO.
 |     /// Constructs a connection with the configured options and IO.
 | ||||||
|     /// See [`client::conn`](crate::client::conn) for more.
 |     /// See [`client::conn`](crate::client::conn) for more.
 | ||||||
|     ///
 |     ///
 | ||||||
| @@ -854,10 +987,6 @@ impl Builder { | |||||||
|                     if opts.h1_preserve_header_case { |                     if opts.h1_preserve_header_case { | ||||||
|                         conn.set_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 { |                     if opts.h09_responses { | ||||||
|                         conn.set_h09_responses(); |                         conn.set_h09_responses(); | ||||||
|                     } |                     } | ||||||
| @@ -1,504 +0,0 @@ | |||||||
| //! 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) }, |  | ||||||
|             )) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
| @@ -1,436 +0,0 @@ | |||||||
| //! 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) }, |  | ||||||
|             )) |  | ||||||
|         } |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|  |  | ||||||
							
								
								
									
										425
									
								
								src/client/connect/dns.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										425
									
								
								src/client/connect/dns.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,425 @@ | |||||||
|  | //! 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); | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1007
									
								
								src/client/connect/http.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1007
									
								
								src/client/connect/http.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										412
									
								
								src/client/connect/mod.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										412
									
								
								src/client/connect/mod.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,412 @@ | |||||||
|  | //! 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")] | #[cfg(feature = "http2")] | ||||||
| use std::future::Future; | use std::future::Future; | ||||||
|  |  | ||||||
|  | use futures_util::FutureExt; | ||||||
| use tokio::sync::{mpsc, oneshot}; | use tokio::sync::{mpsc, oneshot}; | ||||||
|  |  | ||||||
| #[cfg(feature = "http2")] | #[cfg(feature = "http2")] | ||||||
| use crate::common::Pin; | use crate::common::Pin; | ||||||
| use crate::common::{task, Poll}; | use crate::common::{task, Poll}; | ||||||
|  |  | ||||||
| #[cfg(test)] |  | ||||||
| pub(crate) type RetryPromise<T, U> = oneshot::Receiver<Result<U, (crate::Error, Option<T>)>>; | 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>>; | pub(crate) type Promise<T> = oneshot::Receiver<Result<T, crate::Error>>; | ||||||
|  |  | ||||||
| @@ -59,16 +59,13 @@ impl<T, U> Sender<T, U> { | |||||||
|             .map_err(|_| crate::Error::new_closed()) |             .map_err(|_| crate::Error::new_closed()) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(test)] |  | ||||||
|     pub(crate) fn is_ready(&self) -> bool { |     pub(crate) fn is_ready(&self) -> bool { | ||||||
|         self.giver.is_wanting() |         self.giver.is_wanting() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /* |  | ||||||
|     pub(crate) fn is_closed(&self) -> bool { |     pub(crate) fn is_closed(&self) -> bool { | ||||||
|         self.giver.is_canceled() |         self.giver.is_canceled() | ||||||
|     } |     } | ||||||
|     */ |  | ||||||
|  |  | ||||||
|     fn can_send(&mut self) -> bool { |     fn can_send(&mut self) -> bool { | ||||||
|         if self.giver.give() || !self.buffered_once { |         if self.giver.give() || !self.buffered_once { | ||||||
| @@ -83,7 +80,6 @@ impl<T, U> Sender<T, U> { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(test)] |  | ||||||
|     pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { |     pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { | ||||||
|         if !self.can_send() { |         if !self.can_send() { | ||||||
|             return Err(val); |             return Err(val); | ||||||
| @@ -117,17 +113,14 @@ impl<T, U> Sender<T, U> { | |||||||
|  |  | ||||||
| #[cfg(feature = "http2")] | #[cfg(feature = "http2")] | ||||||
| impl<T, U> UnboundedSender<T, U> { | impl<T, U> UnboundedSender<T, U> { | ||||||
|     /* |  | ||||||
|     pub(crate) fn is_ready(&self) -> bool { |     pub(crate) fn is_ready(&self) -> bool { | ||||||
|         !self.giver.is_canceled() |         !self.giver.is_canceled() | ||||||
|     } |     } | ||||||
|     */ |  | ||||||
|  |  | ||||||
|     pub(crate) fn is_closed(&self) -> bool { |     pub(crate) fn is_closed(&self) -> bool { | ||||||
|         self.giver.is_canceled() |         self.giver.is_canceled() | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(test)] |  | ||||||
|     pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { |     pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> { | ||||||
|         let (tx, rx) = oneshot::channel(); |         let (tx, rx) = oneshot::channel(); | ||||||
|         self.inner |         self.inner | ||||||
| @@ -135,14 +128,6 @@ impl<T, U> UnboundedSender<T, U> { | |||||||
|             .map(move |_| rx) |             .map(move |_| rx) | ||||||
|             .map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0) |             .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")] | #[cfg(feature = "http2")] | ||||||
| @@ -184,7 +169,6 @@ impl<T, U> Receiver<T, U> { | |||||||
|  |  | ||||||
|     #[cfg(feature = "http1")] |     #[cfg(feature = "http1")] | ||||||
|     pub(crate) fn try_recv(&mut self) -> Option<(T, Callback<T, U>)> { |     pub(crate) fn try_recv(&mut self) -> Option<(T, Callback<T, U>)> { | ||||||
|         use futures_util::FutureExt; |  | ||||||
|         match self.inner.recv().now_or_never() { |         match self.inner.recv().now_or_never() { | ||||||
|             Some(Some(mut env)) => env.0.take(), |             Some(Some(mut env)) => env.0.take(), | ||||||
|             _ => None, |             _ => None, | ||||||
| @@ -214,7 +198,6 @@ impl<T, U> Drop for Envelope<T, U> { | |||||||
| } | } | ||||||
|  |  | ||||||
| pub(crate) enum Callback<T, U> { | pub(crate) enum Callback<T, U> { | ||||||
|     #[allow(unused)] |  | ||||||
|     Retry(oneshot::Sender<Result<U, (crate::Error, Option<T>)>>), |     Retry(oneshot::Sender<Result<U, (crate::Error, Option<T>)>>), | ||||||
|     NoRetry(oneshot::Sender<Result<U, crate::Error>>), |     NoRetry(oneshot::Sender<Result<U, crate::Error>>), | ||||||
| } | } | ||||||
| @@ -318,7 +301,6 @@ mod tests { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(not(miri))] |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn drop_receiver_sends_cancel_errors() { |     async fn drop_receiver_sends_cancel_errors() { | ||||||
|         let _ = pretty_env_logger::try_init(); |         let _ = pretty_env_logger::try_init(); | ||||||
| @@ -341,7 +323,6 @@ mod tests { | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(not(miri))] |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn sender_checks_for_want_on_send() { |     async fn sender_checks_for_want_on_send() { | ||||||
|         let (mut tx, mut rx) = channel::<Custom, ()>(); |         let (mut tx, mut rx) = channel::<Custom, ()>(); | ||||||
| @@ -382,6 +363,7 @@ mod tests { | |||||||
|         use crate::{Body, Request, Response}; |         use crate::{Body, Request, Response}; | ||||||
|  |  | ||||||
|         let rt = tokio::runtime::Builder::new_current_thread() |         let rt = tokio::runtime::Builder::new_current_thread() | ||||||
|  |             .enable_all() | ||||||
|             .build() |             .build() | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|         let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>(); |         let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>(); | ||||||
| @@ -404,6 +386,7 @@ mod tests { | |||||||
|     #[bench] |     #[bench] | ||||||
|     fn giver_queue_not_ready(b: &mut test::Bencher) { |     fn giver_queue_not_ready(b: &mut test::Bencher) { | ||||||
|         let rt = tokio::runtime::Builder::new_current_thread() |         let rt = tokio::runtime::Builder::new_current_thread() | ||||||
|  |             .enable_all() | ||||||
|             .build() |             .build() | ||||||
|             .unwrap(); |             .unwrap(); | ||||||
|         let (_tx, mut rx) = channel::<i32, ()>(); |         let (_tx, mut rx) = channel::<i32, ()>(); | ||||||
|   | |||||||
| @@ -1,18 +1,68 @@ | |||||||
| //! HTTP Client | //! HTTP Client | ||||||
| //! | //! | ||||||
| //! hyper provides HTTP over a single connection. See the [`conn`](conn) module. | //! 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). | ||||||
| //! | //! | ||||||
| //! ## Example | //! ## Example | ||||||
| //! | //! | ||||||
| //! For a small example program simply fetching a URL, take a look at the | //! For a small example program simply fetching a URL, take a look at the | ||||||
| //! [full client example](https://github.com/hyperium/hyper/blob/master/examples/client.rs). | //! [full client example](https://github.com/hyperium/hyper/blob/master/examples/client.rs). | ||||||
|  | //! | ||||||
|  | //! ``` | ||||||
|  | //! # #[cfg(all(feature = "tcp", feature = "client", any(feature = "http1", feature = "http2")))] | ||||||
|  | //! # async fn fetch_httpbin() -> hyper::Result<()> { | ||||||
|  | //! use hyper::{body::HttpBody as _, Client, Uri}; | ||||||
|  | //! | ||||||
|  | //! let client = Client::new(); | ||||||
|  | //! | ||||||
|  | //! // Make a GET /ip to 'http://httpbin.org' | ||||||
|  | //! let res = client.get(Uri::from_static("http://httpbin.org/ip")).await?; | ||||||
|  | //! | ||||||
|  | //! // And then, if the request gets a response... | ||||||
|  | //! println!("status: {}", res.status()); | ||||||
|  | //! | ||||||
|  | //! // Concatenate the body stream into a single buffer... | ||||||
|  | //! let buf = hyper::body::to_bytes(res).await?; | ||||||
|  | //! | ||||||
|  | //! println!("body: {:?}", buf); | ||||||
|  | //! # Ok(()) | ||||||
|  | //! # } | ||||||
|  | //! # fn main () {} | ||||||
|  | //! ``` | ||||||
|  |  | ||||||
|  | #[cfg(feature = "tcp")] | ||||||
|  | pub use self::connect::HttpConnector; | ||||||
|  |  | ||||||
|  | pub mod connect; | ||||||
| #[cfg(all(test, feature = "runtime"))] | #[cfg(all(test, feature = "runtime"))] | ||||||
| mod tests; | mod tests; | ||||||
|  |  | ||||||
| cfg_feature! { | cfg_feature! { | ||||||
|     #![any(feature = "http1", feature = "http2")] |     #![any(feature = "http1", feature = "http2")] | ||||||
|  |  | ||||||
|  |     pub use self::client::{Builder, Client, ResponseFuture}; | ||||||
|  |  | ||||||
|  |     mod client; | ||||||
|     pub mod conn; |     pub mod conn; | ||||||
|     pub(super) mod dispatch; |     pub(super) mod dispatch; | ||||||
|  |     mod pool; | ||||||
|  |     pub mod service; | ||||||
| } | } | ||||||
|   | |||||||
							
								
								
									
										1044
									
								
								src/client/pool.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										1044
									
								
								src/client/pool.rs
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										89
									
								
								src/client/service.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										89
									
								
								src/client/service.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,89 @@ | |||||||
|  | //! 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,3 +1,28 @@ | |||||||
|  | use std::io; | ||||||
|  |  | ||||||
|  | use futures_util::future; | ||||||
|  | use tokio::net::TcpStream; | ||||||
|  |  | ||||||
|  | use super::Client; | ||||||
|  |  | ||||||
|  | #[tokio::test] | ||||||
|  | async fn client_connect_uri_argument() { | ||||||
|  |     let connector = tower::service_fn(|dst: http::Uri| { | ||||||
|  |         assert_eq!(dst.scheme(), Some(&http::uri::Scheme::HTTP)); | ||||||
|  |         assert_eq!(dst.host(), Some("example.local")); | ||||||
|  |         assert_eq!(dst.port(), None); | ||||||
|  |         assert_eq!(dst.path(), "/", "path should be removed"); | ||||||
|  |  | ||||||
|  |         future::err::<TcpStream, _>(io::Error::new(io::ErrorKind::Other, "expect me")) | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     let client = Client::builder().build::<_, crate::Body>(connector); | ||||||
|  |     let _ = client | ||||||
|  |         .get("http://example.local/and/a/path".parse().unwrap()) | ||||||
|  |         .await | ||||||
|  |         .expect_err("response should fail"); | ||||||
|  | } | ||||||
|  |  | ||||||
| /* | /* | ||||||
| // FIXME: re-implement tests with `async/await` | // FIXME: re-implement tests with `async/await` | ||||||
| #[test] | #[test] | ||||||
|   | |||||||
							
								
								
									
										217
									
								
								src/common/drain.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										217
									
								
								src/common/drain.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,217 @@ | |||||||
|  | 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,17 +3,28 @@ use std::future::Future; | |||||||
| use std::pin::Pin; | use std::pin::Pin; | ||||||
| use std::sync::Arc; | use std::sync::Arc; | ||||||
|  |  | ||||||
|  | #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||||
|  | use crate::body::Body; | ||||||
| #[cfg(feature = "server")] | #[cfg(feature = "server")] | ||||||
| use crate::body::HttpBody; | use crate::body::HttpBody; | ||||||
| #[cfg(all(feature = "http2", feature = "server"))] | #[cfg(all(feature = "http2", feature = "server"))] | ||||||
| use crate::proto::h2::server::H2Stream; | use crate::proto::h2::server::H2Stream; | ||||||
| use crate::rt::Executor; | 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")] | #[cfg(feature = "server")] | ||||||
| pub trait ConnStreamExec<F, B: HttpBody>: Clone { | pub trait ConnStreamExec<F, B: HttpBody>: Clone { | ||||||
|     fn execute_h2stream(&mut self, fut: H2Stream<F, B>); |     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>>; | pub(crate) type BoxSendFuture = Pin<Box<dyn Future<Output = ()> + Send>>; | ||||||
|  |  | ||||||
| // Either the user provides an executor for background tasks, or we use | // Either the user provides an executor for background tasks, or we use | ||||||
| @@ -33,13 +44,13 @@ impl Exec { | |||||||
|     { |     { | ||||||
|         match *self { |         match *self { | ||||||
|             Exec::Default => { |             Exec::Default => { | ||||||
|                 #[cfg(feature = "runtime")] |                 #[cfg(feature = "tcp")] | ||||||
|                 { |                 { | ||||||
|                     tokio::task::spawn(fut); |                     tokio::task::spawn(fut); | ||||||
|                 } |                 } | ||||||
|  |                 #[cfg(not(feature = "tcp"))] | ||||||
|                 #[cfg(not(feature = "runtime"))] |  | ||||||
|                 { |                 { | ||||||
|  |                     // If no runtime, we need an executor! | ||||||
|                     panic!("executor must be set") |                     panic!("executor must be set") | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
| @@ -67,6 +78,18 @@ 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 ===== | // ==== impl Executor ===== | ||||||
|  |  | ||||||
| #[cfg(feature = "server")] | #[cfg(feature = "server")] | ||||||
| @@ -81,6 +104,19 @@ 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 | // 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? | // 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... |                 // TODO: There should be a way to do following two lines cleaner... | ||||||
|                 buf.put_slice(&prefix[..copy_len]); |                 buf.put_slice(&prefix[..copy_len]); | ||||||
|                 prefix.advance(copy_len); |                 prefix.advance(copy_len); | ||||||
|                 // Put back what's left |                 // Put back whats left | ||||||
|                 if !prefix.is_empty() { |                 if !prefix.is_empty() { | ||||||
|                     self.pre = Some(prefix); |                     self.pre = Some(prefix); | ||||||
|                 } |                 } | ||||||
|   | |||||||
							
								
								
									
										76
									
								
								src/common/lazy.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										76
									
								
								src/common/lazy.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,76 @@ | |||||||
|  | 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,13 +10,24 @@ macro_rules! ready { | |||||||
| pub(crate) mod buf; | pub(crate) mod buf; | ||||||
| #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | #[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))] | ||||||
| pub(crate) mod date; | 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"))] | #[cfg(any(feature = "http1", feature = "http2", feature = "server"))] | ||||||
| pub(crate) mod exec; | pub(crate) mod exec; | ||||||
| pub(crate) mod io; | pub(crate) mod io; | ||||||
|  | #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] | ||||||
|  | mod lazy; | ||||||
| mod never; | 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 task; | ||||||
| pub(crate) mod watch; | 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"))] | #[cfg(any(feature = "http1", feature = "http2", feature = "runtime"))] | ||||||
| pub(crate) use self::never::Never; | pub(crate) use self::never::Never; | ||||||
| pub(crate) use self::task::Poll; | pub(crate) use self::task::Poll; | ||||||
|   | |||||||
							
								
								
									
										110
									
								
								src/common/sync_wrapper.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										110
									
								
								src/common/sync_wrapper.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,110 @@ | |||||||
|  | /* | ||||||
|  |  * 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,14 +34,21 @@ pub(super) enum Kind { | |||||||
|     /// An `io::Error` that occurred while trying to read or write to a network stream. |     /// An `io::Error` that occurred while trying to read or write to a network stream. | ||||||
|     #[cfg(any(feature = "http1", feature = "http2"))] |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|     Io, |     Io, | ||||||
|  |     /// Error occurred while connecting. | ||||||
|  |     #[allow(unused)] | ||||||
|  |     Connect, | ||||||
|     /// Error creating a TcpListener. |     /// Error creating a TcpListener. | ||||||
|     #[cfg(all(feature = "tcp", feature = "server"))] |     #[cfg(all(feature = "tcp", feature = "server"))] | ||||||
|     Listen, |     Listen, | ||||||
|  |     /// Error accepting on an Incoming stream. | ||||||
|  |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|  |     #[cfg(feature = "server")] | ||||||
|  |     Accept, | ||||||
|     /// User took too long to send headers |     /// User took too long to send headers | ||||||
|     #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] |     #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] | ||||||
|     HeaderTimeout, |     HeaderTimeout, | ||||||
|     /// Error while reading a body from connection. |     /// Error while reading a body from connection. | ||||||
|     #[cfg(any(feature = "http1", feature = "http2"))] |     #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] | ||||||
|     Body, |     Body, | ||||||
|     /// Error while writing a body to connection. |     /// Error while writing a body to connection. | ||||||
|     #[cfg(any(feature = "http1", feature = "http2"))] |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
| @@ -89,6 +96,10 @@ pub(super) enum User { | |||||||
|     Body, |     Body, | ||||||
|     /// The user aborted writing of the outgoing body. |     /// The user aborted writing of the outgoing body. | ||||||
|     BodyWriteAborted, |     BodyWriteAborted, | ||||||
|  |     /// Error calling user's MakeService. | ||||||
|  |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|  |     #[cfg(feature = "server")] | ||||||
|  |     MakeService, | ||||||
|     /// Error from future of user's Service. |     /// Error from future of user's Service. | ||||||
|     #[cfg(any(feature = "http1", feature = "http2"))] |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|     Service, |     Service, | ||||||
| @@ -98,10 +109,22 @@ pub(super) enum User { | |||||||
|     #[cfg(any(feature = "http1", feature = "http2"))] |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|     #[cfg(feature = "server")] |     #[cfg(feature = "server")] | ||||||
|     UnexpectedHeader, |     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. |     /// User tried to respond with a 1xx (not 101) response code. | ||||||
|     #[cfg(feature = "http1")] |     #[cfg(feature = "http1")] | ||||||
|     #[cfg(feature = "server")] |     #[cfg(feature = "server")] | ||||||
|     UnsupportedStatusCode, |     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. |     /// User tried polling for an upgrade that doesn't exist. | ||||||
|     NoUpgrade, |     NoUpgrade, | ||||||
| @@ -158,6 +181,11 @@ impl Error { | |||||||
|         matches!(self.inner.kind, Kind::ChannelClosed) |         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. |     /// Returns true if the connection closed before a message could complete. | ||||||
|     pub fn is_incomplete_message(&self) -> bool { |     pub fn is_incomplete_message(&self) -> bool { | ||||||
|         matches!(self.inner.kind, Kind::IncompleteMessage) |         matches!(self.inner.kind, Kind::IncompleteMessage) | ||||||
| @@ -250,11 +278,23 @@ impl Error { | |||||||
|         Error::new(Kind::Listen).with(cause) |         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 { |     pub(super) fn new_closed() -> Error { | ||||||
|         Error::new(Kind::ChannelClosed) |         Error::new(Kind::ChannelClosed) | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     #[cfg(any(feature = "http1", feature = "http2"))] |     #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] | ||||||
|     pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error { |     pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error { | ||||||
|         Error::new(Kind::Body).with(cause) |         Error::new(Kind::Body).with(cause) | ||||||
|     } |     } | ||||||
| @@ -283,12 +323,30 @@ impl Error { | |||||||
|         Error::new(Kind::HeaderTimeout) |         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 = "http1")] | ||||||
|     #[cfg(feature = "server")] |     #[cfg(feature = "server")] | ||||||
|     pub(super) fn new_user_unsupported_status_code() -> Error { |     pub(super) fn new_user_unsupported_status_code() -> Error { | ||||||
|         Error::new_user(User::UnsupportedStatusCode) |         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 { |     pub(super) fn new_user_no_upgrade() -> Error { | ||||||
|         Error::new_user(User::NoUpgrade) |         Error::new_user(User::NoUpgrade) | ||||||
|     } |     } | ||||||
| @@ -298,6 +356,12 @@ impl Error { | |||||||
|         Error::new_user(User::ManualUpgrade) |         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"))] |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|     pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error { |     pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error { | ||||||
|         Error::new_user(User::Service).with(cause) |         Error::new_user(User::Service).with(cause) | ||||||
| @@ -367,12 +431,16 @@ impl Error { | |||||||
|             #[cfg(feature = "http1")] |             #[cfg(feature = "http1")] | ||||||
|             Kind::UnexpectedMessage => "received unexpected message from connection", |             Kind::UnexpectedMessage => "received unexpected message from connection", | ||||||
|             Kind::ChannelClosed => "channel closed", |             Kind::ChannelClosed => "channel closed", | ||||||
|  |             Kind::Connect => "error trying to connect", | ||||||
|             Kind::Canceled => "operation was canceled", |             Kind::Canceled => "operation was canceled", | ||||||
|             #[cfg(all(feature = "server", feature = "tcp"))] |             #[cfg(all(feature = "server", feature = "tcp"))] | ||||||
|             Kind::Listen => "error creating server listener", |             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"))] |             #[cfg(all(feature = "http1", feature = "server", feature = "runtime"))] | ||||||
|             Kind::HeaderTimeout => "read header from client timeout", |             Kind::HeaderTimeout => "read header from client timeout", | ||||||
|             #[cfg(any(feature = "http1", feature = "http2"))] |             #[cfg(any(feature = "http1", feature = "http2", feature = "stream"))] | ||||||
|             Kind::Body => "error reading a body from connection", |             Kind::Body => "error reading a body from connection", | ||||||
|             #[cfg(any(feature = "http1", feature = "http2"))] |             #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|             Kind::BodyWrite => "error writing a body to connection", |             Kind::BodyWrite => "error writing a body to connection", | ||||||
| @@ -387,15 +455,27 @@ impl Error { | |||||||
|             Kind::User(User::Body) => "error from user's HttpBody stream", |             Kind::User(User::Body) => "error from user's HttpBody stream", | ||||||
|             Kind::User(User::BodyWriteAborted) => "user body write aborted", |             Kind::User(User::BodyWriteAborted) => "user body write aborted", | ||||||
|             #[cfg(any(feature = "http1", feature = "http2"))] |             #[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", |             Kind::User(User::Service) => "error from user's Service", | ||||||
|             #[cfg(any(feature = "http1", feature = "http2"))] |             #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|             #[cfg(feature = "server")] |             #[cfg(feature = "server")] | ||||||
|             Kind::User(User::UnexpectedHeader) => "user sent unexpected header", |             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 = "http1")] | ||||||
|             #[cfg(feature = "server")] |             #[cfg(feature = "server")] | ||||||
|             Kind::User(User::UnsupportedStatusCode) => { |             Kind::User(User::UnsupportedStatusCode) => { | ||||||
|                 "response has 1xx status code, not supported by server" |                 "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", |             Kind::User(User::NoUpgrade) => "no upgrade available", | ||||||
|             #[cfg(feature = "http1")] |             #[cfg(feature = "http1")] | ||||||
|             Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use", |             Kind::User(User::ManualUpgrade) => "upgrade expected but low level API in use", | ||||||
|   | |||||||
							
								
								
									
										107
									
								
								src/ext.rs
									
									
									
									
									
								
							
							
						
						
									
										107
									
								
								src/ext.rs
									
									
									
									
									
								
							| @@ -1,21 +1,12 @@ | |||||||
| //! HTTP extensions. | //! HTTP extensions. | ||||||
|  |  | ||||||
| use bytes::Bytes; | use bytes::Bytes; | ||||||
| #[cfg(any(feature = "http1", feature = "ffi"))] |  | ||||||
| use http::header::HeaderName; |  | ||||||
| #[cfg(feature = "http1")] | #[cfg(feature = "http1")] | ||||||
| use http::header::{IntoHeaderName, ValueIter}; | use http::header::{HeaderName, IntoHeaderName, ValueIter}; | ||||||
| use http::HeaderMap; | use http::HeaderMap; | ||||||
| #[cfg(feature = "ffi")] |  | ||||||
| use std::collections::HashMap; |  | ||||||
| #[cfg(feature = "http2")] | #[cfg(feature = "http2")] | ||||||
| use std::fmt; | 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")] | #[cfg(feature = "http2")] | ||||||
| /// Represents the `:protocol` pseudo-header used by | /// Represents the `:protocol` pseudo-header used by | ||||||
| /// the [Extended CONNECT Protocol]. | /// the [Extended CONNECT Protocol]. | ||||||
| @@ -129,99 +120,3 @@ impl HeaderCaseMap { | |||||||
|         self.0.append(name, orig); |         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() |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -1,221 +0,0 @@ | |||||||
| 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,7 +93,8 @@ unsafe impl AsTaskType for hyper_clientconn { | |||||||
| ffi_fn! { | ffi_fn! { | ||||||
|     /// Creates a new set of HTTP clientconn options to be used in a handshake. |     /// Creates a new set of HTTP clientconn options to be used in a handshake. | ||||||
|     fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options { |     fn hyper_clientconn_options_new() -> *mut hyper_clientconn_options { | ||||||
|         let builder = conn::Builder::new(); |         let mut builder = conn::Builder::new(); | ||||||
|  |         builder.http1_preserve_header_case(true); | ||||||
|  |  | ||||||
|         Box::into_raw(Box::new(hyper_clientconn_options { |         Box::into_raw(Box::new(hyper_clientconn_options { | ||||||
|             builder, |             builder, | ||||||
| @@ -102,26 +103,6 @@ ffi_fn! { | |||||||
|     } ?= std::ptr::null_mut() |     } ?= 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! { | ffi_fn! { | ||||||
|     /// Free a `hyper_clientconn_options *`. |     /// Free a `hyper_clientconn_options *`. | ||||||
|     fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) { |     fn hyper_clientconn_options_free(opts: *mut hyper_clientconn_options) { | ||||||
| @@ -179,16 +160,3 @@ ffi_fn! { | |||||||
|         hyper_code::HYPERE_OK |         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::error::hyper_code; | ||||||
| use super::task::{hyper_task_return_type, AsTaskType}; | use super::task::{hyper_task_return_type, AsTaskType}; | ||||||
| use super::{UserDataPointer, HYPER_ITER_CONTINUE}; | use super::{UserDataPointer, HYPER_ITER_CONTINUE}; | ||||||
| use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase}; | use crate::ext::HeaderCaseMap; | ||||||
| use crate::header::{HeaderName, HeaderValue}; | use crate::header::{HeaderName, HeaderValue}; | ||||||
| use crate::{Body, HeaderMap, Method, Request, Response, Uri}; | use crate::{Body, HeaderMap, Method, Request, Response, Uri}; | ||||||
|  |  | ||||||
| @@ -22,9 +22,11 @@ pub struct hyper_response(pub(super) Response<Body>); | |||||||
| pub struct hyper_headers { | pub struct hyper_headers { | ||||||
|     pub(super) headers: HeaderMap, |     pub(super) headers: HeaderMap, | ||||||
|     orig_casing: HeaderCaseMap, |     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 RawHeaders(pub(crate) hyper_buf); | ||||||
|  |  | ||||||
| pub(crate) struct OnInformational { | pub(crate) struct OnInformational { | ||||||
| @@ -231,7 +233,6 @@ impl hyper_request { | |||||||
|         if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() { |         if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() { | ||||||
|             *self.0.headers_mut() = headers.headers; |             *self.0.headers_mut() = headers.headers; | ||||||
|             self.0.extensions_mut().insert(headers.orig_casing); |             self.0.extensions_mut().insert(headers.orig_casing); | ||||||
|             self.0.extensions_mut().insert(headers.orig_order); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -347,14 +348,9 @@ impl hyper_response { | |||||||
|             .extensions_mut() |             .extensions_mut() | ||||||
|             .remove::<HeaderCaseMap>() |             .remove::<HeaderCaseMap>() | ||||||
|             .unwrap_or_else(HeaderCaseMap::default); |             .unwrap_or_else(HeaderCaseMap::default); | ||||||
|         let orig_order = resp |  | ||||||
|             .extensions_mut() |  | ||||||
|             .remove::<OriginalHeaderOrder>() |  | ||||||
|             .unwrap_or_else(OriginalHeaderOrder::default); |  | ||||||
|         resp.extensions_mut().insert(hyper_headers { |         resp.extensions_mut().insert(hyper_headers { | ||||||
|             headers, |             headers, | ||||||
|             orig_casing, |             orig_casing, | ||||||
|             orig_order, |  | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         hyper_response(resp) |         hyper_response(resp) | ||||||
| @@ -362,7 +358,7 @@ impl hyper_response { | |||||||
|  |  | ||||||
|     fn reason_phrase(&self) -> &[u8] { |     fn reason_phrase(&self) -> &[u8] { | ||||||
|         if let Some(reason) = self.0.extensions().get::<ReasonPhrase>() { |         if let Some(reason) = self.0.extensions().get::<ReasonPhrase>() { | ||||||
|             return reason.as_bytes(); |             return &reason.0; | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if let Some(reason) = self.0.status().canonical_reason() { |         if let Some(reason) = self.0.status().canonical_reason() { | ||||||
| @@ -408,54 +404,26 @@ ffi_fn! { | |||||||
|         // and for each one, try to pair the originally cased name with the value. |         // and for each one, try to pair the originally cased name with the value. | ||||||
|         // |         // | ||||||
|         // TODO: consider adding http::HeaderMap::entries() iterator |         // TODO: consider adding http::HeaderMap::entries() iterator | ||||||
|         let mut ordered_iter =  headers.orig_order.get_in_order().peekable(); |         for name in headers.headers.keys() { | ||||||
|         if ordered_iter.peek().is_some() { |             let mut names = headers.orig_casing.get_all(name); | ||||||
|             for (name, idx) in ordered_iter { |  | ||||||
|                 let (name_ptr, name_len) = if let Some(orig_name) = headers.orig_casing.get_all(name).nth(*idx) { |             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()) |                     (orig_name.as_ref().as_ptr(), orig_name.as_ref().len()) | ||||||
|                 } else { |                 } else { | ||||||
|                     ( |                     ( | ||||||
|                     name.as_str().as_bytes().as_ptr(), |                         name.as_str().as_bytes().as_ptr(), | ||||||
|                     name.as_str().as_bytes().len(), |                         name.as_str().as_bytes().len(), | ||||||
|                     ) |                     ) | ||||||
|                 }; |                 }; | ||||||
|  |  | ||||||
|                 let val_ptr; |                 let val_ptr = value.as_bytes().as_ptr(); | ||||||
|                 let val_len; |                 let val_len = value.as_bytes().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) { |                 if HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) { | ||||||
|                     return; |                     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; |  | ||||||
|                     } |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -469,8 +437,7 @@ ffi_fn! { | |||||||
|         match unsafe { raw_name_value(name, name_len, value, value_len) } { |         match unsafe { raw_name_value(name, name_len, value, value_len) } { | ||||||
|             Ok((name, value, orig_name)) => { |             Ok((name, value, orig_name)) => { | ||||||
|                 headers.headers.insert(&name, value); |                 headers.headers.insert(&name, value); | ||||||
|                 headers.orig_casing.insert(name.clone(), orig_name.clone()); |                 headers.orig_casing.insert(name, orig_name); | ||||||
|                 headers.orig_order.insert(name); |  | ||||||
|                 hyper_code::HYPERE_OK |                 hyper_code::HYPERE_OK | ||||||
|             } |             } | ||||||
|             Err(code) => code, |             Err(code) => code, | ||||||
| @@ -489,8 +456,7 @@ ffi_fn! { | |||||||
|         match unsafe { raw_name_value(name, name_len, value, value_len) } { |         match unsafe { raw_name_value(name, name_len, value, value_len) } { | ||||||
|             Ok((name, value, orig_name)) => { |             Ok((name, value, orig_name)) => { | ||||||
|                 headers.headers.append(&name, value); |                 headers.headers.append(&name, value); | ||||||
|                 headers.orig_casing.append(&name, orig_name.clone()); |                 headers.orig_casing.append(name, orig_name); | ||||||
|                 headers.orig_order.append(name); |  | ||||||
|                 hyper_code::HYPERE_OK |                 hyper_code::HYPERE_OK | ||||||
|             } |             } | ||||||
|             Err(code) => code, |             Err(code) => code, | ||||||
| @@ -503,7 +469,6 @@ impl Default for hyper_headers { | |||||||
|         Self { |         Self { | ||||||
|             headers: Default::default(), |             headers: Default::default(), | ||||||
|             orig_casing: HeaderCaseMap::default(), |             orig_casing: HeaderCaseMap::default(), | ||||||
|             orig_order: OriginalHeaderOrder::default(), |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -590,68 +555,4 @@ mod tests { | |||||||
|             HYPER_ITER_CONTINUE |             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. |     /// The executor of all task futures. | ||||||
|     /// |     /// | ||||||
|     /// There should never be contention on the mutex, as it is only locked |     /// There should never be contention on the mutex, as it is only locked | ||||||
|     /// to drive the futures. However, we cannot guarantee proper usage from |     /// to drive the futures. However, we cannot gaurantee proper usage from | ||||||
|     /// `hyper_executor_poll()`, which in C could potentially be called inside |     /// `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 |     /// 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. |     /// would result in a deadlock, but that's better than data corruption. | ||||||
|   | |||||||
| @@ -2,7 +2,7 @@ | |||||||
| #![deny(missing_debug_implementations)] | #![deny(missing_debug_implementations)] | ||||||
| #![cfg_attr(test, deny(rust_2018_idioms))] | #![cfg_attr(test, deny(rust_2018_idioms))] | ||||||
| #![cfg_attr(all(test, feature = "full"), deny(unreachable_pub))] | #![cfg_attr(all(test, feature = "full"), deny(unreachable_pub))] | ||||||
| #![cfg_attr(all(test, feature = "full"), deny(warnings))] | #![cfg_attr(test, deny(warnings))] | ||||||
| #![cfg_attr(all(test, feature = "nightly"), feature(test))] | #![cfg_attr(all(test, feature = "nightly"), feature(test))] | ||||||
| #![cfg_attr(docsrs, feature(doc_cfg))] | #![cfg_attr(docsrs, feature(doc_cfg))] | ||||||
|  |  | ||||||
| @@ -51,6 +51,8 @@ | |||||||
| //! - `server`: Enables the HTTP `server`. | //! - `server`: Enables the HTTP `server`. | ||||||
| //! - `runtime`: Enables convenient integration with `tokio`, providing | //! - `runtime`: Enables convenient integration with `tokio`, providing | ||||||
| //!   connectors and acceptors for TCP, and a default executor. | //!   connectors and acceptors for TCP, and a default executor. | ||||||
|  | //! - `tcp`: Enables convenient implementations over TCP (using tokio). | ||||||
|  | //! - `stream`: Provides `futures::Stream` capabilities. | ||||||
| //! | //! | ||||||
| //! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section | //! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section | ||||||
|  |  | ||||||
| @@ -93,10 +95,15 @@ cfg_feature! { | |||||||
|     #![feature = "client"] |     #![feature = "client"] | ||||||
|  |  | ||||||
|     pub mod client; |     pub mod client; | ||||||
|  |     #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
|  |     #[doc(no_inline)] | ||||||
|  |     pub use crate::client::Client; | ||||||
| } | } | ||||||
|  |  | ||||||
| cfg_feature! { | cfg_feature! { | ||||||
|     #![feature = "server"] |     #![feature = "server"] | ||||||
|  |  | ||||||
|     pub mod server; |     pub mod server; | ||||||
|  |     #[doc(no_inline)] | ||||||
|  |     pub use crate::server::Server; | ||||||
| } | } | ||||||
|   | |||||||
| @@ -58,8 +58,6 @@ where | |||||||
|                 #[cfg(all(feature = "server", feature = "runtime"))] |                 #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|                 h1_header_read_timeout_running: false, |                 h1_header_read_timeout_running: false, | ||||||
|                 preserve_header_case: false, |                 preserve_header_case: false, | ||||||
|                 #[cfg(feature = "ffi")] |  | ||||||
|                 preserve_header_order: false, |  | ||||||
|                 title_case_headers: false, |                 title_case_headers: false, | ||||||
|                 h09_responses: false, |                 h09_responses: false, | ||||||
|                 #[cfg(feature = "ffi")] |                 #[cfg(feature = "ffi")] | ||||||
| @@ -113,11 +111,6 @@ where | |||||||
|         self.state.preserve_header_case = true; |         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")] |     #[cfg(feature = "client")] | ||||||
|     pub(crate) fn set_h09_responses(&mut self) { |     pub(crate) fn set_h09_responses(&mut self) { | ||||||
|         self.state.h09_responses = true; |         self.state.h09_responses = true; | ||||||
| @@ -155,15 +148,19 @@ where | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn can_read_head(&self) -> bool { |     pub(crate) fn can_read_head(&self) -> bool { | ||||||
|         if !matches!(self.state.reading, Reading::Init) { |         match self.state.reading { | ||||||
|             return false; |             Reading::Init => { | ||||||
|  |                 if T::should_read_first() { | ||||||
|  |                     true | ||||||
|  |                 } else { | ||||||
|  |                     match self.state.writing { | ||||||
|  |                         Writing::Init => false, | ||||||
|  |                         _ => true, | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|  |             _ => false, | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         if T::should_read_first() { |  | ||||||
|             return true; |  | ||||||
|         } |  | ||||||
|  |  | ||||||
|         !matches!(self.state.writing, Writing::Init) |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn can_read_body(&self) -> bool { |     pub(crate) fn can_read_body(&self) -> bool { | ||||||
| @@ -203,8 +200,6 @@ where | |||||||
|                 #[cfg(all(feature = "server", feature = "runtime"))] |                 #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|                 h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running, |                 h1_header_read_timeout_running: &mut self.state.h1_header_read_timeout_running, | ||||||
|                 preserve_header_case: self.state.preserve_header_case, |                 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, |                 h09_responses: self.state.h09_responses, | ||||||
|                 #[cfg(feature = "ffi")] |                 #[cfg(feature = "ffi")] | ||||||
|                 on_informational: &mut self.state.on_informational, |                 on_informational: &mut self.state.on_informational, | ||||||
| @@ -363,10 +358,10 @@ where | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn is_mid_message(&self) -> bool { |     fn is_mid_message(&self) -> bool { | ||||||
|         !matches!( |         match (&self.state.reading, &self.state.writing) { | ||||||
|             (&self.state.reading, &self.state.writing), |             (&Reading::Init, &Writing::Init) => false, | ||||||
|             (&Reading::Init, &Writing::Init) |             _ => true, | ||||||
|         ) |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // This will check to make sure the io object read is empty. |     // This will check to make sure the io object read is empty. | ||||||
| @@ -489,10 +484,11 @@ where | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     pub(crate) fn can_write_head(&self) -> bool { |     pub(crate) fn can_write_head(&self) -> bool { | ||||||
|         if !T::should_read_first() && matches!(self.state.reading, Reading::Closed) { |         if !T::should_read_first() { | ||||||
|             return false; |             if let Reading::Closed = self.state.reading { | ||||||
|  |                 return false; | ||||||
|  |             } | ||||||
|         } |         } | ||||||
|  |  | ||||||
|         match self.state.writing { |         match self.state.writing { | ||||||
|             Writing::Init => self.io.can_headers_buf(), |             Writing::Init => self.io.can_headers_buf(), | ||||||
|             _ => false, |             _ => false, | ||||||
| @@ -586,7 +582,7 @@ where | |||||||
|         } |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Fix keep-alive when Connection: keep-alive header is not present |     // Fix keep-alives when Connection: keep-alive header is not present | ||||||
|     fn fix_keep_alive(&mut self, head: &mut MessageHead<T::Outgoing>) { |     fn fix_keep_alive(&mut self, head: &mut MessageHead<T::Outgoing>) { | ||||||
|         let outgoing_is_keep_alive = head |         let outgoing_is_keep_alive = head | ||||||
|             .headers |             .headers | ||||||
| @@ -636,14 +632,14 @@ where | |||||||
|             Writing::Body(ref mut encoder) => { |             Writing::Body(ref mut encoder) => { | ||||||
|                 self.io.buffer(encoder.encode(chunk)); |                 self.io.buffer(encoder.encode(chunk)); | ||||||
|  |  | ||||||
|                 if !encoder.is_eof() { |                 if encoder.is_eof() { | ||||||
|                     return; |                     if encoder.is_last() { | ||||||
|                 } |                         Writing::Closed | ||||||
|  |                     } else { | ||||||
|                 if encoder.is_last() { |                         Writing::KeepAlive | ||||||
|                     Writing::Closed |                     } | ||||||
|                 } else { |                 } else { | ||||||
|                     Writing::KeepAlive |                     return; | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|             _ => unreachable!("write_body invalid state: {:?}", self.state.writing), |             _ => unreachable!("write_body invalid state: {:?}", self.state.writing), | ||||||
| @@ -675,31 +671,32 @@ where | |||||||
|     pub(crate) fn end_body(&mut self) -> crate::Result<()> { |     pub(crate) fn end_body(&mut self) -> crate::Result<()> { | ||||||
|         debug_assert!(self.can_write_body()); |         debug_assert!(self.can_write_body()); | ||||||
|  |  | ||||||
|         let encoder = match self.state.writing { |         let mut res = Ok(()); | ||||||
|             Writing::Body(ref mut enc) => enc, |         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 | ||||||
|  |                     } | ||||||
|  |                 } | ||||||
|  |             } | ||||||
|             _ => return Ok(()), |             _ => return Ok(()), | ||||||
|         }; |         }; | ||||||
|  |  | ||||||
|         // end of stream, that means we should try to eof |         self.state.writing = state; | ||||||
|         match encoder.end() { |         res | ||||||
|             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 |     // When we get a parse error, depending on what side we are, we might be able | ||||||
| @@ -752,7 +749,10 @@ where | |||||||
|  |  | ||||||
|         // If still in Reading::Body, just give up |         // If still in Reading::Body, just give up | ||||||
|         match self.state.reading { |         match self.state.reading { | ||||||
|             Reading::Init | Reading::KeepAlive => trace!("body drained"), |             Reading::Init | Reading::KeepAlive => { | ||||||
|  |                 trace!("body drained"); | ||||||
|  |                 return; | ||||||
|  |             } | ||||||
|             _ => self.close_read(), |             _ => self.close_read(), | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| @@ -824,8 +824,6 @@ struct State { | |||||||
|     #[cfg(all(feature = "server", feature = "runtime"))] |     #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|     h1_header_read_timeout_running: bool, |     h1_header_read_timeout_running: bool, | ||||||
|     preserve_header_case: bool, |     preserve_header_case: bool, | ||||||
|     #[cfg(feature = "ffi")] |  | ||||||
|     preserve_header_order: bool, |  | ||||||
|     title_case_headers: bool, |     title_case_headers: bool, | ||||||
|     h09_responses: bool, |     h09_responses: bool, | ||||||
|     /// If set, called with each 1xx informational response received for |     /// If set, called with each 1xx informational response received for | ||||||
| @@ -1003,35 +1001,43 @@ impl State { | |||||||
|  |  | ||||||
|         self.method = None; |         self.method = None; | ||||||
|         self.keep_alive.idle(); |         self.keep_alive.idle(); | ||||||
|  |         if self.is_idle() { | ||||||
|  |             self.reading = Reading::Init; | ||||||
|  |             self.writing = Writing::Init; | ||||||
|  |  | ||||||
|         if !self.is_idle() { |             // !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 { | ||||||
|             self.close(); |             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 { |     fn is_idle(&self) -> bool { | ||||||
|         matches!(self.keep_alive.status(), KA::Idle) |         if let KA::Idle = self.keep_alive.status() { | ||||||
|  |             true | ||||||
|  |         } else { | ||||||
|  |             false | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn is_read_closed(&self) -> bool { |     fn is_read_closed(&self) -> bool { | ||||||
|         matches!(self.reading, Reading::Closed) |         match self.reading { | ||||||
|  |             Reading::Closed => true, | ||||||
|  |             _ => false, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn is_write_closed(&self) -> bool { |     fn is_write_closed(&self) -> bool { | ||||||
|         matches!(self.writing, Writing::Closed) |         match self.writing { | ||||||
|  |             Writing::Closed => true, | ||||||
|  |             _ => false, | ||||||
|  |         } | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn prepare_upgrade(&mut self) -> crate::upgrade::OnUpgrade { |     fn prepare_upgrade(&mut self) -> crate::upgrade::OnUpgrade { | ||||||
|   | |||||||
| @@ -1,17 +1,17 @@ | |||||||
| use std::cmp; | use std::cmp; | ||||||
| use std::fmt; | use std::fmt; | ||||||
| #[cfg(all(feature = "server", feature = "runtime"))] |  | ||||||
| use std::future::Future; |  | ||||||
| use std::io::{self, IoSlice}; | use std::io::{self, IoSlice}; | ||||||
| use std::marker::Unpin; | use std::marker::Unpin; | ||||||
| use std::mem::MaybeUninit; | use std::mem::MaybeUninit; | ||||||
| #[cfg(all(feature = "server", feature = "runtime"))] | #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|  | use std::future::Future; | ||||||
|  | #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| use bytes::{Buf, BufMut, Bytes, BytesMut}; |  | ||||||
| use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; |  | ||||||
| #[cfg(all(feature = "server", feature = "runtime"))] | #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
| use tokio::time::Instant; | use tokio::time::Instant; | ||||||
|  | use bytes::{Buf, BufMut, Bytes, BytesMut}; | ||||||
|  | use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; | ||||||
| use tracing::{debug, trace}; | use tracing::{debug, trace}; | ||||||
|  |  | ||||||
| use super::{Http1Transaction, ParseContext, ParsedMessage}; | use super::{Http1Transaction, ParseContext, ParsedMessage}; | ||||||
| @@ -194,8 +194,6 @@ where | |||||||
|                     #[cfg(all(feature = "server", feature = "runtime"))] |                     #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|                     h1_header_read_timeout_running: parse_ctx.h1_header_read_timeout_running, |                     h1_header_read_timeout_running: parse_ctx.h1_header_read_timeout_running, | ||||||
|                     preserve_header_case: parse_ctx.preserve_header_case, |                     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, |                     h09_responses: parse_ctx.h09_responses, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: parse_ctx.on_informational, |                     on_informational: parse_ctx.on_informational, | ||||||
| @@ -210,13 +208,9 @@ where | |||||||
|                     { |                     { | ||||||
|                         *parse_ctx.h1_header_read_timeout_running = false; |                         *parse_ctx.h1_header_read_timeout_running = false; | ||||||
|  |  | ||||||
|                         if let Some(h1_header_read_timeout_fut) = |                         if let Some(h1_header_read_timeout_fut) = parse_ctx.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 |                             // Reset the timer in order to avoid woken up when the timeout finishes | ||||||
|                             h1_header_read_timeout_fut |                             h1_header_read_timeout_fut.as_mut().reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60)); | ||||||
|                                 .as_mut() |  | ||||||
|                                 .reset(Instant::now() + Duration::from_secs(30 * 24 * 60 * 60)); |  | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
|                     return Poll::Ready(Ok(msg)); |                     return Poll::Ready(Ok(msg)); | ||||||
| @@ -230,14 +224,12 @@ where | |||||||
|  |  | ||||||
|                     #[cfg(all(feature = "server", feature = "runtime"))] |                     #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|                     if *parse_ctx.h1_header_read_timeout_running { |                     if *parse_ctx.h1_header_read_timeout_running { | ||||||
|                         if let Some(h1_header_read_timeout_fut) = |                         if let Some(h1_header_read_timeout_fut) = parse_ctx.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 Pin::new(h1_header_read_timeout_fut).poll(cx).is_ready() { |  | ||||||
|                                 *parse_ctx.h1_header_read_timeout_running = false; |                                 *parse_ctx.h1_header_read_timeout_running = false; | ||||||
|  |  | ||||||
|                                 tracing::warn!("read header from client timeout"); |                                 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())) | ||||||
|                             } |                             } | ||||||
|                         } |                         } | ||||||
|                     } |                     } | ||||||
| @@ -735,15 +727,10 @@ mod tests { | |||||||
|                 cached_headers: &mut None, |                 cached_headers: &mut None, | ||||||
|                 req_method: &mut None, |                 req_method: &mut None, | ||||||
|                 h1_parser_config: Default::default(), |                 h1_parser_config: Default::default(), | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout: None, |                 h1_header_read_timeout: None, | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout_fut: &mut None, |                 h1_header_read_timeout_fut: &mut None, | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout_running: &mut false, |                 h1_header_read_timeout_running: &mut false, | ||||||
|                 preserve_header_case: false, |                 preserve_header_case: false, | ||||||
|                 #[cfg(feature = "ffi")] |  | ||||||
|                 preserve_header_order: false, |  | ||||||
|                 h09_responses: false, |                 h09_responses: false, | ||||||
|                 #[cfg(feature = "ffi")] |                 #[cfg(feature = "ffi")] | ||||||
|                 on_informational: &mut None, |                 on_informational: &mut None, | ||||||
| @@ -907,7 +894,9 @@ mod tests { | |||||||
|     async fn write_buf_flatten() { |     async fn write_buf_flatten() { | ||||||
|         let _ = pretty_env_logger::try_init(); |         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); |         let mut buffered = Buffered::<_, Cursor<Vec<u8>>>::new(mock); | ||||||
|         buffered.write_buf.set_strategy(WriteStrategy::Flatten); |         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::decode::Decoder; | ||||||
| pub(crate) use self::dispatch::Dispatcher; | pub(crate) use self::dispatch::Dispatcher; | ||||||
| pub(crate) use self::encode::{EncodedBuf, Encoder}; | 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; | pub(crate) use self::io::MINIMUM_MAX_BUFFER_SIZE; | ||||||
|  |  | ||||||
| mod conn; | mod conn; | ||||||
| @@ -24,6 +24,7 @@ mod encode; | |||||||
| mod io; | mod io; | ||||||
| mod role; | mod role; | ||||||
|  |  | ||||||
|  |  | ||||||
| cfg_client! { | cfg_client! { | ||||||
|     pub(crate) type ClientTransaction = role::Client; |     pub(crate) type ClientTransaction = role::Client; | ||||||
| } | } | ||||||
| @@ -83,8 +84,6 @@ pub(crate) struct ParseContext<'a> { | |||||||
|     #[cfg(all(feature = "server", feature = "runtime"))] |     #[cfg(all(feature = "server", feature = "runtime"))] | ||||||
|     h1_header_read_timeout_running: &'a mut bool, |     h1_header_read_timeout_running: &'a mut bool, | ||||||
|     preserve_header_case: bool, |     preserve_header_case: bool, | ||||||
|     #[cfg(feature = "ffi")] |  | ||||||
|     preserve_header_order: bool, |  | ||||||
|     h09_responses: bool, |     h09_responses: bool, | ||||||
|     #[cfg(feature = "ffi")] |     #[cfg(feature = "ffi")] | ||||||
|     on_informational: &'a mut Option<crate::ffi::OnInformational>, |     on_informational: &'a mut Option<crate::ffi::OnInformational>, | ||||||
|   | |||||||
| @@ -1,14 +1,15 @@ | |||||||
| use std::fmt::{self, Write}; | use std::fmt::{self, Write}; | ||||||
| use std::mem::MaybeUninit; | 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::Bytes; | ||||||
| use bytes::BytesMut; | use bytes::BytesMut; | ||||||
| #[cfg(feature = "server")] | #[cfg(feature = "server")] | ||||||
| use http::header::ValueIter; | use http::header::ValueIter; | ||||||
| use http::header::{self, Entry, HeaderName, HeaderValue}; | use http::header::{self, Entry, HeaderName, HeaderValue}; | ||||||
| use http::{HeaderMap, Method, StatusCode, Version}; | 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 tracing::{debug, error, trace, trace_span, warn}; | ||||||
|  |  | ||||||
| use crate::body::DecodedLength; | use crate::body::DecodedLength; | ||||||
| @@ -16,8 +17,6 @@ use crate::body::DecodedLength; | |||||||
| use crate::common::date; | use crate::common::date; | ||||||
| use crate::error::Parse; | use crate::error::Parse; | ||||||
| use crate::ext::HeaderCaseMap; | use crate::ext::HeaderCaseMap; | ||||||
| #[cfg(feature = "ffi")] |  | ||||||
| use crate::ext::OriginalHeaderOrder; |  | ||||||
| use crate::headers; | use crate::headers; | ||||||
| use crate::proto::h1::{ | use crate::proto::h1::{ | ||||||
|     Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage, |     Encode, Encoder, Http1Transaction, ParseContext, ParseResult, ParsedMessage, | ||||||
| @@ -79,7 +78,7 @@ where | |||||||
|     if !*ctx.h1_header_read_timeout_running { |     if !*ctx.h1_header_read_timeout_running { | ||||||
|         if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { |         if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { | ||||||
|             let deadline = Instant::now() + 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 { |             match ctx.h1_header_read_timeout_fut { | ||||||
|                 Some(h1_header_read_timeout_fut) => { |                 Some(h1_header_read_timeout_fut) => { | ||||||
|                     debug!("resetting h1 header read timeout timer"); |                     debug!("resetting h1 header read timeout timer"); | ||||||
| @@ -215,13 +214,6 @@ impl Http1Transaction for Server { | |||||||
|             None |             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); |         let mut headers = ctx.cached_headers.take().unwrap_or_else(HeaderMap::new); | ||||||
|  |  | ||||||
|         headers.reserve(headers_len); |         headers.reserve(headers_len); | ||||||
| @@ -298,11 +290,6 @@ impl Http1Transaction for Server { | |||||||
|                 header_case_map.append(&name, slice.slice(header.name.0..header.name.1)); |                 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); |             headers.append(name, value); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -317,11 +304,6 @@ impl Http1Transaction for Server { | |||||||
|             extensions.insert(header_case_map); |             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()); |         *ctx.req_method = Some(subject.0.clone()); | ||||||
|  |  | ||||||
|         Ok(Some(ParsedMessage { |         Ok(Some(ParsedMessage { | ||||||
| @@ -376,13 +358,7 @@ impl Http1Transaction for Server { | |||||||
|  |  | ||||||
|         let init_cap = 30 + msg.head.headers.len() * AVERAGE_HEADER_SIZE; |         let init_cap = 30 + msg.head.headers.len() * AVERAGE_HEADER_SIZE; | ||||||
|         dst.reserve(init_cap); |         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"); |             extend(dst, b"HTTP/1.1 200 OK\r\n"); | ||||||
|         } else { |         } else { | ||||||
|             match msg.head.version { |             match msg.head.version { | ||||||
| @@ -397,21 +373,15 @@ impl Http1Transaction for Server { | |||||||
|  |  | ||||||
|             extend(dst, msg.head.subject.as_str().as_bytes()); |             extend(dst, msg.head.subject.as_str().as_bytes()); | ||||||
|             extend(dst, b" "); |             extend(dst, b" "); | ||||||
|  |             // a reason MUST be written, as many parsers will expect it. | ||||||
|             if let Some(reason) = custom_reason_phrase { |             extend( | ||||||
|                 extend(dst, reason.as_bytes()); |                 dst, | ||||||
|             } else { |                 msg.head | ||||||
|                 // a reason MUST be written, as many parsers will expect it. |                     .subject | ||||||
|                 extend( |                     .canonical_reason() | ||||||
|                     dst, |                     .unwrap_or("<none>") | ||||||
|                     msg.head |                     .as_bytes(), | ||||||
|                         .subject |             ); | ||||||
|                         .canonical_reason() |  | ||||||
|                         .unwrap_or("<none>") |  | ||||||
|                         .as_bytes(), |  | ||||||
|                 ); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             extend(dst, b"\r\n"); |             extend(dst, b"\r\n"); | ||||||
|         } |         } | ||||||
|  |  | ||||||
| @@ -498,10 +468,6 @@ 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( |     fn encode_headers_with_lower_case( | ||||||
|         msg: Encode<'_, StatusCode>, |         msg: Encode<'_, StatusCode>, | ||||||
|         dst: &mut Vec<u8>, |         dst: &mut Vec<u8>, | ||||||
| @@ -854,10 +820,7 @@ impl Server { | |||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|                 None | Some(BodyLength::Known(0)) => { |                 None | Some(BodyLength::Known(0)) => { | ||||||
|                     if Server::can_have_implicit_zero_content_length( |                     if Server::can_have_content_length(msg.req_method, msg.head.subject) { | ||||||
|                         msg.req_method, |  | ||||||
|                         msg.head.subject, |  | ||||||
|                     ) { |  | ||||||
|                         header_name_writer.write_full_header_line( |                         header_name_writer.write_full_header_line( | ||||||
|                             dst, |                             dst, | ||||||
|                             "content-length: 0\r\n", |                             "content-length: 0\r\n", | ||||||
| @@ -955,9 +918,12 @@ impl Http1Transaction for Client { | |||||||
|                         trace!("Response.parse Complete({})", len); |                         trace!("Response.parse Complete({})", len); | ||||||
|                         let status = StatusCode::from_u16(res.code.unwrap())?; |                         let status = StatusCode::from_u16(res.code.unwrap())?; | ||||||
|  |  | ||||||
|  |                         #[cfg(not(feature = "ffi"))] | ||||||
|  |                         let reason = (); | ||||||
|  |                         #[cfg(feature = "ffi")] | ||||||
|                         let reason = { |                         let reason = { | ||||||
|                             let reason = res.reason.unwrap(); |                             let reason = res.reason.unwrap(); | ||||||
|                             // Only save the reason phrase if it isn't the canonical reason |                             // Only save the reason phrase if it isnt the canonical reason | ||||||
|                             if Some(reason) != status.canonical_reason() { |                             if Some(reason) != status.canonical_reason() { | ||||||
|                                 Some(Bytes::copy_from_slice(reason.as_bytes())) |                                 Some(Bytes::copy_from_slice(reason.as_bytes())) | ||||||
|                             } else { |                             } else { | ||||||
| @@ -978,7 +944,12 @@ impl Http1Transaction for Client { | |||||||
|                     Err(httparse::Error::Version) if ctx.h09_responses => { |                     Err(httparse::Error::Version) if ctx.h09_responses => { | ||||||
|                         trace!("Response.parse accepted HTTP/0.9 response"); |                         trace!("Response.parse accepted HTTP/0.9 response"); | ||||||
|  |  | ||||||
|                         (0, StatusCode::OK, None, Version::HTTP_09, 0) |                         #[cfg(not(feature = "ffi"))] | ||||||
|  |                         let reason = (); | ||||||
|  |                         #[cfg(feature = "ffi")] | ||||||
|  |                         let reason = None; | ||||||
|  |  | ||||||
|  |                         (0, StatusCode::OK, reason, Version::HTTP_09, 0) | ||||||
|                     } |                     } | ||||||
|                     Err(e) => return Err(e.into()), |                     Err(e) => return Err(e.into()), | ||||||
|                 } |                 } | ||||||
| @@ -986,15 +957,15 @@ impl Http1Transaction for Client { | |||||||
|  |  | ||||||
|             let mut slice = buf.split_to(len); |             let mut slice = buf.split_to(len); | ||||||
|  |  | ||||||
|             if ctx |             if ctx.h1_parser_config.obsolete_multiline_headers_in_responses_are_allowed() { | ||||||
|                 .h1_parser_config |                 for header in &headers_indices[..headers_len] { | ||||||
|                 .obsolete_multiline_headers_in_responses_are_allowed() |  | ||||||
|             { |  | ||||||
|                 for header in &mut headers_indices[..headers_len] { |  | ||||||
|                     // SAFETY: array is valid up to `headers_len` |                     // SAFETY: array is valid up to `headers_len` | ||||||
|                     let header = unsafe { &mut *header.as_mut_ptr() }; |                     let header = unsafe { &*header.as_ptr() }; | ||||||
|                     Client::obs_fold_line(&mut slice, header); |                     for b in &mut slice[header.value.0..header.value.1] { | ||||||
|  |                         if *b == b'\r' || *b == b'\n' { | ||||||
|  |                             *b = b' '; | ||||||
|  |                         } | ||||||
|  |                     } | ||||||
|                 } |                 } | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -1010,13 +981,6 @@ impl Http1Transaction for Client { | |||||||
|                 None |                 None | ||||||
|             }; |             }; | ||||||
|  |  | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             let mut header_order = if ctx.preserve_header_order { |  | ||||||
|                 Some(OriginalHeaderOrder::default()) |  | ||||||
|             } else { |  | ||||||
|                 None |  | ||||||
|             }; |  | ||||||
|  |  | ||||||
|             headers.reserve(headers_len); |             headers.reserve(headers_len); | ||||||
|             for header in &headers_indices[..headers_len] { |             for header in &headers_indices[..headers_len] { | ||||||
|                 // SAFETY: array is valid up to `headers_len` |                 // SAFETY: array is valid up to `headers_len` | ||||||
| @@ -1039,11 +1003,6 @@ impl Http1Transaction for Client { | |||||||
|                     header_case_map.append(&name, slice.slice(header.name.0..header.name.1)); |                     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); |                 headers.append(name, value); | ||||||
|             } |             } | ||||||
|  |  | ||||||
| @@ -1054,16 +1013,11 @@ impl Http1Transaction for Client { | |||||||
|             } |             } | ||||||
|  |  | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             if let Some(header_order) = header_order { |  | ||||||
|                 extensions.insert(header_order); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             if let Some(reason) = reason { |             if let Some(reason) = reason { | ||||||
|                 // Safety: httparse ensures that only valid reason phrase bytes are present in this |                 extensions.insert(crate::ffi::ReasonPhrase(reason)); | ||||||
|                 // field. |  | ||||||
|                 let reason = unsafe { crate::ext::ReasonPhrase::from_bytes_unchecked(reason) }; |  | ||||||
|                 extensions.insert(reason); |  | ||||||
|             } |             } | ||||||
|  |             #[cfg(not(feature = "ffi"))] | ||||||
|  |             drop(reason); | ||||||
|  |  | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             if ctx.raw_headers { |             if ctx.raw_headers { | ||||||
| @@ -1341,65 +1295,6 @@ impl Client { | |||||||
|  |  | ||||||
|         set_content_length(headers, len) |         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 { | fn set_content_length(headers: &mut HeaderMap, len: u64) -> Encoder { | ||||||
| @@ -1579,15 +1474,10 @@ mod tests { | |||||||
|                 cached_headers: &mut None, |                 cached_headers: &mut None, | ||||||
|                 req_method: &mut method, |                 req_method: &mut method, | ||||||
|                 h1_parser_config: Default::default(), |                 h1_parser_config: Default::default(), | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout: None, |                 h1_header_read_timeout: None, | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout_fut: &mut None, |                 h1_header_read_timeout_fut: &mut None, | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout_running: &mut false, |                 h1_header_read_timeout_running: &mut false, | ||||||
|                 preserve_header_case: false, |                 preserve_header_case: false, | ||||||
|                 #[cfg(feature = "ffi")] |  | ||||||
|                 preserve_header_order: false, |  | ||||||
|                 h09_responses: false, |                 h09_responses: false, | ||||||
|                 #[cfg(feature = "ffi")] |                 #[cfg(feature = "ffi")] | ||||||
|                 on_informational: &mut None, |                 on_informational: &mut None, | ||||||
| @@ -1614,15 +1504,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut Some(crate::Method::GET), |             req_method: &mut Some(crate::Method::GET), | ||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: false, |             preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: false, |             h09_responses: false, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1644,15 +1529,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut None, |             req_method: &mut None, | ||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: false, |             preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: false, |             h09_responses: false, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1672,15 +1552,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut Some(crate::Method::GET), |             req_method: &mut Some(crate::Method::GET), | ||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: false, |             preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: true, |             h09_responses: true, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1702,15 +1577,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut Some(crate::Method::GET), |             req_method: &mut Some(crate::Method::GET), | ||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: false, |             preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: false, |             h09_responses: false, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1736,15 +1606,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut Some(crate::Method::GET), |             req_method: &mut Some(crate::Method::GET), | ||||||
|             h1_parser_config, |             h1_parser_config, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: false, |             preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: false, |             h09_responses: false, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1767,15 +1632,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut Some(crate::Method::GET), |             req_method: &mut Some(crate::Method::GET), | ||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: false, |             preserve_header_case: false, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: false, |             h09_responses: false, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1793,15 +1653,10 @@ mod tests { | |||||||
|             cached_headers: &mut None, |             cached_headers: &mut None, | ||||||
|             req_method: &mut None, |             req_method: &mut None, | ||||||
|             h1_parser_config: Default::default(), |             h1_parser_config: Default::default(), | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout: None, |             h1_header_read_timeout: None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_fut: &mut None, |             h1_header_read_timeout_fut: &mut None, | ||||||
|             #[cfg(feature = "runtime")] |  | ||||||
|             h1_header_read_timeout_running: &mut false, |             h1_header_read_timeout_running: &mut false, | ||||||
|             preserve_header_case: true, |             preserve_header_case: true, | ||||||
|             #[cfg(feature = "ffi")] |  | ||||||
|             preserve_header_order: false, |  | ||||||
|             h09_responses: false, |             h09_responses: false, | ||||||
|             #[cfg(feature = "ffi")] |             #[cfg(feature = "ffi")] | ||||||
|             on_informational: &mut None, |             on_informational: &mut None, | ||||||
| @@ -1840,15 +1695,10 @@ mod tests { | |||||||
|                     cached_headers: &mut None, |                     cached_headers: &mut None, | ||||||
|                     req_method: &mut None, |                     req_method: &mut None, | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
| @@ -1868,15 +1718,10 @@ mod tests { | |||||||
|                     cached_headers: &mut None, |                     cached_headers: &mut None, | ||||||
|                     req_method: &mut None, |                     req_method: &mut None, | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
| @@ -2105,15 +1950,10 @@ mod tests { | |||||||
|                     cached_headers: &mut None, |                     cached_headers: &mut None, | ||||||
|                     req_method: &mut Some(Method::GET), |                     req_method: &mut Some(Method::GET), | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
| @@ -2133,15 +1973,10 @@ mod tests { | |||||||
|                     cached_headers: &mut None, |                     cached_headers: &mut None, | ||||||
|                     req_method: &mut Some(m), |                     req_method: &mut Some(m), | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
| @@ -2161,15 +1996,10 @@ mod tests { | |||||||
|                     cached_headers: &mut None, |                     cached_headers: &mut None, | ||||||
|                     req_method: &mut Some(Method::GET), |                     req_method: &mut Some(Method::GET), | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
| @@ -2440,30 +2270,6 @@ 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] |     #[test] | ||||||
|     fn test_client_request_encode_title_case() { |     fn test_client_request_encode_title_case() { | ||||||
|         use crate::proto::BodyLength; |         use crate::proto::BodyLength; | ||||||
| @@ -2690,15 +2496,10 @@ mod tests { | |||||||
|                 cached_headers: &mut None, |                 cached_headers: &mut None, | ||||||
|                 req_method: &mut Some(Method::GET), |                 req_method: &mut Some(Method::GET), | ||||||
|                 h1_parser_config: Default::default(), |                 h1_parser_config: Default::default(), | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout: None, |                 h1_header_read_timeout: None, | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout_fut: &mut None, |                 h1_header_read_timeout_fut: &mut None, | ||||||
|                 #[cfg(feature = "runtime")] |  | ||||||
|                 h1_header_read_timeout_running: &mut false, |                 h1_header_read_timeout_running: &mut false, | ||||||
|                 preserve_header_case: false, |                 preserve_header_case: false, | ||||||
|                 #[cfg(feature = "ffi")] |  | ||||||
|                 preserve_header_order: false, |  | ||||||
|                 h09_responses: false, |                 h09_responses: false, | ||||||
|                 #[cfg(feature = "ffi")] |                 #[cfg(feature = "ffi")] | ||||||
|                 on_informational: &mut None, |                 on_informational: &mut None, | ||||||
| @@ -2782,15 +2583,10 @@ mod tests { | |||||||
|                     cached_headers: &mut headers, |                     cached_headers: &mut headers, | ||||||
|                     req_method: &mut None, |                     req_method: &mut None, | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
| @@ -2830,15 +2626,10 @@ mod tests { | |||||||
|                     cached_headers: &mut headers, |                     cached_headers: &mut headers, | ||||||
|                     req_method: &mut None, |                     req_method: &mut None, | ||||||
|                     h1_parser_config: Default::default(), |                     h1_parser_config: Default::default(), | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout: None, |                     h1_header_read_timeout: None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_fut: &mut None, |                     h1_header_read_timeout_fut: &mut None, | ||||||
|                     #[cfg(feature = "runtime")] |  | ||||||
|                     h1_header_read_timeout_running: &mut false, |                     h1_header_read_timeout_running: &mut false, | ||||||
|                     preserve_header_case: false, |                     preserve_header_case: false, | ||||||
|                     #[cfg(feature = "ffi")] |  | ||||||
|                     preserve_header_order: false, |  | ||||||
|                     h09_responses: false, |                     h09_responses: false, | ||||||
|                     #[cfg(feature = "ffi")] |                     #[cfg(feature = "ffi")] | ||||||
|                     on_informational: &mut None, |                     on_informational: &mut None, | ||||||
|   | |||||||
| @@ -36,7 +36,7 @@ type ConnEof = oneshot::Receiver<Never>; | |||||||
| // for performance. | // for performance. | ||||||
| const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024 * 5; // 5mb | const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024 * 5; // 5mb | ||||||
| const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb | const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb | ||||||
| const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb | // const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb | ||||||
| const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb | const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb | ||||||
|  |  | ||||||
| #[derive(Clone, Debug)] | #[derive(Clone, Debug)] | ||||||
| @@ -44,7 +44,7 @@ pub(crate) struct Config { | |||||||
|     pub(crate) adaptive_window: bool, |     pub(crate) adaptive_window: bool, | ||||||
|     pub(crate) initial_conn_window_size: u32, |     pub(crate) initial_conn_window_size: u32, | ||||||
|     pub(crate) initial_stream_window_size: u32, |     pub(crate) initial_stream_window_size: u32, | ||||||
|     pub(crate) max_frame_size: u32, |     pub(crate) max_frame_size: Option<u32>, | ||||||
|     #[cfg(feature = "runtime")] |     #[cfg(feature = "runtime")] | ||||||
|     pub(crate) keep_alive_interval: Option<Duration>, |     pub(crate) keep_alive_interval: Option<Duration>, | ||||||
|     #[cfg(feature = "runtime")] |     #[cfg(feature = "runtime")] | ||||||
| @@ -53,6 +53,10 @@ pub(crate) struct Config { | |||||||
|     pub(crate) keep_alive_while_idle: bool, |     pub(crate) keep_alive_while_idle: bool, | ||||||
|     pub(crate) max_concurrent_reset_streams: Option<usize>, |     pub(crate) max_concurrent_reset_streams: Option<usize>, | ||||||
|     pub(crate) max_send_buffer_size: usize, |     pub(crate) max_send_buffer_size: usize, | ||||||
|  |     pub(crate) max_concurrent_streams: Option<u32>, | ||||||
|  |     pub(crate) max_header_list_size: Option<u32>, | ||||||
|  |     pub(crate) enable_push: Option<bool>, | ||||||
|  |     pub(crate) header_table_size: Option<u32>, | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Config { | impl Default for Config { | ||||||
| @@ -61,7 +65,7 @@ impl Default for Config { | |||||||
|             adaptive_window: false, |             adaptive_window: false, | ||||||
|             initial_conn_window_size: DEFAULT_CONN_WINDOW, |             initial_conn_window_size: DEFAULT_CONN_WINDOW, | ||||||
|             initial_stream_window_size: DEFAULT_STREAM_WINDOW, |             initial_stream_window_size: DEFAULT_STREAM_WINDOW, | ||||||
|             max_frame_size: DEFAULT_MAX_FRAME_SIZE, |             max_frame_size: None, | ||||||
|             #[cfg(feature = "runtime")] |             #[cfg(feature = "runtime")] | ||||||
|             keep_alive_interval: None, |             keep_alive_interval: None, | ||||||
|             #[cfg(feature = "runtime")] |             #[cfg(feature = "runtime")] | ||||||
| @@ -70,6 +74,10 @@ impl Default for Config { | |||||||
|             keep_alive_while_idle: false, |             keep_alive_while_idle: false, | ||||||
|             max_concurrent_reset_streams: None, |             max_concurrent_reset_streams: None, | ||||||
|             max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE, |             max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE, | ||||||
|  |             max_concurrent_streams: None, | ||||||
|  |             max_header_list_size: None, | ||||||
|  |             enable_push: None, | ||||||
|  |             header_table_size: None, | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -79,12 +87,26 @@ fn new_builder(config: &Config) -> Builder { | |||||||
|     builder |     builder | ||||||
|         .initial_window_size(config.initial_stream_window_size) |         .initial_window_size(config.initial_stream_window_size) | ||||||
|         .initial_connection_window_size(config.initial_conn_window_size) |         .initial_connection_window_size(config.initial_conn_window_size) | ||||||
|         .max_frame_size(config.max_frame_size) |         .max_send_buffer_size(config.max_send_buffer_size); | ||||||
|         .max_send_buffer_size(config.max_send_buffer_size) |  | ||||||
|         .enable_push(false); |  | ||||||
|     if let Some(max) = config.max_concurrent_reset_streams { |     if let Some(max) = config.max_concurrent_reset_streams { | ||||||
|         builder.max_concurrent_reset_streams(max); |         builder.max_concurrent_reset_streams(max); | ||||||
|     } |     } | ||||||
|  |     if let Some(max) = config.max_concurrent_streams { | ||||||
|  |         builder.max_concurrent_streams(max); | ||||||
|  |     } | ||||||
|  |     if let Some(max) = config.max_header_list_size { | ||||||
|  |         builder.max_header_list_size(max); | ||||||
|  |     } | ||||||
|  |     if let Some(opt) = config.enable_push { | ||||||
|  |         builder.enable_push(opt); | ||||||
|  |     } | ||||||
|  |     if let Some(max) = config.max_frame_size { | ||||||
|  |         builder.max_frame_size(max); | ||||||
|  |     } | ||||||
|  |     if let Some(max) = config.header_table_size { | ||||||
|  |         builder.header_table_size(max); | ||||||
|  |     } | ||||||
|  |  | ||||||
|     builder |     builder | ||||||
| } | } | ||||||
|  |  | ||||||
|   | |||||||
| @@ -35,8 +35,6 @@ const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024; // 1mb | |||||||
| const DEFAULT_STREAM_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_FRAME_SIZE: u32 = 1024 * 16; // 16kb | ||||||
| const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 400; // 400kb | 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)] | #[derive(Clone, Debug)] | ||||||
| pub(crate) struct Config { | pub(crate) struct Config { | ||||||
| @@ -51,7 +49,6 @@ pub(crate) struct Config { | |||||||
|     #[cfg(feature = "runtime")] |     #[cfg(feature = "runtime")] | ||||||
|     pub(crate) keep_alive_timeout: Duration, |     pub(crate) keep_alive_timeout: Duration, | ||||||
|     pub(crate) max_send_buffer_size: usize, |     pub(crate) max_send_buffer_size: usize, | ||||||
|     pub(crate) max_header_list_size: u32, |  | ||||||
| } | } | ||||||
|  |  | ||||||
| impl Default for Config { | impl Default for Config { | ||||||
| @@ -68,7 +65,6 @@ impl Default for Config { | |||||||
|             #[cfg(feature = "runtime")] |             #[cfg(feature = "runtime")] | ||||||
|             keep_alive_timeout: Duration::from_secs(20), |             keep_alive_timeout: Duration::from_secs(20), | ||||||
|             max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE, |             max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE, | ||||||
|             max_header_list_size: DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE, |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| } | } | ||||||
| @@ -120,7 +116,6 @@ where | |||||||
|             .initial_window_size(config.initial_stream_window_size) |             .initial_window_size(config.initial_stream_window_size) | ||||||
|             .initial_connection_window_size(config.initial_conn_window_size) |             .initial_connection_window_size(config.initial_conn_window_size) | ||||||
|             .max_frame_size(config.max_frame_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); |             .max_send_buffer_size(config.max_send_buffer_size); | ||||||
|         if let Some(max) = config.max_concurrent_streams { |         if let Some(max) = config.max_concurrent_streams { | ||||||
|             builder.max_concurrent_streams(max); |             builder.max_concurrent_streams(max); | ||||||
| @@ -143,7 +138,7 @@ where | |||||||
|             #[cfg(feature = "runtime")] |             #[cfg(feature = "runtime")] | ||||||
|             keep_alive_timeout: config.keep_alive_timeout, |             keep_alive_timeout: config.keep_alive_timeout, | ||||||
|             // If keep-alive is enabled for servers, always enabled while |             // If keep-alive is enabled for servers, always enabled while | ||||||
|             // idle, so it can more aggressively close dead connections. |             // idle, so it can more aggresively close dead connections. | ||||||
|             #[cfg(feature = "runtime")] |             #[cfg(feature = "runtime")] | ||||||
|             keep_alive_while_idle: true, |             keep_alive_while_idle: true, | ||||||
|         }; |         }; | ||||||
| @@ -264,7 +259,7 @@ where | |||||||
|                         let reason = err.h2_reason(); |                         let reason = err.h2_reason(); | ||||||
|                         if reason == Reason::NO_ERROR { |                         if reason == Reason::NO_ERROR { | ||||||
|                             // NO_ERROR is only used for graceful shutdowns... |                             // NO_ERROR is only used for graceful shutdowns... | ||||||
|                             trace!("interpreting NO_ERROR user error as graceful_shutdown"); |                             trace!("interpretting NO_ERROR user error as graceful_shutdown"); | ||||||
|                             self.conn.graceful_shutdown(); |                             self.conn.graceful_shutdown(); | ||||||
|                         } else { |                         } else { | ||||||
|                             trace!("abruptly shutting down with {:?}", reason); |                             trace!("abruptly shutting down with {:?}", reason); | ||||||
|   | |||||||
							
								
								
									
										111
									
								
								src/server/accept.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										111
									
								
								src/server/accept.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,111 @@ | |||||||
|  | //! 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,6 +5,9 @@ | |||||||
| //! are not handled at this level. This module provides the building blocks to | //! are not handled at this level. This module provides the building blocks to | ||||||
| //! customize those things externally. | //! customize those things externally. | ||||||
| //! | //! | ||||||
|  | //! If you don't have need to manage connections yourself, consider using the | ||||||
|  | //! higher-level [Server](super) API. | ||||||
|  | //! | ||||||
| //! ## Example | //! ## Example | ||||||
| //! A simple example that uses the `Http` struct to talk HTTP over a Tokio TCP stream | //! A simple example that uses the `Http` struct to talk HTTP over a Tokio TCP stream | ||||||
| //! ```no_run | //! ```no_run | ||||||
| @@ -45,7 +48,8 @@ | |||||||
|     not(all(feature = "http1", feature = "http2")) |     not(all(feature = "http1", feature = "http2")) | ||||||
| ))] | ))] | ||||||
| use std::marker::PhantomData; | use std::marker::PhantomData; | ||||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "runtime"))] | #[cfg(feature = "tcp")] | ||||||
|  | use std::net::SocketAddr; | ||||||
| use std::time::Duration; | use std::time::Duration; | ||||||
|  |  | ||||||
| #[cfg(feature = "http2")] | #[cfg(feature = "http2")] | ||||||
| @@ -66,25 +70,34 @@ cfg_feature! { | |||||||
|     use tokio::io::{AsyncRead, AsyncWrite}; |     use tokio::io::{AsyncRead, AsyncWrite}; | ||||||
|     use tracing::trace; |     use tracing::trace; | ||||||
|  |  | ||||||
|  |     use super::accept::Accept; | ||||||
|     use crate::body::{Body, HttpBody}; |     use crate::body::{Body, HttpBody}; | ||||||
|     use crate::common::{task, Future, Pin, Poll, Unpin}; |     use crate::common::{task, Future, Pin, Poll, Unpin}; | ||||||
|     #[cfg(not(all(feature = "http1", feature = "http2")))] |     #[cfg(not(all(feature = "http1", feature = "http2")))] | ||||||
|     use crate::common::Never; |     use crate::common::Never; | ||||||
|     use crate::common::exec::{ConnStreamExec, Exec}; |     use crate::common::exec::{ConnStreamExec, Exec, NewSvcExec}; | ||||||
|     use crate::proto; |     use crate::proto; | ||||||
|     use crate::service::HttpService; |     use crate::service::{HttpService, MakeServiceRef}; | ||||||
|  |     use self::spawn_all::NewSvcTask; | ||||||
|  |  | ||||||
|  |     pub(super) use self::spawn_all::{NoopWatcher, Watcher}; | ||||||
|     pub(super) use self::upgrades::UpgradeableConnection; |     pub(super) use self::upgrades::UpgradeableConnection; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "tcp")] | ||||||
|  | pub use super::tcp::{AddrIncoming, AddrStream}; | ||||||
|  |  | ||||||
| /// A lower-level configuration of the HTTP protocol. | /// A lower-level configuration of the HTTP protocol. | ||||||
| /// | /// | ||||||
| /// This structure is used to configure options for an HTTP server connection. | /// 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)] | #[derive(Clone, Debug)] | ||||||
| #[cfg(any(feature = "http1", feature = "http2"))] | #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
| #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | #[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] | ||||||
| pub struct Http<E = Exec> { | pub struct Http<E = Exec> { | ||||||
|     pub(crate) exec: E, |     exec: E, | ||||||
|     h1_half_close: bool, |     h1_half_close: bool, | ||||||
|     h1_keep_alive: bool, |     h1_keep_alive: bool, | ||||||
|     h1_title_case_headers: bool, |     h1_title_case_headers: bool, | ||||||
| @@ -114,6 +127,51 @@ enum ConnectionMode { | |||||||
|     Fallback, |     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"))] | #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
| pin_project! { | pin_project! { | ||||||
|     /// A future binding a connection with a Service. |     /// A future binding a connection with a Service. | ||||||
| @@ -317,7 +375,7 @@ impl<E> Http<E> { | |||||||
|         self |         self | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     /// Set a timeout for reading client request headers. If a client does not |     /// Set a timeout for reading client request headers. If a client does not  | ||||||
|     /// transmit the entire header within this time, the connection is closed. |     /// transmit the entire header within this time, the connection is closed. | ||||||
|     /// |     /// | ||||||
|     /// Default is None. |     /// Default is None. | ||||||
| @@ -509,16 +567,6 @@ impl<E> Http<E> { | |||||||
|         self |         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. |     /// Set the maximum buffer size for the connection. | ||||||
|     /// |     /// | ||||||
|     /// Default is ~400kb. |     /// Default is ~400kb. | ||||||
| @@ -671,6 +719,23 @@ impl<E> Http<E> { | |||||||
|             fallback: PhantomData, |             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 ===== | // ===== impl Connection ===== | ||||||
| @@ -922,6 +987,141 @@ 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 ===== | // ===== impl ProtoServer ===== | ||||||
|  |  | ||||||
| #[cfg(any(feature = "http1", feature = "http2"))] | #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
| @@ -951,6 +1151,150 @@ 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"))] | #[cfg(any(feature = "http1", feature = "http2"))] | ||||||
| mod upgrades { | mod upgrades { | ||||||
|     use super::*; |     use super::*; | ||||||
|   | |||||||
| @@ -1,10 +1,164 @@ | |||||||
| //! HTTP Server | //! HTTP Server | ||||||
| //! | //! | ||||||
| //! A "server" is usually created by listening on a port for new connections, | //! A `Server` is created to listen on a port, parse HTTP requests, and hand | ||||||
| //! parse HTTP requests, and hand them off to a `Service`. | //! them off to a `Service`. | ||||||
| //! | //! | ||||||
| //! How exactly you choose to listen for connections is not something hyper | //! There are two levels of APIs provide for constructing HTTP servers: | ||||||
| //! concerns itself with. After you have a connection, you can handle HTTP over | //! | ||||||
| //! it with the types in the [`conn`](conn) module. | //! - The higher-level [`Server`](Server) type. | ||||||
| pub mod conn; | //! - 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; | ||||||
|  | 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; | ||||||
|  | } | ||||||
|   | |||||||
							
								
								
									
										560
									
								
								src/server/server.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										560
									
								
								src/server/server.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,560 @@ | |||||||
|  | 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 | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										127
									
								
								src/server/shutdown.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										127
									
								
								src/server/shutdown.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,127 @@ | |||||||
|  | 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() | ||||||
|  | } | ||||||
							
								
								
									
										302
									
								
								src/server/tcp.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										302
									
								
								src/server/tcp.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,302 @@ | |||||||
|  | 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() | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										187
									
								
								src/service/make.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										187
									
								
								src/service/make.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,187 @@ | |||||||
|  | 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,6 +10,10 @@ | |||||||
| //! | //! | ||||||
| //! - `HttpService`: This is blanketly implemented for all types that | //! - `HttpService`: This is blanketly implemented for all types that | ||||||
| //!   implement `Service<http::Request<B1>, Response = http::Response<B2>>`. | //!   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 | //! # HttpService | ||||||
| //! | //! | ||||||
| @@ -20,13 +24,32 @@ | |||||||
| //! The helper [`service_fn`](service_fn) should be sufficient for most cases, but | //! 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 | //! if you need to implement `Service` for a type manually, you can follow the example | ||||||
| //! in `service_struct_impl.rs`. | //! 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; | pub use tower_service::Service; | ||||||
|  |  | ||||||
| mod http; | mod http; | ||||||
|  | mod make; | ||||||
|  | #[cfg(all(any(feature = "http1", feature = "http2"), feature = "client"))] | ||||||
|  | mod oneshot; | ||||||
| mod util; | mod util; | ||||||
|  |  | ||||||
| #[cfg(all(any(feature = "http1", feature = "http2"), feature = "server"))] |  | ||||||
| pub(super) use self::http::HttpService; | 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 use self::make::make_service_fn; | ||||||
| pub use self::util::service_fn; | pub use self::util::service_fn; | ||||||
|   | |||||||
							
								
								
									
										73
									
								
								src/service/oneshot.rs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										73
									
								
								src/service/oneshot.rs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,73 @@ | |||||||
|  | // 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!(), | ||||||
|  |             } | ||||||
|  |         } | ||||||
|  |     } | ||||||
|  | } | ||||||
							
								
								
									
										1382
									
								
								tests/client.rs
									
									
									
									
									
								
							
							
						
						
									
										1382
									
								
								tests/client.rs
									
									
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
							
								
								
									
										340
									
								
								tests/server.rs
									
									
									
									
									
								
							
							
						
						
									
										340
									
								
								tests/server.rs
									
									
									
									
									
								
							| @@ -1,7 +1,6 @@ | |||||||
| #![deny(warnings)] | #![deny(warnings)] | ||||||
| #![deny(rust_2018_idioms)] | #![deny(rust_2018_idioms)] | ||||||
|  |  | ||||||
| use std::convert::TryInto; |  | ||||||
| use std::future::Future; | use std::future::Future; | ||||||
| use std::io::{self, Read, Write}; | use std::io::{self, Read, Write}; | ||||||
| use std::net::TcpListener as StdTcpListener; | use std::net::TcpListener as StdTcpListener; | ||||||
| @@ -17,18 +16,20 @@ use std::time::Duration; | |||||||
| use bytes::Bytes; | use bytes::Bytes; | ||||||
| use futures_channel::oneshot; | use futures_channel::oneshot; | ||||||
| use futures_util::future::{self, Either, FutureExt, TryFutureExt}; | use futures_util::future::{self, Either, FutureExt, TryFutureExt}; | ||||||
|  | #[cfg(feature = "stream")] | ||||||
|  | use futures_util::stream::StreamExt as _; | ||||||
| use h2::client::SendRequest; | use h2::client::SendRequest; | ||||||
| use h2::{RecvStream, SendStream}; | use h2::{RecvStream, SendStream}; | ||||||
| use http::header::{HeaderName, HeaderValue}; | use http::header::{HeaderName, HeaderValue}; | ||||||
| use http_body_util::{combinators::BoxBody, BodyExt, StreamBody}; | use tokio::io::{AsyncRead, AsyncReadExt, AsyncWrite, AsyncWriteExt, ReadBuf}; | ||||||
| use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; | use tokio::net::{TcpListener, TcpStream as TkTcpStream}; | ||||||
| use tokio::io::{AsyncReadExt, AsyncWriteExt}; |  | ||||||
| use tokio::net::{TcpListener as TkTcpListener, TcpListener, TcpStream as TkTcpStream}; |  | ||||||
|  |  | ||||||
| use hyper::body::HttpBody; | use hyper::body::HttpBody as _; | ||||||
|  | use hyper::client::Client; | ||||||
| use hyper::server::conn::Http; | use hyper::server::conn::Http; | ||||||
| use hyper::service::service_fn; | use hyper::server::Server; | ||||||
| use hyper::{Body, Method, Request, Response, StatusCode, Uri, Version}; | use hyper::service::{make_service_fn, service_fn}; | ||||||
|  | use hyper::{Body, Request, Response, StatusCode, Version}; | ||||||
|  |  | ||||||
| mod support; | mod support; | ||||||
|  |  | ||||||
| @@ -108,7 +109,8 @@ mod response_body_lengths { | |||||||
|                 b |                 b | ||||||
|             } |             } | ||||||
|             Bd::Unknown(b) => { |             Bd::Unknown(b) => { | ||||||
|                 let body = futures_util::stream::once(async move { Ok(b.into()) }); |                 let (mut tx, body) = hyper::Body::channel(); | ||||||
|  |                 tx.try_send_data(b.into()).expect("try_send_data"); | ||||||
|                 reply.body_stream(body); |                 reply.body_stream(body); | ||||||
|                 b |                 b | ||||||
|             } |             } | ||||||
| @@ -319,11 +321,15 @@ mod response_body_lengths { | |||||||
|  |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn http2_auto_response_with_known_length() { |     async fn http2_auto_response_with_known_length() { | ||||||
|  |         use http_body::Body; | ||||||
|  |  | ||||||
|         let server = serve(); |         let server = serve(); | ||||||
|         let addr_str = format!("http://{}", server.addr()); |         let addr_str = format!("http://{}", server.addr()); | ||||||
|         server.reply().body("Hello, World!"); |         server.reply().body("Hello, World!"); | ||||||
|  |  | ||||||
|         let client = TestClient::new().http2_only(); |         let client = Client::builder() | ||||||
|  |             .http2_only(true) | ||||||
|  |             .build_http::<hyper::Body>(); | ||||||
|         let uri = addr_str |         let uri = addr_str | ||||||
|             .parse::<hyper::Uri>() |             .parse::<hyper::Uri>() | ||||||
|             .expect("server addr should parse"); |             .expect("server addr should parse"); | ||||||
| @@ -335,6 +341,8 @@ mod response_body_lengths { | |||||||
|  |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn http2_auto_response_with_conflicting_lengths() { |     async fn http2_auto_response_with_conflicting_lengths() { | ||||||
|  |         use http_body::Body; | ||||||
|  |  | ||||||
|         let server = serve(); |         let server = serve(); | ||||||
|         let addr_str = format!("http://{}", server.addr()); |         let addr_str = format!("http://{}", server.addr()); | ||||||
|         server |         server | ||||||
| @@ -342,7 +350,9 @@ mod response_body_lengths { | |||||||
|             .header("content-length", "10") |             .header("content-length", "10") | ||||||
|             .body("Hello, World!"); |             .body("Hello, World!"); | ||||||
|  |  | ||||||
|         let client = TestClient::new().http2_only(); |         let client = Client::builder() | ||||||
|  |             .http2_only(true) | ||||||
|  |             .build_http::<hyper::Body>(); | ||||||
|         let uri = addr_str |         let uri = addr_str | ||||||
|             .parse::<hyper::Uri>() |             .parse::<hyper::Uri>() | ||||||
|             .expect("server addr should parse"); |             .expect("server addr should parse"); | ||||||
| @@ -354,11 +364,15 @@ mod response_body_lengths { | |||||||
|  |  | ||||||
|     #[tokio::test] |     #[tokio::test] | ||||||
|     async fn http2_implicit_empty_size_hint() { |     async fn http2_implicit_empty_size_hint() { | ||||||
|  |         use http_body::Body; | ||||||
|  |  | ||||||
|         let server = serve(); |         let server = serve(); | ||||||
|         let addr_str = format!("http://{}", server.addr()); |         let addr_str = format!("http://{}", server.addr()); | ||||||
|         server.reply(); |         server.reply(); | ||||||
|  |  | ||||||
|         let client = TestClient::new().http2_only(); |         let client = Client::builder() | ||||||
|  |             .http2_only(true) | ||||||
|  |             .build_http::<hyper::Body>(); | ||||||
|         let uri = addr_str |         let uri = addr_str | ||||||
|             .parse::<hyper::Uri>() |             .parse::<hyper::Uri>() | ||||||
|             .expect("server addr should parse"); |             .expect("server addr should parse"); | ||||||
| @@ -369,33 +383,6 @@ 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] | #[test] | ||||||
| fn get_chunked_response_with_ka() { | fn get_chunked_response_with_ka() { | ||||||
|     let foo_bar = b"foo bar baz"; |     let foo_bar = b"foo bar baz"; | ||||||
| @@ -1467,6 +1454,8 @@ async fn header_read_timeout_slow_writes_multiple_requests() { | |||||||
|  |  | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn upgrades() { | async fn upgrades() { | ||||||
|  |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = listener.local_addr().unwrap(); | ||||||
| @@ -1524,6 +1513,8 @@ async fn upgrades() { | |||||||
|  |  | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn http_connect() { | async fn http_connect() { | ||||||
|  |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = listener.local_addr().unwrap(); | ||||||
| @@ -1658,19 +1649,15 @@ async fn upgrades_ignored() { | |||||||
|             future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty())) |             future::ok::<_, hyper::Error>(Response::new(hyper::Body::empty())) | ||||||
|         }); |         }); | ||||||
|  |  | ||||||
|         loop { |         let (socket, _) = listener.accept().await.unwrap(); | ||||||
|             let (socket, _) = listener.accept().await.unwrap(); |         Http::new() | ||||||
|             tokio::task::spawn(async move { |             .serve_connection(socket, svc) | ||||||
|                 Http::new() |             .with_upgrades() | ||||||
|                     .serve_connection(socket, svc) |             .await | ||||||
|                     .with_upgrades() |             .expect("server task"); | ||||||
|                     .await |  | ||||||
|                     .expect("server task"); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     let client = TestClient::new(); |     let client = hyper::Client::new(); | ||||||
|     let url = format!("http://{}/", addr); |     let url = format!("http://{}/", addr); | ||||||
|  |  | ||||||
|     let make_req = || { |     let make_req = || { | ||||||
| @@ -1692,6 +1679,8 @@ async fn upgrades_ignored() { | |||||||
|  |  | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn http_connect_new() { | async fn http_connect_new() { | ||||||
|  |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = listener.local_addr().unwrap(); | ||||||
| @@ -1756,6 +1745,8 @@ async fn http_connect_new() { | |||||||
|  |  | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn h2_connect() { | async fn h2_connect() { | ||||||
|  |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = listener.local_addr().unwrap(); | ||||||
| @@ -1825,7 +1816,7 @@ async fn h2_connect() { | |||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn h2_connect_multiplex() { | async fn h2_connect_multiplex() { | ||||||
|     use futures_util::stream::FuturesUnordered; |     use futures_util::stream::FuturesUnordered; | ||||||
|     use futures_util::StreamExt; |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
| @@ -1936,6 +1927,8 @@ async fn h2_connect_multiplex() { | |||||||
|  |  | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn h2_connect_large_body() { | async fn h2_connect_large_body() { | ||||||
|  |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = listener.local_addr().unwrap(); | ||||||
| @@ -2011,6 +2004,8 @@ async fn h2_connect_large_body() { | |||||||
|  |  | ||||||
| #[tokio::test] | #[tokio::test] | ||||||
| async fn h2_connect_empty_frames() { | async fn h2_connect_empty_frames() { | ||||||
|  |     use tokio::io::{AsyncReadExt, AsyncWriteExt}; | ||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); |     let listener = tcp_bind(&"127.0.0.1:0".parse().unwrap()).unwrap(); | ||||||
|     let addr = listener.local_addr().unwrap(); |     let addr = listener.local_addr().unwrap(); | ||||||
| @@ -2169,17 +2164,17 @@ async fn max_buf_size() { | |||||||
|         .expect_err("should TooLarge error"); |         .expect_err("should TooLarge error"); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "stream")] | ||||||
| #[test] | #[test] | ||||||
| fn streaming_body() { | fn streaming_body() { | ||||||
|     use futures_util::StreamExt; |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|  |  | ||||||
|     // disable keep-alive so we can use read_to_end |     // disable keep-alive so we can use read_to_end | ||||||
|     let server = serve_opts().keep_alive(false).serve(); |     let server = serve_opts().keep_alive(false).serve(); | ||||||
|  |  | ||||||
|     static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 100] as _; |     static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_00] as _; | ||||||
|     let b = |     let b = futures_util::stream::iter(S.iter()).map(|&s| Ok::<_, hyper::Error>(s)); | ||||||
|         futures_util::stream::iter(S.iter()).map(|&s| Ok::<_, BoxError>(Bytes::copy_from_slice(s))); |     let b = hyper::Body::wrap_stream(b); | ||||||
|     server.reply().body_stream(b); |     server.reply().body_stream(b); | ||||||
|  |  | ||||||
|     let mut tcp = connect(server.addr()); |     let mut tcp = connect(server.addr()); | ||||||
| @@ -2203,8 +2198,8 @@ fn http1_response_with_http2_version() { | |||||||
|  |  | ||||||
|     server.reply().version(hyper::Version::HTTP_2); |     server.reply().version(hyper::Version::HTTP_2); | ||||||
|  |  | ||||||
|     let client = TestClient::new(); |  | ||||||
|     rt.block_on({ |     rt.block_on({ | ||||||
|  |         let client = Client::new(); | ||||||
|         let uri = addr_str.parse().expect("server addr should parse"); |         let uri = addr_str.parse().expect("server addr should parse"); | ||||||
|         client.get(uri) |         client.get(uri) | ||||||
|     }) |     }) | ||||||
| @@ -2218,8 +2213,10 @@ fn try_h2() { | |||||||
|  |  | ||||||
|     let rt = support::runtime(); |     let rt = support::runtime(); | ||||||
|  |  | ||||||
|     let client = TestClient::new().http2_only(); |  | ||||||
|     rt.block_on({ |     rt.block_on({ | ||||||
|  |         let client = Client::builder() | ||||||
|  |             .http2_only(true) | ||||||
|  |             .build_http::<hyper::Body>(); | ||||||
|         let uri = addr_str.parse().expect("server addr should parse"); |         let uri = addr_str.parse().expect("server addr should parse"); | ||||||
|  |  | ||||||
|         client.get(uri).map_ok(|_| ()).map_err(|_e| ()) |         client.get(uri).map_ok(|_| ()).map_err(|_e| ()) | ||||||
| @@ -2236,8 +2233,10 @@ fn http1_only() { | |||||||
|  |  | ||||||
|     let rt = support::runtime(); |     let rt = support::runtime(); | ||||||
|  |  | ||||||
|     let client = TestClient::new().http2_only(); |  | ||||||
|     rt.block_on({ |     rt.block_on({ | ||||||
|  |         let client = Client::builder() | ||||||
|  |             .http2_only(true) | ||||||
|  |             .build_http::<hyper::Body>(); | ||||||
|         let uri = addr_str.parse().expect("server addr should parse"); |         let uri = addr_str.parse().expect("server addr should parse"); | ||||||
|         client.get(uri) |         client.get(uri) | ||||||
|     }) |     }) | ||||||
| @@ -2257,8 +2256,9 @@ async fn http2_service_error_sends_reset_reason() { | |||||||
|  |  | ||||||
|     let uri = addr_str.parse().expect("server addr should parse"); |     let uri = addr_str.parse().expect("server addr should parse"); | ||||||
|     dbg!("start"); |     dbg!("start"); | ||||||
|     let err = dbg!(TestClient::new() |     let err = dbg!(Client::builder() | ||||||
|         .http2_only() |         .http2_only(true) | ||||||
|  |         .build_http::<hyper::Body>() | ||||||
|         .get(uri) |         .get(uri) | ||||||
|         .await |         .await | ||||||
|         .expect_err("client.get")); |         .expect_err("client.get")); | ||||||
| @@ -2272,28 +2272,32 @@ async fn http2_service_error_sends_reset_reason() { | |||||||
|     assert_eq!(h2_err.reason(), Some(h2::Reason::INADEQUATE_SECURITY)); |     assert_eq!(h2_err.reason(), Some(h2::Reason::INADEQUATE_SECURITY)); | ||||||
| } | } | ||||||
|  |  | ||||||
|  | #[cfg(feature = "stream")] | ||||||
| #[test] | #[test] | ||||||
| fn http2_body_user_error_sends_reset_reason() { | fn http2_body_user_error_sends_reset_reason() { | ||||||
|     use std::error::Error; |     use std::error::Error; | ||||||
|     let server = serve(); |     let server = serve(); | ||||||
|     let addr_str = format!("http://{}", server.addr()); |     let addr_str = format!("http://{}", server.addr()); | ||||||
|  |  | ||||||
|     let b = futures_util::stream::once(future::err::<Bytes, BoxError>(Box::new(h2::Error::from( |     let b = futures_util::stream::once(future::err::<String, _>(h2::Error::from( | ||||||
|         h2::Reason::INADEQUATE_SECURITY, |         h2::Reason::INADEQUATE_SECURITY, | ||||||
|     )))); |     ))); | ||||||
|  |     let b = hyper::Body::wrap_stream(b); | ||||||
|  |  | ||||||
|     server.reply().body_stream(b); |     server.reply().body_stream(b); | ||||||
|  |  | ||||||
|     let rt = support::runtime(); |     let rt = support::runtime(); | ||||||
|  |  | ||||||
|     let err: hyper::Error = rt |     let err: hyper::Error = rt | ||||||
|         .block_on(async move { |         .block_on(async move { | ||||||
|             let client = TestClient::new().http2_only(); |             let client = Client::builder() | ||||||
|  |                 .http2_only(true) | ||||||
|  |                 .build_http::<hyper::Body>(); | ||||||
|             let uri = addr_str.parse().expect("server addr should parse"); |             let uri = addr_str.parse().expect("server addr should parse"); | ||||||
|  |  | ||||||
|             let mut res = client.get(uri).await?; |             let mut res = client.get(uri).await?; | ||||||
|  |  | ||||||
|             while let Some(chunk) = res.body_mut().data().await { |             while let Some(chunk) = res.body_mut().next().await { | ||||||
|                 chunk?; |                 chunk?; | ||||||
|             } |             } | ||||||
|             Ok(()) |             Ok(()) | ||||||
| @@ -2335,33 +2339,22 @@ async fn http2_service_poll_ready_error_sends_goaway() { | |||||||
|  |  | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
|  |  | ||||||
|     let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) |     let server = hyper::Server::bind(&([127, 0, 0, 1], 0).into()) | ||||||
|         .await |         .http2_only(true) | ||||||
|         .unwrap(); |         .serve(make_service_fn(|_| async move { | ||||||
|  |             Ok::<_, BoxError>(Http2ReadyErrorSvc) | ||||||
|  |         })); | ||||||
|  |  | ||||||
|     let addr_str = format!("http://{}", listener.local_addr().unwrap()); |     let addr_str = format!("http://{}", server.local_addr()); | ||||||
|  |  | ||||||
|     tokio::task::spawn(async move { |     tokio::task::spawn(async move { | ||||||
|         loop { |         server.await.expect("server"); | ||||||
|             tokio::select! { |  | ||||||
|                 res = listener.accept() => { |  | ||||||
|                     let (stream, _) = res.unwrap(); |  | ||||||
|  |  | ||||||
|                     tokio::task::spawn(async move { |  | ||||||
|                         let mut http = Http::new(); |  | ||||||
|                         http.http2_only(true); |  | ||||||
|  |  | ||||||
|                         let service = Http2ReadyErrorSvc; |  | ||||||
|                         http.serve_connection(stream, service).await.unwrap(); |  | ||||||
|                     }); |  | ||||||
|                 } |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     let uri = addr_str.parse().expect("server addr should parse"); |     let uri = addr_str.parse().expect("server addr should parse"); | ||||||
|     let err = dbg!(TestClient::new() |     let err = dbg!(Client::builder() | ||||||
|         .http2_only() |         .http2_only(true) | ||||||
|  |         .build_http::<hyper::Body>() | ||||||
|         .get(uri) |         .get(uri) | ||||||
|         .await |         .await | ||||||
|         .expect_err("client.get should fail")); |         .expect_err("client.get should fail")); | ||||||
| @@ -2428,26 +2421,6 @@ fn skips_content_length_and_body_for_304_responses() { | |||||||
|     assert_eq!(lines.next(), None); |     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] | #[tokio::test] | ||||||
| async fn http2_keep_alive_detects_unresponsive_client() { | async fn http2_keep_alive_detects_unresponsive_client() { | ||||||
|     let _ = pretty_env_logger::try_init(); |     let _ = pretty_env_logger::try_init(); | ||||||
| @@ -2666,7 +2639,7 @@ impl Serve { | |||||||
| } | } | ||||||
|  |  | ||||||
| type BoxError = Box<dyn std::error::Error + Send + Sync>; | type BoxError = Box<dyn std::error::Error + Send + Sync>; | ||||||
| type BoxFuture = Pin<Box<dyn Future<Output = Result<Response<ReplyBody>, BoxError>> + Send>>; | type BoxFuture = Pin<Box<dyn Future<Output = Result<Response<Body>, BoxError>> + Send>>; | ||||||
|  |  | ||||||
| struct ReplyBuilder<'a> { | struct ReplyBuilder<'a> { | ||||||
|     tx: &'a Mutex<spmc::Sender<Reply>>, |     tx: &'a Mutex<spmc::Sender<Reply>>, | ||||||
| @@ -2678,17 +2651,6 @@ impl<'a> ReplyBuilder<'a> { | |||||||
|         self |         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 { |     fn version(self, version: hyper::Version) -> Self { | ||||||
|         self.tx |         self.tx | ||||||
|             .lock() |             .lock() | ||||||
| @@ -2710,16 +2672,14 @@ impl<'a> ReplyBuilder<'a> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn body<T: AsRef<[u8]>>(self, body: T) { |     fn body<T: AsRef<[u8]>>(self, body: T) { | ||||||
|         let chunk = Bytes::copy_from_slice(body.as_ref()); |         self.tx | ||||||
|         let body = BodyExt::boxed(http_body_util::Full::new(chunk).map_err(|e| match e {})); |             .lock() | ||||||
|         self.tx.lock().unwrap().send(Reply::Body(body)).unwrap(); |             .unwrap() | ||||||
|  |             .send(Reply::Body(body.as_ref().to_vec().into())) | ||||||
|  |             .unwrap(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     fn body_stream<S>(self, stream: S) |     fn body_stream(self, body: Body) { | ||||||
|     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(); |         self.tx.lock().unwrap().send(Reply::Body(body)).unwrap(); | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -2761,15 +2721,12 @@ struct TestService { | |||||||
|     reply: spmc::Receiver<Reply>, |     reply: spmc::Receiver<Reply>, | ||||||
| } | } | ||||||
|  |  | ||||||
| type ReplyBody = BoxBody<Bytes, BoxError>; |  | ||||||
|  |  | ||||||
| #[derive(Debug)] | #[derive(Debug)] | ||||||
| enum Reply { | enum Reply { | ||||||
|     Status(hyper::StatusCode), |     Status(hyper::StatusCode), | ||||||
|     ReasonPhrase(hyper::ext::ReasonPhrase), |  | ||||||
|     Version(hyper::Version), |     Version(hyper::Version), | ||||||
|     Header(HeaderName, HeaderValue), |     Header(HeaderName, HeaderValue), | ||||||
|     Body(ReplyBody), |     Body(hyper::Body), | ||||||
|     Error(BoxError), |     Error(BoxError), | ||||||
|     End, |     End, | ||||||
| } | } | ||||||
| @@ -2782,7 +2739,7 @@ enum Msg { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl tower_service::Service<Request<Body>> for TestService { | impl tower_service::Service<Request<Body>> for TestService { | ||||||
|     type Response = Response<ReplyBody>; |     type Response = Response<Body>; | ||||||
|     type Error = BoxError; |     type Error = BoxError; | ||||||
|     type Future = BoxFuture; |     type Future = BoxFuture; | ||||||
|  |  | ||||||
| @@ -2815,18 +2772,13 @@ impl tower_service::Service<Request<Body>> for TestService { | |||||||
| } | } | ||||||
|  |  | ||||||
| impl TestService { | impl TestService { | ||||||
|     fn build_reply(replies: spmc::Receiver<Reply>) -> Result<Response<ReplyBody>, BoxError> { |     fn build_reply(replies: spmc::Receiver<Reply>) -> Result<Response<Body>, BoxError> { | ||||||
|         let empty = |         let mut res = Response::new(Body::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() { |         while let Ok(reply) = replies.try_recv() { | ||||||
|             match reply { |             match reply { | ||||||
|                 Reply::Status(s) => { |                 Reply::Status(s) => { | ||||||
|                     *res.status_mut() = s; |                     *res.status_mut() = s; | ||||||
|                 } |                 } | ||||||
|                 Reply::ReasonPhrase(reason) => { |  | ||||||
|                     res.extensions_mut().insert(reason); |  | ||||||
|                 } |  | ||||||
|                 Reply::Version(v) => { |                 Reply::Version(v) => { | ||||||
|                     *res.version_mut() = v; |                     *res.version_mut() = v; | ||||||
|                 } |                 } | ||||||
| @@ -2865,7 +2817,7 @@ impl tower_service::Service<Request<Body>> for HelloWorld { | |||||||
|  |  | ||||||
| fn unreachable_service() -> impl tower_service::Service< | fn unreachable_service() -> impl tower_service::Service< | ||||||
|     http::Request<hyper::Body>, |     http::Request<hyper::Body>, | ||||||
|     Response = http::Response<ReplyBody>, |     Response = http::Response<hyper::Body>, | ||||||
|     Error = BoxError, |     Error = BoxError, | ||||||
|     Future = BoxFuture, |     Future = BoxFuture, | ||||||
| > { | > { | ||||||
| @@ -2931,9 +2883,9 @@ impl ServeOptions { | |||||||
|         let (addr_tx, addr_rx) = mpsc::channel(); |         let (addr_tx, addr_rx) = mpsc::channel(); | ||||||
|         let (msg_tx, msg_rx) = mpsc::channel(); |         let (msg_tx, msg_rx) = mpsc::channel(); | ||||||
|         let (reply_tx, reply_rx) = spmc::channel(); |         let (reply_tx, reply_rx) = spmc::channel(); | ||||||
|         let (shutdown_tx, mut shutdown_rx) = oneshot::channel(); |         let (shutdown_tx, shutdown_rx) = oneshot::channel(); | ||||||
|  |  | ||||||
|         let addr: SocketAddr = ([127, 0, 0, 1], 0).into(); |         let addr = ([127, 0, 0, 1], 0).into(); | ||||||
|  |  | ||||||
|         let thread_name = format!( |         let thread_name = format!( | ||||||
|             "test-server-{}", |             "test-server-{}", | ||||||
| @@ -2944,46 +2896,36 @@ impl ServeOptions { | |||||||
|         let thread = thread::Builder::new() |         let thread = thread::Builder::new() | ||||||
|             .name(thread_name) |             .name(thread_name) | ||||||
|             .spawn(move || { |             .spawn(move || { | ||||||
|                 support::runtime().block_on(async move { |                 support::runtime() | ||||||
|                     let listener = TkTcpListener::bind(addr).await.unwrap(); |                     .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, | ||||||
|  |                             }) | ||||||
|  |                         }); | ||||||
|  |  | ||||||
|                     addr_tx |                         let builder = Server::bind(&addr); | ||||||
|                         .send(listener.local_addr().unwrap()) |  | ||||||
|                         .expect("server addr tx"); |  | ||||||
|  |  | ||||||
|                     loop { |                         #[cfg(feature = "http1")] | ||||||
|                         let msg_tx = msg_tx.clone(); |                         let builder = builder | ||||||
|                         let reply_rx = reply_rx.clone(); |                             .http1_only(_options.http1_only) | ||||||
|  |                             .http1_keepalive(_options.keep_alive) | ||||||
|  |                             .http1_pipeline_flush(_options.pipeline); | ||||||
|  |  | ||||||
|                         tokio::select! { |                         let server = builder.serve(service); | ||||||
|                             res = listener.accept() => { |  | ||||||
|                                 let (stream, _) = res.unwrap(); |  | ||||||
|  |  | ||||||
|                                 tokio::task::spawn(async move { |                         addr_tx.send(server.local_addr()).expect("server addr tx"); | ||||||
|                                     let mut http = Http::new(); |  | ||||||
|  |  | ||||||
|                                     #[cfg(feature = "http1")] |                         server | ||||||
|                                     let http = http |                             .with_graceful_shutdown(async { | ||||||
|                                         .http1_only(_options.http1_only) |                                 let _ = shutdown_rx.await; | ||||||
|                                         .http1_keep_alive(_options.keep_alive) |                             }) | ||||||
|                                         .pipeline_flush(_options.pipeline); |                             .await | ||||||
|  |                     }) | ||||||
|                                     let msg_tx = msg_tx.clone(); |                     .expect("serve()"); | ||||||
|                                     let reply_rx = reply_rx.clone(); |  | ||||||
|                                     let service = TestService { |  | ||||||
|                                         tx: msg_tx, |  | ||||||
|                                         reply: reply_rx, |  | ||||||
|                                     }; |  | ||||||
|  |  | ||||||
|                                     http.serve_connection(stream, service).await.unwrap(); |  | ||||||
|                                 }); |  | ||||||
|                             } |  | ||||||
|                             _ = &mut shutdown_rx => { |  | ||||||
|                                 break; |  | ||||||
|                             } |  | ||||||
|                         } |  | ||||||
|                     } |  | ||||||
|                 }) |  | ||||||
|             }) |             }) | ||||||
|             .expect("thread spawn"); |             .expect("thread spawn"); | ||||||
|  |  | ||||||
| @@ -3112,49 +3054,3 @@ impl Drop for Dropped { | |||||||
|         self.0.store(true, Ordering::SeqCst); |         self.0.store(true, Ordering::SeqCst); | ||||||
|     } |     } | ||||||
| } | } | ||||||
|  |  | ||||||
| struct TestClient { |  | ||||||
|     http2_only: bool, |  | ||||||
| } |  | ||||||
|  |  | ||||||
| impl TestClient { |  | ||||||
|     fn new() -> Self { |  | ||||||
|         Self { http2_only: false } |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     fn http2_only(mut self) -> Self { |  | ||||||
|         self.http2_only = true; |  | ||||||
|         self |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn get(&self, uri: Uri) -> Result<Response<Body>, hyper::Error> { |  | ||||||
|         self.request( |  | ||||||
|             Request::builder() |  | ||||||
|                 .uri(uri) |  | ||||||
|                 .method(Method::GET) |  | ||||||
|                 .body(Body::empty()) |  | ||||||
|                 .unwrap(), |  | ||||||
|         ) |  | ||||||
|         .await |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     async fn request(&self, req: Request<Body>) -> Result<Response<Body>, hyper::Error> { |  | ||||||
|         let host = req.uri().host().expect("uri has no host"); |  | ||||||
|         let port = req.uri().port_u16().expect("uri has no port"); |  | ||||||
|  |  | ||||||
|         let mut builder = hyper::client::conn::Builder::new(); |  | ||||||
|         builder.http2_only(self.http2_only); |  | ||||||
|  |  | ||||||
|         let stream = TkTcpStream::connect(format!("{}:{}", host, port)) |  | ||||||
|             .await |  | ||||||
|             .unwrap(); |  | ||||||
|  |  | ||||||
|         let (mut sender, conn) = builder.handshake(stream).await.unwrap(); |  | ||||||
|  |  | ||||||
|         tokio::task::spawn(async move { |  | ||||||
|             conn.await.unwrap(); |  | ||||||
|         }); |  | ||||||
|  |  | ||||||
|         sender.send_request(req).await |  | ||||||
|     } |  | ||||||
| } |  | ||||||
|   | |||||||
| @@ -6,12 +6,9 @@ use std::sync::{ | |||||||
|     Arc, Mutex, |     Arc, Mutex, | ||||||
| }; | }; | ||||||
|  |  | ||||||
| use hyper::client::conn::Builder; | use hyper::client::HttpConnector; | ||||||
| use hyper::server::conn::Http; | use hyper::service::{make_service_fn, service_fn}; | ||||||
| use tokio::net::{TcpListener, TcpStream}; | use hyper::{Body, Client, Request, Response, Server, Version}; | ||||||
|  |  | ||||||
| use hyper::service::service_fn; |  | ||||||
| use hyper::{Body, Request, Response, Version}; |  | ||||||
|  |  | ||||||
| pub use futures_util::{ | pub use futures_util::{ | ||||||
|     future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _, |     future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _, | ||||||
| @@ -329,20 +326,16 @@ async fn async_test(cfg: __TestConfig) { | |||||||
|         Version::HTTP_11 |         Version::HTTP_11 | ||||||
|     }; |     }; | ||||||
|  |  | ||||||
|     let http2_only = cfg.server_version == 2; |     let connector = HttpConnector::new(); | ||||||
|  |     let client = Client::builder() | ||||||
|  |         .http2_only(cfg.client_version == 2) | ||||||
|  |         .build::<_, Body>(connector); | ||||||
|  |  | ||||||
|     let serve_handles = Arc::new(Mutex::new(cfg.server_msgs)); |     let serve_handles = Arc::new(Mutex::new(cfg.server_msgs)); | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) |  | ||||||
|         .await |  | ||||||
|         .unwrap(); |  | ||||||
|  |  | ||||||
|     let mut addr = listener.local_addr().unwrap(); |  | ||||||
|  |  | ||||||
|     let expected_connections = cfg.connections; |     let expected_connections = cfg.connections; | ||||||
|     tokio::task::spawn(async move { |     let mut cnt = 0; | ||||||
|         let mut cnt = 0; |     let new_service = make_service_fn(move |_| { | ||||||
|  |  | ||||||
|         cnt += 1; |         cnt += 1; | ||||||
|         assert!( |         assert!( | ||||||
|             cnt <= expected_connections, |             cnt <= expected_connections, | ||||||
| @@ -351,108 +344,98 @@ async fn async_test(cfg: __TestConfig) { | |||||||
|             cnt |             cnt | ||||||
|         ); |         ); | ||||||
|  |  | ||||||
|         loop { |         // Move a clone into the service_fn | ||||||
|             let (stream, _) = listener.accept().await.expect("server error"); |         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); | ||||||
|  |  | ||||||
|             // Move a clone into the service_fn |             assert_eq!(req.uri().path(), sreq.uri, "client path"); | ||||||
|             let serve_handles = serve_handles.clone(); |             assert_eq!(req.method(), &sreq.method, "client method"); | ||||||
|             let service = service_fn(move |req: Request<Body>| { |             assert_eq!(req.version(), version, "client version"); | ||||||
|                 let (sreq, sres) = serve_handles.lock().unwrap().remove(0); |             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"); | ||||||
|  |  | ||||||
|                 assert_eq!(req.uri().path(), sreq.uri, "client path"); |                 let mut res = Response::builder() | ||||||
|                 assert_eq!(req.method(), &sreq.method, "client method"); |                     .status(sres.status) | ||||||
|                 assert_eq!(req.version(), version, "client version"); |                     .body(Body::from(sres.body)) | ||||||
|                 for func in &sreq.headers { |                     .expect("Response::build"); | ||||||
|                     func(&req.headers()); |                 *res.headers_mut() = sres.headers; | ||||||
|                 } |                 res | ||||||
|                 let sbody = sreq.body; |             }) | ||||||
|                 hyper::body::to_bytes(req).map_ok(move |body| { |         })) | ||||||
|                     assert_eq!(body.as_ref(), sbody.as_slice(), "client body"); |  | ||||||
|  |  | ||||||
|                     let mut res = Response::builder() |  | ||||||
|                         .status(sres.status) |  | ||||||
|                         .body(Body::from(sres.body)) |  | ||||||
|                         .expect("Response::build"); |  | ||||||
|                     *res.headers_mut() = sres.headers; |  | ||||||
|                     res |  | ||||||
|                 }) |  | ||||||
|             }); |  | ||||||
|  |  | ||||||
|             tokio::task::spawn(async move { |  | ||||||
|                 Http::new() |  | ||||||
|                     .http2_only(http2_only) |  | ||||||
|                     .serve_connection(stream, service) |  | ||||||
|                     .await |  | ||||||
|                     .expect("server error"); |  | ||||||
|             }); |  | ||||||
|         } |  | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     let server = hyper::Server::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) | ||||||
|  |         .http2_only(cfg.server_version == 2) | ||||||
|  |         .serve(new_service); | ||||||
|  |  | ||||||
|  |     let mut addr = server.local_addr(); | ||||||
|  |  | ||||||
|  |     tokio::task::spawn(server.map(|result| { | ||||||
|  |         result.expect("server error"); | ||||||
|  |     })); | ||||||
|  |  | ||||||
|     if cfg.proxy { |     if cfg.proxy { | ||||||
|         let (proxy_addr, proxy) = naive_proxy(ProxyConfig { |         let (proxy_addr, proxy) = naive_proxy(ProxyConfig { | ||||||
|             connections: cfg.connections, |             connections: cfg.connections, | ||||||
|             dst: addr, |             dst: addr, | ||||||
|             version: cfg.server_version, |             version: cfg.server_version, | ||||||
|         }) |         }); | ||||||
|         .await; |  | ||||||
|         tokio::task::spawn(proxy); |         tokio::task::spawn(proxy); | ||||||
|         addr = proxy_addr; |         addr = proxy_addr; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     let make_request = Arc::new(move |creq: __CReq, cres: __CRes| { |     let make_request = Arc::new( | ||||||
|         let uri = format!("http://{}{}", addr, creq.uri); |         move |client: &Client<HttpConnector>, creq: __CReq, cres: __CRes| { | ||||||
|         let mut req = Request::builder() |             let uri = format!("http://{}{}", addr, creq.uri); | ||||||
|             .method(creq.method) |             let mut req = Request::builder() | ||||||
|             .uri(uri) |                 .method(creq.method) | ||||||
|             //.headers(creq.headers) |                 .uri(uri) | ||||||
|             .body(creq.body.into()) |                 //.headers(creq.headers) | ||||||
|             .expect("Request::build"); |                 .body(creq.body.into()) | ||||||
|         *req.headers_mut() = creq.headers; |                 .expect("Request::build"); | ||||||
|         let cstatus = cres.status; |             *req.headers_mut() = creq.headers; | ||||||
|         let cheaders = cres.headers; |             let cstatus = cres.status; | ||||||
|         let cbody = cres.body; |             let cheaders = cres.headers; | ||||||
|  |             let cbody = cres.body; | ||||||
|  |  | ||||||
|         async move { |             client | ||||||
|             let stream = TcpStream::connect(addr).await.unwrap(); |                 .request(req) | ||||||
|  |                 .and_then(move |res| { | ||||||
|             let (mut sender, conn) = hyper::client::conn::Builder::new() |                     assert_eq!(res.status(), cstatus, "server status"); | ||||||
|                 .http2_only(http2_only) |                     assert_eq!(res.version(), version, "server version"); | ||||||
|                 .handshake::<TcpStream, Body>(stream) |                     for func in &cheaders { | ||||||
|                 .await |                         func(&res.headers()); | ||||||
|                 .unwrap(); |                     } | ||||||
|  |                     hyper::body::to_bytes(res) | ||||||
|             tokio::task::spawn(async move { |                 }) | ||||||
|                 if let Err(err) = conn.await { |                 .map_ok(move |body| { | ||||||
|                     panic!("{:?}", err); |                     assert_eq!(body.as_ref(), cbody.as_slice(), "server body"); | ||||||
|                 } |                 }) | ||||||
|             }); |                 .map(|res| res.expect("client error")) | ||||||
|  |         }, | ||||||
|             let res = sender.send_request(req).await.unwrap(); |     ); | ||||||
|  |  | ||||||
|             assert_eq!(res.status(), cstatus, "server status"); |  | ||||||
|             assert_eq!(res.version(), version, "server version"); |  | ||||||
|             for func in &cheaders { |  | ||||||
|                 func(&res.headers()); |  | ||||||
|             } |  | ||||||
|  |  | ||||||
|             let body = hyper::body::to_bytes(res).await.unwrap(); |  | ||||||
|  |  | ||||||
|             assert_eq!(body.as_ref(), cbody.as_slice(), "server body"); |  | ||||||
|         } |  | ||||||
|     }); |  | ||||||
|  |  | ||||||
|     let client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = if cfg.parallel { |     let client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = if cfg.parallel { | ||||||
|         let mut client_futures = vec![]; |         let mut client_futures = vec![]; | ||||||
|         for (creq, cres) in cfg.client_msgs { |         for (creq, cres) in cfg.client_msgs { | ||||||
|             client_futures.push(make_request(creq, cres)); |             client_futures.push(make_request(&client, creq, cres)); | ||||||
|         } |         } | ||||||
|  |         drop(client); | ||||||
|         Box::pin(future::join_all(client_futures).map(|_| ())) |         Box::pin(future::join_all(client_futures).map(|_| ())) | ||||||
|     } else { |     } else { | ||||||
|         let mut client_futures: Pin<Box<dyn Future<Output = ()> + Send>> = |         let mut client_futures: Pin<Box<dyn Future<Output = Client<HttpConnector>> + Send>> = | ||||||
|             Box::pin(future::ready(())); |             Box::pin(future::ready(client)); | ||||||
|         for (creq, cres) in cfg.client_msgs { |         for (creq, cres) in cfg.client_msgs { | ||||||
|             let mk_request = make_request.clone(); |             let mk_request = make_request.clone(); | ||||||
|             client_futures = Box::pin(client_futures.then(move |_| mk_request(creq, cres))); |             client_futures = Box::pin(client_futures.then(move |client| { | ||||||
|  |                 let fut = mk_request(&client, creq, cres); | ||||||
|  |                 fut.map(move |()| client) | ||||||
|  |             })); | ||||||
|         } |         } | ||||||
|         Box::pin(client_futures.map(|_| ())) |         Box::pin(client_futures.map(|_| ())) | ||||||
|     }; |     }; | ||||||
| @@ -466,75 +449,27 @@ struct ProxyConfig { | |||||||
|     version: usize, |     version: usize, | ||||||
| } | } | ||||||
|  |  | ||||||
| async fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) { | fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future<Output = ()>) { | ||||||
|  |     let client = Client::builder() | ||||||
|  |         .http2_only(cfg.version == 2) | ||||||
|  |         .build_http::<Body>(); | ||||||
|  |  | ||||||
|     let dst_addr = cfg.dst; |     let dst_addr = cfg.dst; | ||||||
|     let max_connections = cfg.connections; |     let max_connections = cfg.connections; | ||||||
|     let counter = AtomicUsize::new(0); |     let counter = AtomicUsize::new(0); | ||||||
|     let http2_only = cfg.version == 2; |  | ||||||
|  |  | ||||||
|     let listener = TcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) |     let srv = Server::bind(&([127, 0, 0, 1], 0).into()).serve(make_service_fn(move |_| { | ||||||
|         .await |         let prev = counter.fetch_add(1, Ordering::Relaxed); | ||||||
|         .unwrap(); |         assert!(max_connections > prev, "proxy max connections"); | ||||||
|  |         let client = client.clone(); | ||||||
|     let proxy_addr = listener.local_addr().unwrap(); |         future::ok::<_, hyper::Error>(service_fn(move |mut req| { | ||||||
|  |             let uri = format!("http://{}{}", dst_addr, req.uri().path()) | ||||||
|     let fut = async move { |                 .parse() | ||||||
|         tokio::task::spawn(async move { |                 .expect("proxy new uri parse"); | ||||||
|             let prev = counter.fetch_add(1, Ordering::Relaxed); |             *req.uri_mut() = uri; | ||||||
|             assert!(max_connections > prev, "proxy max connections"); |             client.request(req) | ||||||
|  |         })) | ||||||
|             loop { |     })); | ||||||
|                 let (stream, _) = listener.accept().await.unwrap(); |     let proxy_addr = srv.local_addr(); | ||||||
|  |     (proxy_addr, srv.map(|res| res.expect("proxy error"))) | ||||||
|                 let service = service_fn(move |mut req| { |  | ||||||
|                     async move { |  | ||||||
|                         let uri = format!("http://{}{}", dst_addr, req.uri().path()) |  | ||||||
|                             .parse() |  | ||||||
|                             .expect("proxy new uri parse"); |  | ||||||
|                         *req.uri_mut() = uri; |  | ||||||
|  |  | ||||||
|                         // Make the client request |  | ||||||
|                         let uri = req.uri().host().expect("uri has no host"); |  | ||||||
|                         let port = req.uri().port_u16().expect("uri has no port"); |  | ||||||
|  |  | ||||||
|                         let stream = TcpStream::connect(format!("{}:{}", uri, port)) |  | ||||||
|                             .await |  | ||||||
|                             .unwrap(); |  | ||||||
|  |  | ||||||
|                         let mut builder = Builder::new(); |  | ||||||
|                         builder.http2_only(http2_only); |  | ||||||
|                         let (mut sender, conn) = builder.handshake(stream).await.unwrap(); |  | ||||||
|  |  | ||||||
|                         tokio::task::spawn(async move { |  | ||||||
|                             if let Err(err) = conn.await { |  | ||||||
|                                 panic!("{:?}", err); |  | ||||||
|                             } |  | ||||||
|                         }); |  | ||||||
|  |  | ||||||
|                         let resp = sender.send_request(req).await?; |  | ||||||
|  |  | ||||||
|                         let (mut parts, body) = resp.into_parts(); |  | ||||||
|  |  | ||||||
|                         // Remove the Connection header for HTTP/1.1 proxy connections. |  | ||||||
|                         if !http2_only { |  | ||||||
|                             parts.headers.remove("Connection"); |  | ||||||
|                         } |  | ||||||
|  |  | ||||||
|                         let mut builder = Response::builder().status(parts.status); |  | ||||||
|                         *builder.headers_mut().unwrap() = parts.headers; |  | ||||||
|  |  | ||||||
|                         Result::<Response<Body>, hyper::Error>::Ok(builder.body(body).unwrap()) |  | ||||||
|                     } |  | ||||||
|                 }); |  | ||||||
|  |  | ||||||
|                 Http::new() |  | ||||||
|                     .http2_only(http2_only) |  | ||||||
|                     .serve_connection(stream, service) |  | ||||||
|                     .await |  | ||||||
|                     .unwrap(); |  | ||||||
|             } |  | ||||||
|         }); |  | ||||||
|     }; |  | ||||||
|  |  | ||||||
|     (proxy_addr, fut) |  | ||||||
| } | } | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user