diff --git a/.github/workflows/CI.yml b/.github/workflows/CI.yml index a32bce7c38..91bdd675c2 100644 --- a/.github/workflows/CI.yml +++ b/.github/workflows/CI.yml @@ -30,21 +30,19 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal - toolchain: stable - override: true components: rustfmt - name: cargo fmt --check - uses: actions-rs/cargo@v1 - with: - command: fmt - args: --all -- --check + run: | + if ! rustfmt --check --edition 2018 $(git ls-files '*.rs'); then + printf "Please run \`rustfmt --edition 2018 \$(git ls-files '*.rs')\` to fix rustfmt errors.\nSee CONTRIBUTING.md for more details.\n" >&2 + exit 1 + fi test: name: Test ${{ matrix.rust }} on ${{ matrix.os }} @@ -74,27 +72,21 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust (${{ matrix.rust }}) - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true + + - uses: Swatinem/rust-cache@v2 - name: Test - uses: actions-rs/cargo@v1 - with: - command: test - args: ${{ matrix.features }} + run: cargo test ${{ matrix.features }} - name: Test all benches if: matrix.benches - uses: actions-rs/cargo@v1 - with: - command: test - args: --benches ${{ matrix.features }} + run: cargo test --benches ${{ matrix.features }} msrv: name: Check MSRV (${{ matrix.rust }}) @@ -102,7 +94,7 @@ jobs: strategy: matrix: rust: - - 1.56 # keep in sync with MSRV.md dev doc + - 1.63 # keep in sync with MSRV.md dev doc os: - ubuntu-latest @@ -111,20 +103,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust (${{ matrix.rust }}) - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true + + - uses: Swatinem/rust-cache@v2 - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --features full + run: cargo check --features full miri: name: Test with Miri @@ -133,15 +122,12 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@nightly with: - profile: minimal - toolchain: nightly components: miri - override: true - name: Test # Can't enable tcp feature since Miri does not support the tokio runtime @@ -153,20 +139,18 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@stable - name: Install cargo-hack - run: cargo install cargo-hack + uses: taiki-e/install-action@cargo-hack + + - uses: Swatinem/rust-cache@v2 - name: check --feature-powerset - run: cargo hack check --feature-powerset --depth 2 --skip ffi -Z avoid-dev-deps + run: cargo hack --no-dev-deps check --feature-powerset --depth 2 --skip ffi deprecated: name: Check deprecated on ${{ matrix.rust }} @@ -181,20 +165,17 @@ jobs: steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust (${{ matrix.rust }}) - uses: actions-rs/toolchain@v1 + uses: dtolnay/rust-toolchain@stable with: - profile: minimal toolchain: ${{ matrix.rust }} - override: true + + - uses: Swatinem/rust-cache@v2 - name: Check - uses: actions-rs/cargo@v1 - with: - command: check - args: --features full,backports,deprecated + run: cargo check --features full,backports,deprecated ffi: name: Test C API (FFI) @@ -202,69 +183,57 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@stable - name: Install cbindgen - uses: actions-rs/cargo@v1 + uses: taiki-e/cache-cargo-install-action@v1 with: - command: install - args: cbindgen + tool: cbindgen + + - uses: Swatinem/rust-cache@v2 - name: Build FFI - uses: actions-rs/cargo@v1 + run: cargo rustc --features client,http1,http2,ffi --crate-type cdylib env: RUSTFLAGS: --cfg hyper_unstable_ffi - with: - command: rustc - args: --features client,http1,http2,ffi -Z unstable-options --crate-type cdylib - name: Make Examples run: cd capi/examples && make client - name: Run FFI unit tests - uses: actions-rs/cargo@v1 + run: cargo test --features full,ffi --lib env: RUSTFLAGS: --cfg hyper_unstable_ffi - with: - command: test - args: --features full,ffi --lib ffi-header: name: Verify hyper.h is up to date runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - default: true - override: true - components: cargo + uses: dtolnay/rust-toolchain@stable - name: Install cbindgen - uses: actions-rs/cargo@v1 + uses: taiki-e/cache-cargo-install-action@v1 + with: + tool: cbindgen + + - name: Install cargo-expand + uses: taiki-e/cache-cargo-install-action@v1 with: - command: install - args: cbindgen + tool: cargo-expand + + - uses: Swatinem/rust-cache@v2 - name: Build FFI - uses: actions-rs/cargo@v1 + run: cargo build --features client,http1,http2,ffi env: RUSTFLAGS: --cfg hyper_unstable_ffi - with: - command: build - args: --features client,http1,http2,ffi - name: Ensure that hyper.h is up to date run: ./capi/gen_header.sh --verify @@ -275,17 +244,10 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v1 + uses: actions/checkout@v4 - name: Install Rust - uses: actions-rs/toolchain@v1 - with: - profile: minimal - toolchain: nightly - override: true + uses: dtolnay/rust-toolchain@nightly - name: cargo doc - uses: actions-rs/cargo@v1 - with: - command: rustdoc - args: --features full,ffi -- --cfg docsrs --cfg hyper_unstable_ffi -D broken-intra-doc-links + run: cargo rustdoc --features full,ffi -- --cfg docsrs --cfg hyper_unstable_ffi -D broken-intra-doc-links diff --git a/CHANGELOG.md b/CHANGELOG.md index 31756b3901..43b4ad3dd2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,38 @@ +### v0.14.29 (2024-06-03) + + +#### Bug Fixes + +* **http1:** start header read timeout immediately (#3305) ([b5c2592f](https://github.com/hyperium/hyper/commit/b5c2592fde5e20d29c69428c85aef3d682ee36bc)) + + +#### Features + +* **http2:** add config for `max_local_error_reset_streams` in server (#3528) ([dedcb674](https://github.com/hyperium/hyper/commit/dedcb674f35eaec765a42b550caabe6f694d86d1)) + + +### v0.14.28 (2023-12-18) + + +#### Bug Fixes + +* **client:** + * panic when pool idle timeout set to zero (#3365) ([34d38008](https://github.com/hyperium/hyper/commit/34d38008499de37d9b5b65440b3123ccd05c7510)) + * divide by zero error when DNS returns no addrs (#3355) ([41eaf204](https://github.com/hyperium/hyper/commit/41eaf2042b8169d3dd067d49cfdbdaaf36678903)) + * Do not strip `path` and `scheme` components from URIs for HTTP/2 Extended CONNEC ([45aa6249](https://github.com/hyperium/hyper/commit/45aa62494127066c63c987a57cc5eae2c5361886)) + * early respond from server shouldn't propagate reset error (#3274) ([aac6760e](https://github.com/hyperium/hyper/commit/aac6760e032050dd47f5dbd32f852bf1ede9312b), closes [#2872](https://github.com/hyperium/hyper/issues/2872)) +* **http1:** + * add internal limit for chunked extensions (#3495) ([344a8782](https://github.com/hyperium/hyper/commit/344a87822951a46d252843ccc0b48e62988fc85b)) + * reject chunked headers missing a digit (#3494) ([5eca028f](https://github.com/hyperium/hyper/commit/5eca028f4142e3e73f6d6188a4076f4db292b252)) + + +#### Features + +* **body:** deprecate to_bytes() and aggregate() (#3466) ([7f382ad6](https://github.com/hyperium/hyper/commit/7f382ad64326e1470912feb310d348fd79099c44)) +* **client:** add `conn::http1::Connection::without_shutdown()` method (#3431) ([ad504977](https://github.com/hyperium/hyper/commit/ad504977b520a9582e5516a08b2f1028ef1b5e45)) +* **server:** add `Builder::local_addr()` (#3278) ([d342c2c7](https://github.com/hyperium/hyper/commit/d342c2c714498d33891fa285a3c9ae991dc34769)) + + ### v0.14.27 (2023-06-26) diff --git a/Cargo.toml b/Cargo.toml index f7696c3e19..85865fea38 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "hyper" -version = "0.14.27" +version = "0.14.29" description = "A fast and correct HTTP library." readme = "README.md" homepage = "https://hyper.rs" @@ -28,7 +28,7 @@ http = "0.2" http-body = "0.4" httpdate = "1.0" httparse = "1.8" -h2 = { version = "0.3.17", optional = true } +h2 = { version = "0.3.24", optional = true } itoa = "1" tracing = { version = "0.1", default-features = false, features = ["std"] } pin-project-lite = "0.2.4" @@ -39,7 +39,7 @@ want = "0.3" # Optional libc = { version = "0.2", optional = true } -socket2 = { version = "0.4.7", optional = true, features = ["all"] } +socket2 = { version = ">=0.4.7, <0.6.0", optional = true, features = ["all"] } [dev-dependencies] futures-util = { version = "0.3", default-features = false, features = ["alloc"] } @@ -197,7 +197,7 @@ required-features = ["full"] [[example]] name = "tower_client" path = "examples/tower_client.rs" -required-features = ["full"] +required-features = ["full", "backports"] [[example]] name = "tower_server" diff --git a/capi/gen_header.sh b/capi/gen_header.sh index d0b9c13a32..7a08d3e6ff 100755 --- a/capi/gen_header.sh +++ b/capi/gen_header.sh @@ -6,101 +6,44 @@ set -e CAPI_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )" - -WORK_DIR=$(mktemp -d) - -# check if tmp dir was created -if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then - echo "Could not create temp dir" - exit 1 -fi - header_file_backup="$CAPI_DIR/include/hyper.h.backup" function cleanup { - rm -rf "$WORK_DIR" + rm -rf "$WORK_DIR" || true rm "$header_file_backup" || true } trap cleanup EXIT -mkdir "$WORK_DIR/src" - -# Fake a library -cat > "$WORK_DIR/src/lib.rs" << EOF -#[path = "$CAPI_DIR/../src/ffi/mod.rs"] -pub mod ffi; -EOF - -# And its Cargo.toml -cat > "$WORK_DIR/Cargo.toml" << EOF -[package] -name = "hyper" -version = "0.0.0" -edition = "2018" -publish = false - -[dependencies] -# Determined which dependencies we need by running the "cargo rustc" command -# below and watching the compile error output for references to unknown imports, -# until we didn't get any errors. -bytes = "1" -futures-channel = "0.3" -futures-util = { version = "0.3", default-features = false, features = ["alloc"] } -libc = { version = "0.2", optional = true } -http = "0.2" -http-body = "0.4" -tokio = { version = "1", features = ["rt"] } - -[features] -default = [ - "client", - "ffi", - "http1", -] +WORK_DIR=$(mktemp -d) -http1 = [] -client = [] -ffi = ["libc", "tokio/rt"] -EOF +# check if tmp dir was created +if [[ ! "$WORK_DIR" || ! -d "$WORK_DIR" ]]; then + echo "Could not create temp dir" + exit 1 +fi cp "$CAPI_DIR/include/hyper.h" "$header_file_backup" -#cargo metadata --no-default-features --features ffi --format-version 1 > "$WORK_DIR/metadata.json" - -cd "${WORK_DIR}" || exit 2 - # Expand just the ffi module -if ! output=$(RUSTFLAGS='--cfg hyper_unstable_ffi' cargo rustc -- -Z unpretty=expanded 2>&1 > expanded.rs); then - # As of April 2021 the script above prints a lot of warnings/errors, and - # exits with a nonzero return code, but hyper.h still gets generated. - # - # However, on Github Actions, this will result in automatic "annotations" - # being added to files not related to a PR, so if this is `--verify` mode, - # then don't show it. - # - # But yes show it when using it locally. - if [[ "--verify" != "$1" ]]; then - echo "$output" - fi +if ! RUSTFLAGS='--cfg hyper_unstable_ffi' cargo expand --features client,http1,http2,ffi ::ffi 2> $WORK_DIR/expand_stderr.err > $WORK_DIR/expanded.rs; then + cat $WORK_DIR/expand_stderr.err fi -# Replace the previous copy with the single expanded file -rm -rf ./src -mkdir src -mv expanded.rs src/lib.rs - - # Bindgen! if ! cbindgen \ --config "$CAPI_DIR/cbindgen.toml" \ --lockfile "$CAPI_DIR/../Cargo.lock" \ --output "$CAPI_DIR/include/hyper.h" \ - "${@}"; then + "${@}"\ + $WORK_DIR/expanded.rs 2> $WORK_DIR/cbindgen_stderr.err; then bindgen_exit_code=$? if [[ "--verify" == "$1" ]]; then - echo "diff generated (<) vs backup (>)" - diff "$CAPI_DIR/include/hyper.h" "$header_file_backup" + echo "Changes from previous header (old < > new)" + diff -u "$header_file_backup" "$CAPI_DIR/include/hyper.h" + else + echo "cbindgen failed:" + cat $WORK_DIR/cbindgen_stderr.err fi exit $bindgen_exit_code fi diff --git a/docs/MSRV.md b/docs/MSRV.md index 65127c99bd..70752c9138 100644 --- a/docs/MSRV.md +++ b/docs/MSRV.md @@ -6,4 +6,4 @@ hyper. It is possible that an older compiler can work, but that is not guaranteed. We try to increase the MSRV responsibly, only when a significant new feature is needed. -The current MSRV is: **1.56**. +The current MSRV is: **1.63**. diff --git a/examples/client_json.rs b/examples/client_json.rs index ef92f14b10..04ca6f7d91 100644 --- a/examples/client_json.rs +++ b/examples/client_json.rs @@ -28,6 +28,7 @@ async fn fetch_json(url: hyper::Uri) -> Result> { let res = client.get(url).await?; // asynchronously aggregate the chunks of the body + #[allow(deprecated)] let body = hyper::body::aggregate(res).await?; // try to parse as json with serde_json diff --git a/examples/echo.rs b/examples/echo.rs index ff7573049e..ff13085004 100644 --- a/examples/echo.rs +++ b/examples/echo.rs @@ -34,6 +34,7 @@ async fn echo(req: Request) -> Result, hyper::Error> { // So here we do `.await` on the future, waiting on concatenating the full body, // then afterwards the content can be reversed. Only then can we return a `Response`. (&Method::POST, "/echo/reversed") => { + #[allow(deprecated)] let whole_body = hyper::body::to_bytes(req.into_body()).await?; let reversed_body = whole_body.iter().rev().cloned().collect::>(); diff --git a/examples/params.rs b/examples/params.rs index 87c2368928..8c9d923788 100644 --- a/examples/params.rs +++ b/examples/params.rs @@ -17,6 +17,7 @@ async fn param_example(req: Request) -> Result, hyper::Erro (&Method::GET, "/") | (&Method::GET, "/post") => Ok(Response::new(INDEX.into())), (&Method::POST, "/post") => { // Concatenate the body... + #[allow(deprecated)] let b = hyper::body::to_bytes(req).await?; // Parse the request body. form_urlencoded::parse // always succeeds, but in general parsing may diff --git a/examples/web_api.rs b/examples/web_api.rs index 5226249b35..6e6fea311a 100644 --- a/examples/web_api.rs +++ b/examples/web_api.rs @@ -40,6 +40,7 @@ async fn client_request_response(client: &Client) -> Result) -> Result> { // Aggregate the body... + #[allow(deprecated)] let whole_body = hyper::body::aggregate(req).await?; // Decode as JSON... let mut data: serde_json::Value = serde_json::from_reader(whole_body.reader())?; diff --git a/src/body/aggregate.rs b/src/body/aggregate.rs index 99662419d3..4bce1767ff 100644 --- a/src/body/aggregate.rs +++ b/src/body/aggregate.rs @@ -13,6 +13,13 @@ use crate::common::buf::BufList; /// Care needs to be taken if the remote is untrusted. The function doesn't implement any length /// checks and an malicious peer might make it consume arbitrary amounts of memory. Checking the /// `Content-Length` is a possibility, but it is not strictly mandated to be present. +#[cfg_attr( + feature = "deprecated", + deprecated( + note = "This function has been replaced by a method on the `hyper::body::HttpBody` trait. Use `.collect().await?.aggregate()` instead." + ) +)] +#[cfg_attr(feature = "deprecated", allow(deprecated))] pub async fn aggregate(body: T) -> Result where T: HttpBody, diff --git a/src/body/body.rs b/src/body/body.rs index 111867a5ed..7df87404f6 100644 --- a/src/body/body.rs +++ b/src/body/body.rs @@ -1,7 +1,12 @@ use std::borrow::Cow; +#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] +use std::convert::Infallible; #[cfg(feature = "stream")] use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; use bytes::Bytes; use futures_channel::mpsc; @@ -15,10 +20,7 @@ use http_body::{Body as HttpBody, SizeHint}; use super::DecodedLength; #[cfg(feature = "stream")] use crate::common::sync_wrapper::SyncWrapper; -use crate::common::Future; -#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] -use crate::common::Never; -use crate::common::{task, watch, Pin, Poll}; +use crate::common::watch; #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] use crate::proto::h2::ping; @@ -77,7 +79,7 @@ struct Extra { } #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] -type DelayEofUntil = oneshot::Receiver; +type DelayEofUntil = oneshot::Receiver; enum DelayEof { /// Initial state, stream hasn't seen EOF yet. @@ -239,7 +241,7 @@ impl Body { .get_or_insert_with(|| Box::new(Extra { delayed_eof: None })) } - fn poll_eof(&mut self, cx: &mut task::Context<'_>) -> Poll>> { + fn poll_eof(&mut self, cx: &mut Context<'_>) -> Poll>> { match self.take_delayed_eof() { #[cfg(any(feature = "http1", feature = "http2"))] #[cfg(feature = "client")] @@ -292,7 +294,7 @@ impl Body { } } - fn poll_inner(&mut self, cx: &mut task::Context<'_>) -> Poll>> { + fn poll_inner(&mut self, cx: &mut Context<'_>) -> Poll>> { match self.kind { Kind::Once(ref mut val) => Poll::Ready(val.take().map(Ok)), Kind::Chan { @@ -323,7 +325,12 @@ impl Body { ping.record_data(bytes.len()); Poll::Ready(Some(Ok(bytes))) } - Some(Err(e)) => Poll::Ready(Some(Err(crate::Error::new_body(e)))), + Some(Err(e)) => match e.reason() { + // These reasons should cause stop of body reading, but nor fail it. + // The same logic as for `AsyncRead for H2Upgraded` is applied here. + Some(h2::Reason::NO_ERROR) | Some(h2::Reason::CANCEL) => Poll::Ready(None), + _ => Poll::Ready(Some(Err(crate::Error::new_body(e)))), + }, None => Poll::Ready(None), }, @@ -362,14 +369,14 @@ impl HttpBody for Body { fn poll_data( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>> { self.poll_eof(cx) } fn poll_trailers( #[cfg_attr(not(feature = "http2"), allow(unused_mut))] mut self: Pin<&mut Self>, - #[cfg_attr(not(feature = "http2"), allow(unused))] cx: &mut task::Context<'_>, + #[cfg_attr(not(feature = "http2"), allow(unused))] cx: &mut Context<'_>, ) -> Poll, Self::Error>> { match self.kind { #[cfg(all(feature = "http2", any(feature = "client", feature = "server")))] @@ -465,7 +472,7 @@ impl fmt::Debug for Body { impl Stream for Body { type Item = crate::Result; - fn poll_next(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_next(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { HttpBody::poll_data(self, cx) } } @@ -545,7 +552,7 @@ impl From> for Body { impl Sender { /// Check to see if this `Sender` can send more data. - pub fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { // Check if the receiver end has tried polling for the body yet ready!(self.poll_want(cx)?); self.data_tx @@ -553,7 +560,7 @@ impl Sender { .map_err(|_| crate::Error::new_closed()) } - fn poll_want(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_want(&mut self, cx: &mut Context<'_>) -> Poll> { match self.want_rx.load(cx) { WANT_READY => Poll::Ready(Ok(())), WANT_PENDING => Poll::Pending, diff --git a/src/body/mod.rs b/src/body/mod.rs index 5e2181e941..109b1e6b72 100644 --- a/src/body/mod.rs +++ b/src/body/mod.rs @@ -19,9 +19,11 @@ pub use bytes::{Buf, Bytes}; pub use http_body::Body as HttpBody; pub use http_body::SizeHint; +#[cfg_attr(feature = "deprecated", allow(deprecated))] pub use self::aggregate::aggregate; pub use self::body::{Body, Sender}; pub(crate) use self::length::DecodedLength; +#[cfg_attr(feature = "deprecated", allow(deprecated))] pub use self::to_bytes::to_bytes; mod aggregate; diff --git a/src/body/to_bytes.rs b/src/body/to_bytes.rs index 038c6fd0f3..2e398d250a 100644 --- a/src/body/to_bytes.rs +++ b/src/body/to_bytes.rs @@ -44,6 +44,13 @@ use super::HttpBody; /// # Ok(()) /// # } /// ``` +#[cfg_attr( + feature = "deprecated", + deprecated( + note = "This function has been replaced by a method on the `hyper::body::HttpBody` trait. Use `.collect().await?.to_bytes()` instead." + ) +)] +#[cfg_attr(feature = "deprecated", allow(deprecated))] pub async fn to_bytes(body: T) -> Result where T: HttpBody, diff --git a/src/client/client.rs b/src/client/client.rs index bf4db79fde..8195554bd7 100644 --- a/src/client/client.rs +++ b/src/client/client.rs @@ -1,6 +1,10 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::marker::Unpin; use std::mem; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; use futures_channel::oneshot; @@ -12,10 +16,9 @@ use tracing::{debug, trace, warn}; use crate::body::{Body, HttpBody}; use crate::client::connect::CaptureConnectionExtension; -use crate::common::{ - exec::BoxSendFuture, lazy as hyper_lazy, sync_wrapper::SyncWrapper, task, Future, Lazy, Pin, - Poll, -}; +use crate::common::{exec::BoxSendFuture, lazy as hyper_lazy, sync_wrapper::SyncWrapper, Lazy}; +#[cfg(feature = "http2")] +use crate::ext::Protocol; use crate::rt::Executor; use super::conn; @@ -251,7 +254,8 @@ where if req.version() == Version::HTTP_2 { warn!("Connection is HTTP/1, but request requires HTTP/2"); return Err(ClientError::Normal( - crate::Error::new_user_unsupported_version().with_client_connect_info(pooled.conn_info.clone()), + crate::Error::new_user_unsupported_version() + .with_client_connect_info(pooled.conn_info.clone()), )); } @@ -278,7 +282,13 @@ where origin_form(req.uri_mut()); } } else if req.method() == Method::CONNECT { + #[cfg(not(feature = "http2"))] authority_form(req.uri_mut()); + + #[cfg(feature = "http2")] + if req.extensions().get::().is_none() { + authority_form(req.uri_mut()); + } } let mut res = match pooled.send_request_retryable(req).await { @@ -544,7 +554,7 @@ where type Error = crate::Error; type Future = ResponseFuture; - fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } @@ -564,7 +574,7 @@ where type Error = crate::Error; type Future = ResponseFuture; - fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, _: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } @@ -598,7 +608,7 @@ impl ResponseFuture { F: Future>> + Send + 'static, { Self { - inner: SyncWrapper::new(Box::pin(value)) + inner: SyncWrapper::new(Box::pin(value)), } } @@ -619,7 +629,7 @@ impl fmt::Debug for ResponseFuture { impl Future for ResponseFuture { type Output = crate::Result>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.inner.get_mut().as_mut().poll(cx) } } @@ -641,7 +651,7 @@ enum PoolTx { } impl PoolClient { - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self.tx { PoolTx::Http1(ref mut tx) => tx.poll_ready(cx), #[cfg(feature = "http2")] @@ -703,7 +713,10 @@ where { fn is_open(&self) -> bool { if self.conn_info.poisoned.poisoned() { - trace!("marking {:?} as closed because it was poisoned", self.conn_info); + trace!( + "marking {:?} as closed because it was poisoned", + self.conn_info + ); return false; } match self.tx { @@ -1106,10 +1119,7 @@ impl Builder { /// line in the input to resume parsing the rest of the headers. An error /// will be emitted nonetheless if it finds `\0` or a lone `\r` while /// looking for the next line. - pub fn http1_ignore_invalid_headers_in_responses( - &mut self, - val: bool, - ) -> &mut Builder { + pub fn http1_ignore_invalid_headers_in_responses(&mut self, val: bool) -> &mut Builder { self.conn_builder .http1_ignore_invalid_headers_in_responses(val); self diff --git a/src/client/conn.rs b/src/client/conn.rs index 88e2c413a7..8da457da64 100644 --- a/src/client/conn.rs +++ b/src/client/conn.rs @@ -59,11 +59,17 @@ pub mod http1; #[cfg(all(feature = "backports", feature = "http2"))] pub mod http2; +#[cfg(not(all(feature = "http1", feature = "http2")))] +use std::convert::Infallible; use std::error::Error as StdError; use std::fmt; +use std::future::Future; #[cfg(not(all(feature = "http1", feature = "http2")))] use std::marker::PhantomData; +use std::marker::Unpin; +use std::pin::Pin; use std::sync::Arc; +use std::task::{Context, Poll}; #[cfg(all(feature = "runtime", feature = "http2"))] use std::time::Duration; @@ -77,12 +83,7 @@ use tracing::{debug, trace}; use super::dispatch; use crate::body::HttpBody; -#[cfg(not(all(feature = "http1", feature = "http2")))] -use crate::common::Never; -use crate::common::{ - exec::{BoxSendFuture, Exec}, - task, Future, Pin, Poll, -}; +use crate::common::exec::{BoxSendFuture, Exec}; use crate::proto; use crate::rt::Executor; #[cfg(feature = "http1")] @@ -94,13 +95,13 @@ type Http1Dispatcher = proto::dispatch::Dispatcher, B, T, proto::h1::ClientTransaction>; #[cfg(not(feature = "http1"))] -type Http1Dispatcher = (Never, PhantomData<(T, Pin>)>); +type Http1Dispatcher = (Infallible, PhantomData<(T, Pin>)>); #[cfg(feature = "http2")] type Http2ClientTask = proto::h2::ClientTask; #[cfg(not(feature = "http2"))] -type Http2ClientTask = (Never, PhantomData>>); +type Http2ClientTask = (Infallible, PhantomData>>); pin_project! { #[project = ProtoClientProj] @@ -257,7 +258,7 @@ impl SendRequest { /// 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> { + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.dispatch.poll_ready(cx) } @@ -381,7 +382,7 @@ where type Error = crate::Error; type Future = ResponseFuture; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_ready(cx) } @@ -502,7 +503,7 @@ where /// Use [`poll_fn`](https://docs.rs/futures/0.1.25/futures/future/fn.poll_fn.html) /// and [`try_ready!`](https://docs.rs/futures/0.1.25/futures/macro.try_ready.html) /// to work with this function; or use the `without_shutdown` wrapper. - pub fn poll_without_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub fn poll_without_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { match *self.inner.as_mut().expect("already upgraded") { #[cfg(feature = "http1")] ProtoClient::H1 { ref mut h1 } => h1.poll_without_shutdown(cx), @@ -554,7 +555,7 @@ where { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match ready!(Pin::new(self.inner.as_mut().unwrap()).poll(cx))? { proto::Dispatched::Shutdown => Poll::Ready(Ok(())), #[cfg(feature = "http1")] @@ -710,10 +711,7 @@ impl Builder { /// Note that this setting does not affect HTTP/2. /// /// Default is false. - pub fn http1_ignore_invalid_headers_in_responses( - &mut self, - enabled: bool, - ) -> &mut Builder { + pub fn http1_ignore_invalid_headers_in_responses(&mut self, enabled: bool) -> &mut Builder { self.h1_parser_config .ignore_invalid_headers_in_responses(enabled); self @@ -1070,7 +1068,7 @@ impl Builder { impl Future for ResponseFuture { type Output = crate::Result>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.inner { ResponseFutureState::Waiting(ref mut rx) => { Pin::new(rx).poll(cx).map(|res| match res { @@ -1104,7 +1102,7 @@ where { type Output = crate::Result; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { #[cfg(feature = "http1")] ProtoClientProj::H1 { h1 } => h1.poll(cx), diff --git a/src/client/conn/http1.rs b/src/client/conn/http1.rs index d8936d8655..37eda04067 100644 --- a/src/client/conn/http1.rs +++ b/src/client/conn/http1.rs @@ -2,17 +2,18 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use bytes::Bytes; use http::{Request, Response}; use httparse::ParserConfig; use tokio::io::{AsyncRead, AsyncWrite}; -use crate::body::{Body as IncomingBody, HttpBody as Body}; use super::super::dispatch; -use crate::common::{ - task, Future, Pin, Poll, -}; +use crate::body::{Body as IncomingBody, HttpBody as Body}; use crate::proto; use crate::upgrade::Upgraded; @@ -44,7 +45,6 @@ pub struct Parts { _inner: (), } - /// 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 @@ -87,8 +87,21 @@ where /// Use [`poll_fn`](https://docs.rs/futures/0.1.25/futures/future/fn.poll_fn.html) /// and [`try_ready!`](https://docs.rs/futures/0.1.25/futures/macro.try_ready.html) /// to work with this function; or use the `without_shutdown` wrapper. - pub fn poll_without_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> { - self.inner.as_mut().expect("algready upgraded").poll_without_shutdown(cx) + pub fn poll_without_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { + self.inner + .as_mut() + .expect("algready upgraded") + .poll_without_shutdown(cx) + } + + /// Prevent shutdown of the underlying IO object at the end of service the request, + /// instead run `into_parts`. This is a convenience wrapper over `poll_without_shutdown`. + pub fn without_shutdown(self) -> impl Future>> { + let mut conn = Some(self); + futures_util::future::poll_fn(move |cx| -> Poll>> { + ready!(conn.as_mut().unwrap().poll_without_shutdown(cx))?; + Poll::Ready(Ok(conn.take().unwrap().into_parts())) + }) } } @@ -112,9 +125,7 @@ pub struct Builder { /// /// This is a shortcut for `Builder::new().handshake(io)`. /// See [`client::conn`](crate::client::conn) for more. -pub async fn handshake( - io: T, -) -> crate::Result<(SendRequest, Connection)> +pub async fn handshake(io: T) -> crate::Result<(SendRequest, Connection)> where T: AsyncRead + AsyncWrite + Unpin + Send + 'static, B: Body + 'static, @@ -130,7 +141,7 @@ impl SendRequest { /// 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> { + pub fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.dispatch.poll_ready(cx) } @@ -260,7 +271,7 @@ where { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { 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() { @@ -324,10 +335,7 @@ impl Builder { /// Default is false. /// /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4 - pub fn allow_spaces_after_header_name_in_responses( - &mut self, - enabled: bool, - ) -> &mut Builder { + pub fn 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 @@ -365,10 +373,7 @@ impl Builder { /// Default is false. /// /// [RFC 7230 Section 3.2.4.]: https://tools.ietf.org/html/rfc7230#section-3.2.4 - pub fn allow_obsolete_multiline_headers_in_responses( - &mut self, - enabled: bool, - ) -> &mut Builder { + pub fn allow_obsolete_multiline_headers_in_responses(&mut self, enabled: bool) -> &mut Builder { self.h1_parser_config .allow_obsolete_multiline_headers_in_responses(enabled); self @@ -381,10 +386,7 @@ impl Builder { /// and no error will be reported. /// /// Default is false. - pub fn ignore_invalid_headers_in_responses( - &mut self, - enabled: bool, - ) -> &mut Builder { + pub fn ignore_invalid_headers_in_responses(&mut self, enabled: bool) -> &mut Builder { self.h1_parser_config .ignore_invalid_headers_in_responses(enabled); self diff --git a/src/client/conn/http2.rs b/src/client/conn/http2.rs index fd0adf897b..5697e9ee47 100644 --- a/src/client/conn/http2.rs +++ b/src/client/conn/http2.rs @@ -2,19 +2,20 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::marker::Unpin; +use std::pin::Pin; use std::sync::Arc; +use std::task::{Context, Poll}; use std::time::Duration; use http::{Request, Response}; use tokio::io::{AsyncRead, AsyncWrite}; use super::super::dispatch; -use crate::body::{HttpBody as Body, Body as IncomingBody}; -use crate::common::{ - exec::{BoxSendFuture, Exec}, - task, Future, Pin, Poll, -}; +use crate::body::{Body as IncomingBody, HttpBody as Body}; +use crate::common::exec::{BoxSendFuture, Exec}; use crate::proto; use crate::rt::Executor; @@ -25,7 +26,9 @@ pub struct SendRequest { impl Clone for SendRequest { fn clone(&self) -> SendRequest { - SendRequest { dispatch: self.dispatch.clone() } + SendRequest { + dispatch: self.dispatch.clone(), + } } } @@ -55,10 +58,7 @@ pub struct Builder { /// /// This is a shortcut for `Builder::new().handshake(io)`. /// See [`client::conn`](crate::client::conn) for more. -pub async fn handshake( - exec: E, - io: T, -) -> crate::Result<(SendRequest, Connection)> +pub async fn handshake(exec: E, io: T) -> crate::Result<(SendRequest, Connection)> where E: Executor + Send + Sync + 'static, T: AsyncRead + AsyncWrite + Unpin + Send + 'static, @@ -75,7 +75,7 @@ impl SendRequest { /// 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> { + pub fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { if self.is_closed() { Poll::Ready(Err(crate::Error::new_closed())) } else { @@ -230,7 +230,7 @@ where { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match ready!(Pin::new(&mut self.inner.1).poll(cx))? { proto::Dispatched::Shutdown => Poll::Ready(Ok(())), #[cfg(feature = "http1")] @@ -244,7 +244,7 @@ where impl Builder { /// Creates a new connection builder. #[inline] - pub fn new(exec: E) -> Builder + pub fn new(exec: E) -> Builder where E: Executor + Send + Sync + 'static, { @@ -285,10 +285,7 @@ impl Builder { /// Passing `None` will do nothing. /// /// If not set, hyper will use a default. - pub fn initial_connection_window_size( - &mut self, - sz: impl Into>, - ) -> &mut Self { + pub fn initial_connection_window_size(&mut self, sz: impl Into>) -> &mut Self { if let Some(sz) = sz.into() { self.h2_builder.adaptive_window = false; self.h2_builder.initial_conn_window_size = sz; @@ -331,10 +328,7 @@ impl Builder { /// /// Default is currently disabled. #[cfg(feature = "runtime")] - pub fn keep_alive_interval( - &mut self, - interval: impl Into>, - ) -> &mut Self { + pub fn keep_alive_interval(&mut self, interval: impl Into>) -> &mut Self { self.h2_builder.keep_alive_interval = interval.into(); self } @@ -412,8 +406,7 @@ impl Builder { 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?; + let h2 = proto::h2::client::handshake(io, rx, &opts.h2_builder, opts.exec).await?; Ok(( SendRequest { dispatch: tx.unbound(), diff --git a/src/client/connect/dns.rs b/src/client/connect/dns.rs index e4465078b3..50245de68d 100644 --- a/src/client/connect/dns.rs +++ b/src/client/connect/dns.rs @@ -26,7 +26,7 @@ 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::task::{Context, Poll}; use std::{fmt, io, vec}; use tokio::task::JoinHandle; @@ -113,7 +113,7 @@ impl Service for GaiResolver { type Error = io::Error; type Future = GaiFuture; - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } @@ -138,7 +138,7 @@ impl fmt::Debug for GaiResolver { impl Future for GaiFuture { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.inner).poll(cx).map(|res| match res { Ok(Ok(addrs)) => Ok(GaiAddrs { inner: addrs }), Ok(Err(err)) => Err(err), @@ -286,7 +286,7 @@ impl Service for TokioThreadpoolGaiResolver { type Error = io::Error; type Future = TokioThreadpoolGaiFuture; - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } @@ -299,7 +299,7 @@ impl Service for TokioThreadpoolGaiResolver { impl Future for TokioThreadpoolGaiFuture { type Output = Result; - fn poll(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll { match ready!(tokio_executor::threadpool::blocking(|| ( self.name.as_str(), 0 @@ -318,8 +318,10 @@ impl Future for TokioThreadpoolGaiFuture { */ mod sealed { - use super::{SocketAddr, Name}; - use crate::common::{task, Future, Poll}; + use std::future::Future; + use std::task::{Context, Poll}; + + use super::{Name, SocketAddr}; use tower_service::Service; // "Trait alias" for `Service` @@ -328,7 +330,7 @@ mod sealed { type Error: Into>; type Future: Future>; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll>; fn resolve(&mut self, name: Name) -> Self::Future; } @@ -342,7 +344,7 @@ mod sealed { type Error = S::Error; type Future = S::Future; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { Service::poll_ready(self, cx) } diff --git a/src/client/connect/http.rs b/src/client/connect/http.rs index afe7b155eb..ee09afe9a3 100644 --- a/src/client/connect/http.rs +++ b/src/client/connect/http.rs @@ -362,7 +362,10 @@ impl Connection for TcpStream { fn connected(&self) -> Connected { let connected = Connected::new(); if let (Ok(remote_addr), Ok(local_addr)) = (self.peer_addr(), self.local_addr()) { - connected.extra(HttpInfo { remote_addr, local_addr }) + connected.extra(HttpInfo { + remote_addr, + local_addr, + }) } else { connected } @@ -521,7 +524,9 @@ struct ConnectingTcpRemote { impl ConnectingTcpRemote { fn new(addrs: dns::SocketAddrs, connect_timeout: Option) -> Self { - let connect_timeout = connect_timeout.map(|t| t / (addrs.len() as u32)); + let connect_timeout = connect_timeout + .map(|t| t.checked_div(addrs.len() as u32)) + .flatten(); Self { addrs, diff --git a/src/client/connect/mod.rs b/src/client/connect/mod.rs index 4815524811..4c29dd3a3e 100644 --- a/src/client/connect/mod.rs +++ b/src/client/connect/mod.rs @@ -81,8 +81,8 @@ //! [`Connection`]: Connection use std::fmt; use std::fmt::{Debug, Formatter}; -use std::sync::atomic::{AtomicBool, Ordering}; use std::ops::Deref; +use std::sync::atomic::{AtomicBool, Ordering}; use std::sync::Arc; use ::http::Extensions; @@ -129,7 +129,12 @@ pub(crate) struct PoisonPill { impl Debug for PoisonPill { fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result { // print the address of the pill—this makes debugging issues much easier - write!(f, "PoisonPill@{:p} {{ poisoned: {} }}", self.poisoned, self.poisoned.load(Ordering::Relaxed)) + write!( + f, + "PoisonPill@{:p} {{ poisoned: {} }}", + self.poisoned, + self.poisoned.load(Ordering::Relaxed) + ) } } @@ -422,12 +427,13 @@ where #[cfg(any(feature = "http1", feature = "http2"))] pub(super) mod sealed { use std::error::Error as StdError; + use std::future::Future; + use std::marker::Unpin; use ::http::Uri; use tokio::io::{AsyncRead, AsyncWrite}; use super::Connection; - use crate::common::{Future, Unpin}; /// Connect to a destination, returning an IO transport. /// @@ -448,6 +454,7 @@ pub(super) mod sealed { fn connect(self, internal_only: Internal, dst: Uri) -> ::Future; } + #[allow(unreachable_pub)] pub trait ConnectSvc { type Connection: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static; type Error: Into>; diff --git a/src/client/dispatch.rs b/src/client/dispatch.rs index 771c40da30..a1a93ea964 100644 --- a/src/client/dispatch.rs +++ b/src/client/dispatch.rs @@ -1,13 +1,13 @@ #[cfg(feature = "http2")] use std::future::Future; +use std::marker::Unpin; +#[cfg(feature = "http2")] +use std::pin::Pin; +use std::task::{Context, Poll}; use futures_util::FutureExt; use tokio::sync::{mpsc, oneshot}; -#[cfg(feature = "http2")] -use crate::common::Pin; -use crate::common::{task, Poll}; - pub(crate) type RetryPromise = oneshot::Receiver)>>; pub(crate) type Promise = oneshot::Receiver>; @@ -53,7 +53,7 @@ pub(crate) struct UnboundedSender { } impl Sender { - pub(crate) fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub(crate) fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.giver .poll_want(cx) .map_err(|_| crate::Error::new_closed()) @@ -155,10 +155,7 @@ pub(crate) struct Receiver { } impl Receiver { - pub(crate) fn poll_recv( - &mut self, - cx: &mut task::Context<'_>, - ) -> Poll)>> { + pub(crate) fn poll_recv(&mut self, cx: &mut Context<'_>) -> Poll)>> { match self.inner.poll_recv(cx) { Poll::Ready(item) => { Poll::Ready(item.map(|mut env| env.0.take().expect("envelope not dropped"))) @@ -245,7 +242,7 @@ impl Callback { } } - pub(crate) fn poll_canceled(&mut self, cx: &mut task::Context<'_>) -> Poll<()> { + pub(crate) fn poll_canceled(&mut self, cx: &mut Context<'_>) -> Poll<()> { match *self { Callback::Retry(Some(ref mut tx)) => tx.poll_closed(cx), Callback::NoRetry(Some(ref mut tx)) => tx.poll_closed(cx), diff --git a/src/client/pool.rs b/src/client/pool.rs index b9772d688d..1dfd6ba3d3 100644 --- a/src/client/pool.rs +++ b/src/client/pool.rs @@ -1,8 +1,12 @@ use std::collections::{HashMap, HashSet, VecDeque}; use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::marker::Unpin; use std::ops::{Deref, DerefMut}; +use std::pin::Pin; use std::sync::{Arc, Mutex, Weak}; +use std::task::{Context, Poll}; #[cfg(not(feature = "runtime"))] use std::time::{Duration, Instant}; @@ -13,7 +17,7 @@ use tokio::time::{Duration, Instant, Interval}; use tracing::{debug, trace}; use super::client::Ver; -use crate::common::{exec::Exec, task, Future, Pin, Poll, Unpin}; +use crate::common::exec::Exec; // FIXME: allow() required due to `impl Trait` leaking types to this lint #[allow(missing_debug_implementations)] @@ -79,7 +83,7 @@ struct PoolInner { // A oneshot channel is used to allow the interval to be notified when // the Pool completely drops. That way, the interval can cancel immediately. #[cfg(feature = "runtime")] - idle_interval_ref: Option>, + idle_interval_ref: Option>, #[cfg(feature = "runtime")] exec: Exec, timeout: Option, @@ -113,7 +117,7 @@ impl Pool { waiters: HashMap::new(), #[cfg(feature = "runtime")] exec: __exec.clone(), - timeout: config.idle_timeout, + timeout: config.idle_timeout.filter(|&t| t > Duration::ZERO), }))) } else { None @@ -576,10 +580,7 @@ impl fmt::Display for CheckoutIsClosedError { } impl Checkout { - fn poll_waiter( - &mut self, - cx: &mut task::Context<'_>, - ) -> Poll>>> { + fn poll_waiter(&mut self, cx: &mut Context<'_>) -> Poll>>> { if let Some(mut rx) = self.waiter.take() { match Pin::new(&mut rx).poll(cx) { Poll::Ready(Ok(value)) => { @@ -604,7 +605,7 @@ impl Checkout { } } - fn checkout(&mut self, cx: &mut task::Context<'_>) -> Option> { + fn checkout(&mut self, cx: &mut Context<'_>) -> Option> { let entry = { let mut inner = self.pool.inner.as_ref()?.lock().unwrap(); let expiration = Expiration::new(inner.timeout); @@ -657,7 +658,7 @@ impl Checkout { impl Future for Checkout { type Output = crate::Result>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { if let Some(pooled) = ready!(self.poll_waiter(cx)?) { return Poll::Ready(Ok(pooled)); } @@ -740,7 +741,7 @@ pin_project_lite::pin_project! { // Pool is fully dropped, and shutdown. This channel is never sent on, // but Err(Canceled) will be received when the Pool is dropped. #[pin] - pool_drop_notifier: oneshot::Receiver, + pool_drop_notifier: oneshot::Receiver, } } @@ -748,7 +749,7 @@ pin_project_lite::pin_project! { impl Future for IdleTask { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); loop { match this.pool_drop_notifier.as_mut().poll(cx) { @@ -790,11 +791,14 @@ impl WeakOpt { #[cfg(test)] mod tests { + use std::future::Future; + use std::pin::Pin; + use std::task::Context; use std::task::Poll; use std::time::Duration; use super::{Connecting, Key, Pool, Poolable, Reservation, WeakOpt}; - use crate::common::{exec::Exec, task, Future, Pin}; + use crate::common::exec::Exec; /// Test unique reservations. #[derive(Debug, PartialEq, Eq)] @@ -864,7 +868,7 @@ mod tests { { type Output = Option<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match Pin::new(&mut self.0).poll(cx) { Poll::Ready(Ok(_)) => Poll::Ready(Some(())), Poll::Ready(Err(_)) => Poll::Ready(Some(())), diff --git a/src/client/service.rs b/src/client/service.rs index f3560ea088..047dd98766 100644 --- a/src/client/service.rs +++ b/src/client/service.rs @@ -5,6 +5,8 @@ use std::error::Error as StdError; use std::future::Future; use std::marker::PhantomData; +use std::pin::Pin; +use std::task::{Context, Poll}; use tracing::debug; @@ -12,7 +14,6 @@ use tracing::debug; use super::conn::{Builder, SendRequest}; use crate::{ body::HttpBody, - common::{task, Pin, Poll}, service::{MakeConnection, Service}, }; @@ -58,7 +59,7 @@ where type Future = Pin> + Send + 'static>>; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { self.inner .poll_ready(cx) .map_err(|e| crate::Error::new(crate::error::Kind::Connect).with(e.into())) diff --git a/src/common/drain.rs b/src/common/drain.rs index 174da876df..c8562d3c98 100644 --- a/src/common/drain.rs +++ b/src/common/drain.rs @@ -1,10 +1,11 @@ +use std::future::Future; use std::mem; +use std::pin::Pin; +use std::task::{Context, Poll}; 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 }) @@ -47,7 +48,7 @@ impl Signal { impl Future for Draining { type Output = (); - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { Pin::new(&mut self.as_mut().0).poll(cx) } } @@ -80,7 +81,7 @@ where { type Output = F::Output; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut me = self.project(); loop { match mem::replace(me.state, State::Draining) { @@ -115,7 +116,7 @@ mod tests { impl Future for TestMe { type Output = (); - fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, _: &mut Context<'_>) -> Poll { self.poll_cnt += 1; if self.finished { Poll::Ready(()) diff --git a/src/common/io/rewind.rs b/src/common/io/rewind.rs index 0afef5f7ea..9ed7c42fea 100644 --- a/src/common/io/rewind.rs +++ b/src/common/io/rewind.rs @@ -1,11 +1,11 @@ use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::{cmp, io}; use bytes::{Buf, Bytes}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; -use crate::common::{task, Pin, Poll}; - /// Combine a buffer with an IO, rewinding reads to use the buffer. #[derive(Debug)] pub(crate) struct Rewind { @@ -50,7 +50,7 @@ where { fn poll_read( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { if let Some(mut prefix) = self.pre.take() { @@ -78,7 +78,7 @@ where { fn poll_write( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.inner).poll_write(cx, buf) @@ -86,17 +86,17 @@ where fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { Pin::new(&mut self.inner).poll_write_vectored(cx, bufs) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.inner).poll_flush(cx) } - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.inner).poll_shutdown(cx) } diff --git a/src/common/lazy.rs b/src/common/lazy.rs index 2722077303..df2c07d596 100644 --- a/src/common/lazy.rs +++ b/src/common/lazy.rs @@ -1,6 +1,9 @@ -use pin_project_lite::pin_project; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; -use super::{task, Future, Pin, Poll}; +use pin_project_lite::pin_project; pub(crate) trait Started: Future { fn started(&self) -> bool; @@ -55,7 +58,7 @@ where { type Output = R::Output; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut this = self.project(); if let InnerProj::Fut { fut } = this.inner.as_mut().project() { diff --git a/src/common/mod.rs b/src/common/mod.rs index e38c6f5c7a..3d83946243 100644 --- a/src/common/mod.rs +++ b/src/common/mod.rs @@ -17,23 +17,14 @@ pub(crate) mod exec; pub(crate) mod io; #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] mod lazy; -mod never; #[cfg(any( feature = "stream", all(feature = "client", any(feature = "http1", feature = "http2")) ))] pub(crate) mod sync_wrapper; +#[cfg(feature = "http1")] pub(crate) mod task; pub(crate) mod watch; #[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))] pub(crate) use self::lazy::{lazy, Started as Lazy}; -#[cfg(any(feature = "http1", feature = "http2", feature = "runtime"))] -pub(crate) use self::never::Never; -pub(crate) use self::task::Poll; - -// group up types normally needed for `Future` -cfg_proto! { - pub(crate) use std::marker::Unpin; -} -pub(crate) use std::{future::Future, pin::Pin}; diff --git a/src/common/never.rs b/src/common/never.rs deleted file mode 100644 index f143caf60f..0000000000 --- a/src/common/never.rs +++ /dev/null @@ -1,21 +0,0 @@ -//! An uninhabitable type meaning it can never happen. -//! -//! To be replaced with `!` once it is stable. - -use std::error::Error; -use std::fmt; - -#[derive(Debug)] -pub(crate) enum Never {} - -impl fmt::Display for Never { - fn fmt(&self, _: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self {} - } -} - -impl Error for Never { - fn description(&self) -> &str { - match *self {} - } -} diff --git a/src/common/task.rs b/src/common/task.rs index ec70c957d6..0ac047a462 100644 --- a/src/common/task.rs +++ b/src/common/task.rs @@ -1,12 +1,12 @@ -#[cfg(feature = "http1")] -use super::Never; -pub(crate) use std::task::{Context, Poll}; +use std::{ + convert::Infallible, + task::{Context, Poll}, +}; /// A function to help "yield" a future, such that it is re-scheduled immediately. /// /// Useful for spin counts, so a future doesn't hog too much time. -#[cfg(feature = "http1")] -pub(crate) fn yield_now(cx: &mut Context<'_>) -> Poll { +pub(crate) fn yield_now(cx: &mut Context<'_>) -> Poll { cx.waker().wake_by_ref(); Poll::Pending } diff --git a/src/headers.rs b/src/headers.rs index 8407be185f..2e5e5db0f2 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -53,15 +53,15 @@ pub(super) fn content_length_parse_all_values(values: ValueIter<'_, HeaderValue> return None; } } else { - return None + return None; } } } else { - return None + return None; } } - return content_length + content_length } fn from_digits(bytes: &[u8]) -> Option { @@ -80,7 +80,7 @@ fn from_digits(bytes: &[u8]) -> Option { b'0'..=b'9' => { result = result.checked_mul(RADIX)?; result = result.checked_add((b - b'0') as u64)?; - }, + } _ => { // not a DIGIT, get outta here! return None; diff --git a/src/lib.rs b/src/lib.rs index e5e4cfc56e..064a18ec30 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,8 @@ #![deny(missing_debug_implementations)] #![cfg_attr(test, deny(rust_2018_idioms))] #![cfg_attr(all(test, feature = "full"), deny(unreachable_pub))] -#![cfg_attr(all(test, feature = "full"), deny(warnings))] +// 0.14.x is not actively developed, new warnings just get in the way. +//#![cfg_attr(all(test, feature = "full", not(feature = "nightly")), deny(warnings))] #![cfg_attr(all(test, feature = "nightly"), feature(test))] #![cfg_attr(docsrs, feature(doc_cfg))] diff --git a/src/proto/h1/conn.rs b/src/proto/h1/conn.rs index 5ebff2803e..5ab72f264e 100644 --- a/src/proto/h1/conn.rs +++ b/src/proto/h1/conn.rs @@ -1,6 +1,9 @@ use std::fmt; use std::io; use std::marker::PhantomData; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; #[cfg(all(feature = "server", feature = "runtime"))] use std::time::Duration; @@ -16,7 +19,6 @@ use tracing::{debug, error, trace}; use super::io::Buffered; use super::{Decoder, Encode, EncodedBuf, Encoder, Http1Transaction, ParseContext, Wants}; use crate::body::DecodedLength; -use crate::common::{task, Pin, Poll, Unpin}; use crate::headers::connection_keep_alive; use crate::proto::{BodyLength, MessageHead}; @@ -185,7 +187,7 @@ where pub(super) fn poll_read_head( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll, DecodedLength, Wants)>>> { debug_assert!(self.can_read_head()); trace!("Conn::read_head"); @@ -286,7 +288,7 @@ where pub(crate) fn poll_read_body( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>> { debug_assert!(self.can_read_body()); @@ -347,10 +349,7 @@ where ret } - pub(crate) fn poll_read_keep_alive( - &mut self, - cx: &mut task::Context<'_>, - ) -> Poll> { + pub(crate) fn poll_read_keep_alive(&mut self, cx: &mut Context<'_>) -> Poll> { debug_assert!(!self.can_read_head() && !self.can_read_body()); if self.is_read_closed() { @@ -373,7 +372,7 @@ where // // This should only be called for Clients wanting to enter the idle // state. - fn require_empty_read(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn require_empty_read(&mut self, cx: &mut Context<'_>) -> Poll> { debug_assert!(!self.can_read_head() && !self.can_read_body() && !self.is_read_closed()); debug_assert!(!self.is_mid_message()); debug_assert!(T::is_client()); @@ -406,7 +405,7 @@ where Poll::Ready(Err(crate::Error::new_unexpected_message())) } - fn mid_message_detect_eof(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn mid_message_detect_eof(&mut self, cx: &mut Context<'_>) -> Poll> { debug_assert!(!self.can_read_head() && !self.can_read_body() && !self.is_read_closed()); debug_assert!(self.is_mid_message()); @@ -425,7 +424,7 @@ where } } - fn force_io_read(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn force_io_read(&mut self, cx: &mut Context<'_>) -> Poll> { debug_assert!(!self.state.is_read_closed()); let result = ready!(self.io.poll_read_from_io(cx)); @@ -436,7 +435,7 @@ where })) } - fn maybe_notify(&mut self, cx: &mut task::Context<'_>) { + fn maybe_notify(&mut self, cx: &mut Context<'_>) { // its possible that we returned NotReady from poll() without having // exhausted the underlying Io. We would have done this when we // determined we couldn't keep reading until we knew how writing @@ -483,7 +482,7 @@ where } } - fn try_keep_alive(&mut self, cx: &mut task::Context<'_>) { + fn try_keep_alive(&mut self, cx: &mut Context<'_>) { self.state.try_keep_alive::(); self.maybe_notify(cx); } @@ -726,14 +725,14 @@ where Err(err) } - pub(crate) fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub(crate) fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll> { ready!(Pin::new(&mut self.io).poll_flush(cx))?; self.try_keep_alive(cx); trace!("flushed({}): {:?}", T::LOG, self.state); Poll::Ready(Ok(())) } - pub(crate) fn poll_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub(crate) fn poll_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { match ready!(Pin::new(self.io.io_mut()).poll_shutdown(cx)) { Ok(()) => { trace!("shut down IO complete"); @@ -747,7 +746,7 @@ where } /// If the read side can be cheaply drained, do so. Otherwise, close. - pub(super) fn poll_drain_or_close_read(&mut self, cx: &mut task::Context<'_>) { + pub(super) fn poll_drain_or_close_read(&mut self, cx: &mut Context<'_>) { if let Reading::Continue(ref decoder) = self.state.reading { // skip sending the 100-continue // just move forward to a read, in case a tiny body was included diff --git a/src/proto/h1/decode.rs b/src/proto/h1/decode.rs index 1e3a38effc..6752d58369 100644 --- a/src/proto/h1/decode.rs +++ b/src/proto/h1/decode.rs @@ -1,18 +1,22 @@ use std::error::Error as StdError; use std::fmt; use std::io; +use std::task::{Context, Poll}; use std::usize; use bytes::Bytes; use tracing::{debug, trace}; -use crate::common::{task, Poll}; - use super::io::MemRead; use super::DecodedLength; use self::Kind::{Chunked, Eof, Length}; +/// Maximum amount of bytes allowed in chunked extensions. +/// +/// This limit is currentlty applied for the entire body, not per chunk. +const CHUNKED_EXTENSIONS_LIMIT: u64 = 1024 * 16; + /// Decoders to handle different Transfer-Encodings. /// /// If a message body does not include a Transfer-Encoding, it *should* @@ -27,7 +31,11 @@ enum Kind { /// A Reader used when a Content-Length header is passed with a positive integer. Length(u64), /// A Reader used when Transfer-Encoding is `chunked`. - Chunked(ChunkedState, u64), + Chunked { + state: ChunkedState, + chunk_len: u64, + extensions_cnt: u64, + }, /// A Reader used for responses that don't indicate a length or chunked. /// /// The bool tracks when EOF is seen on the transport. @@ -49,6 +57,7 @@ enum Kind { #[derive(Debug, PartialEq, Clone, Copy)] enum ChunkedState { + Start, Size, SizeLws, Extension, @@ -74,7 +83,11 @@ impl Decoder { pub(crate) fn chunked() -> Decoder { Decoder { - kind: Kind::Chunked(ChunkedState::Size, 0), + kind: Kind::Chunked { + state: ChunkedState::new(), + chunk_len: 0, + extensions_cnt: 0, + }, } } @@ -95,12 +108,20 @@ impl Decoder { // methods pub(crate) fn is_eof(&self) -> bool { - matches!(self.kind, Length(0) | Chunked(ChunkedState::End, _) | Eof(true)) + matches!( + self.kind, + Length(0) + | Chunked { + state: ChunkedState::End, + .. + } + | Eof(true) + ) } pub(crate) fn decode( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, body: &mut R, ) -> Poll> { trace!("decode; state={:?}", self.kind); @@ -125,11 +146,15 @@ impl Decoder { Poll::Ready(Ok(buf)) } } - Chunked(ref mut state, ref mut size) => { + Chunked { + ref mut state, + ref mut chunk_len, + ref mut extensions_cnt, + } => { loop { let mut buf = None; // advances the chunked state - *state = ready!(state.step(cx, body, size, &mut buf))?; + *state = ready!(state.step(cx, body, chunk_len, extensions_cnt, &mut buf))?; if *state == ChunkedState::End { trace!("end of chunked"); return Poll::Ready(Ok(Bytes::new())); @@ -179,19 +204,36 @@ macro_rules! byte ( }) ); +macro_rules! or_overflow { + ($e:expr) => ( + match $e { + Some(val) => val, + None => return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "invalid chunk size: overflow", + ))), + } + ) +} + impl ChunkedState { + fn new() -> ChunkedState { + ChunkedState::Start + } fn step( &self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, body: &mut R, size: &mut u64, + extensions_cnt: &mut u64, buf: &mut Option, ) -> Poll> { use self::ChunkedState::*; match *self { + Start => ChunkedState::read_start(cx, body, size), Size => ChunkedState::read_size(cx, body, size), SizeLws => ChunkedState::read_size_lws(cx, body), - Extension => ChunkedState::read_extension(cx, body), + Extension => ChunkedState::read_extension(cx, body, extensions_cnt), SizeLf => ChunkedState::read_size_lf(cx, body, *size), Body => ChunkedState::read_body(cx, body, size, buf), BodyCr => ChunkedState::read_body_cr(cx, body), @@ -203,25 +245,46 @@ impl ChunkedState { End => Poll::Ready(Ok(ChunkedState::End)), } } - fn read_size( - cx: &mut task::Context<'_>, + + fn read_start( + cx: &mut Context<'_>, rdr: &mut R, size: &mut u64, ) -> Poll> { - trace!("Read chunk hex size"); + trace!("Read chunk start"); - macro_rules! or_overflow { - ($e:expr) => ( - match $e { - Some(val) => val, - None => return Poll::Ready(Err(io::Error::new( - io::ErrorKind::InvalidData, - "invalid chunk size: overflow", - ))), - } - ) + let radix = 16; + match byte!(rdr, cx) { + b @ b'0'..=b'9' => { + *size = or_overflow!(size.checked_mul(radix)); + *size = or_overflow!(size.checked_add((b - b'0') as u64)); + } + b @ b'a'..=b'f' => { + *size = or_overflow!(size.checked_mul(radix)); + *size = or_overflow!(size.checked_add((b + 10 - b'a') as u64)); + } + b @ b'A'..=b'F' => { + *size = or_overflow!(size.checked_mul(radix)); + *size = or_overflow!(size.checked_add((b + 10 - b'A') as u64)); + } + _ => { + return Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidInput, + "Invalid chunk size line: missing size digit", + ))); + } } + Poll::Ready(Ok(ChunkedState::Size)) + } + + fn read_size( + cx: &mut Context<'_>, + rdr: &mut R, + size: &mut u64, + ) -> Poll> { + trace!("Read chunk hex size"); + let radix = 16; match byte!(rdr, cx) { b @ b'0'..=b'9' => { @@ -249,7 +312,7 @@ impl ChunkedState { Poll::Ready(Ok(ChunkedState::Size)) } fn read_size_lws( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { trace!("read_size_lws"); @@ -265,8 +328,9 @@ impl ChunkedState { } } fn read_extension( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, + extensions_cnt: &mut u64, ) -> Poll> { trace!("read_extension"); // We don't care about extensions really at all. Just ignore them. @@ -281,11 +345,21 @@ impl ChunkedState { io::ErrorKind::InvalidData, "invalid chunk extension contains newline", ))), - _ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions + _ => { + *extensions_cnt += 1; + if *extensions_cnt >= CHUNKED_EXTENSIONS_LIMIT { + Poll::Ready(Err(io::Error::new( + io::ErrorKind::InvalidData, + "chunk extensions over limit", + ))) + } else { + Poll::Ready(Ok(ChunkedState::Extension)) + } + } // no supported extensions } } fn read_size_lf( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, size: u64, ) -> Poll> { @@ -307,7 +381,7 @@ impl ChunkedState { } fn read_body( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, rem: &mut u64, buf: &mut Option, @@ -341,7 +415,7 @@ impl ChunkedState { } } fn read_body_cr( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { match byte!(rdr, cx) { @@ -353,7 +427,7 @@ impl ChunkedState { } } fn read_body_lf( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { match byte!(rdr, cx) { @@ -366,7 +440,7 @@ impl ChunkedState { } fn read_trailer( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { trace!("read_trailer"); @@ -376,7 +450,7 @@ impl ChunkedState { } } fn read_trailer_lf( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { match byte!(rdr, cx) { @@ -389,7 +463,7 @@ impl ChunkedState { } fn read_end_cr( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { match byte!(rdr, cx) { @@ -398,7 +472,7 @@ impl ChunkedState { } } fn read_end_lf( - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, rdr: &mut R, ) -> Poll> { match byte!(rdr, cx) { @@ -430,7 +504,7 @@ mod tests { use tokio::io::{AsyncRead, ReadBuf}; impl<'a> MemRead for &'a [u8] { - fn read_mem(&mut self, _: &mut task::Context<'_>, len: usize) -> Poll> { + fn read_mem(&mut self, _: &mut Context<'_>, len: usize) -> Poll> { let n = std::cmp::min(len, self.len()); if n > 0 { let (a, b) = self.split_at(n); @@ -444,7 +518,7 @@ mod tests { } impl<'a> MemRead for &'a mut (dyn AsyncRead + Unpin) { - fn read_mem(&mut self, cx: &mut task::Context<'_>, len: usize) -> Poll> { + fn read_mem(&mut self, cx: &mut Context<'_>, len: usize) -> Poll> { let mut v = vec![0; len]; let mut buf = ReadBuf::new(&mut v); ready!(Pin::new(self).poll_read(cx, &mut buf)?); @@ -452,9 +526,8 @@ mod tests { } } - #[cfg(feature = "nightly")] impl MemRead for Bytes { - fn read_mem(&mut self, _: &mut task::Context<'_>, len: usize) -> Poll> { + fn read_mem(&mut self, _: &mut Context<'_>, len: usize) -> Poll> { let n = std::cmp::min(len, self.len()); let ret = self.split_to(n); Poll::Ready(Ok(ret)) @@ -476,13 +549,15 @@ mod tests { use std::io::ErrorKind::{InvalidData, InvalidInput, UnexpectedEof}; async fn read(s: &str) -> u64 { - let mut state = ChunkedState::Size; + let mut state = ChunkedState::new(); let rdr = &mut s.as_bytes(); let mut size = 0; + let mut ext_cnt = 0; loop { - let result = - futures_util::future::poll_fn(|cx| state.step(cx, rdr, &mut size, &mut None)) - .await; + let result = futures_util::future::poll_fn(|cx| { + state.step(cx, rdr, &mut size, &mut ext_cnt, &mut None) + }) + .await; let desc = format!("read_size failed for {:?}", s); state = result.expect(desc.as_str()); if state == ChunkedState::Body || state == ChunkedState::EndCr { @@ -493,13 +568,15 @@ mod tests { } async fn read_err(s: &str, expected_err: io::ErrorKind) { - let mut state = ChunkedState::Size; + let mut state = ChunkedState::new(); let rdr = &mut s.as_bytes(); let mut size = 0; + let mut ext_cnt = 0; loop { - let result = - futures_util::future::poll_fn(|cx| state.step(cx, rdr, &mut size, &mut None)) - .await; + let result = futures_util::future::poll_fn(|cx| { + state.step(cx, rdr, &mut size, &mut ext_cnt, &mut None) + }) + .await; state = match result { Ok(s) => s, Err(e) => { @@ -530,6 +607,9 @@ mod tests { // Missing LF or CRLF read_err("F\rF", InvalidInput).await; read_err("F", UnexpectedEof).await; + // Missing digit + read_err("\r\n\r\n", InvalidInput).await; + read_err("\r\n", InvalidInput).await; // Invalid hex digit read_err("X\r\n", InvalidInput).await; read_err("1X\r\n", InvalidInput).await; @@ -586,6 +666,33 @@ mod tests { assert_eq!("1234567890abcdef", &result); } + #[tokio::test] + async fn test_read_chunked_extensions_over_limit() { + // construct a chunked body where each individual chunked extension + // is totally fine, but combined is over the limit. + let per_chunk = super::CHUNKED_EXTENSIONS_LIMIT * 2 / 3; + let mut scratch = vec![]; + for _ in 0..2 { + scratch.extend(b"1;"); + scratch.extend(b"x".repeat(per_chunk as usize)); + scratch.extend(b"\r\nA\r\n"); + } + scratch.extend(b"0\r\n\r\n"); + let mut mock_buf = Bytes::from(scratch); + + let mut decoder = Decoder::chunked(); + let buf1 = decoder.decode_fut(&mut mock_buf).await.expect("decode1"); + assert_eq!(&buf1[..], b"A"); + + let err = decoder + .decode_fut(&mut mock_buf) + .await + .expect_err("decode2"); + assert_eq!(err.kind(), io::ErrorKind::InvalidData); + assert_eq!(err.to_string(), "chunk extensions over limit"); + } + + #[cfg(not(miri))] #[tokio::test] async fn test_read_chunked_trailer_with_missing_lf() { let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\nbad\r\r\n"[..]; diff --git a/src/proto/h1/dispatch.rs b/src/proto/h1/dispatch.rs index fddf639d9e..3516d7ad21 100644 --- a/src/proto/h1/dispatch.rs +++ b/src/proto/h1/dispatch.rs @@ -1,4 +1,8 @@ use std::error::Error as StdError; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use bytes::{Buf, Bytes}; use http::Request; @@ -7,10 +11,8 @@ use tracing::{debug, trace}; use super::{Http1Transaction, Wants}; use crate::body::{Body, DecodedLength, HttpBody}; -use crate::common::{task, Future, Pin, Poll, Unpin}; -use crate::proto::{ - BodyLength, Conn, Dispatched, MessageHead, RequestHead, -}; +use crate::common; +use crate::proto::{BodyLength, Conn, Dispatched, MessageHead, RequestHead}; use crate::upgrade::OnUpgrade; pub(crate) struct Dispatcher { @@ -28,10 +30,10 @@ pub(crate) trait Dispatch { type RecvItem; fn poll_msg( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>>; fn recv_msg(&mut self, msg: crate::Result<(Self::RecvItem, Body)>) -> crate::Result<()>; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll>; fn should_poll(&self) -> bool; } @@ -60,10 +62,10 @@ cfg_client! { impl Dispatcher where D: Dispatch< - PollItem = MessageHead, - PollBody = Bs, - RecvItem = MessageHead, - > + Unpin, + PollItem = MessageHead, + PollBody = Bs, + RecvItem = MessageHead, + > + Unpin, D::PollError: Into>, I: AsyncRead + AsyncWrite + Unpin, T: Http1Transaction + Unpin, @@ -98,10 +100,7 @@ where /// /// This is useful for old-style HTTP upgrades, but ignores /// newer-style upgrade API. - pub(crate) fn poll_without_shutdown( - &mut self, - cx: &mut task::Context<'_>, - ) -> Poll> + pub(crate) fn poll_without_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> where Self: Unpin, { @@ -114,7 +113,7 @@ where fn poll_catch( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, should_shutdown: bool, ) -> Poll> { Poll::Ready(ready!(self.poll_inner(cx, should_shutdown)).or_else(|e| { @@ -133,7 +132,7 @@ where fn poll_inner( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, should_shutdown: bool, ) -> Poll> { T::update_date(); @@ -154,7 +153,7 @@ where } } - fn poll_loop(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_loop(&mut self, cx: &mut Context<'_>) -> Poll> { // Limit the looping on this connection, in case it is ready far too // often, so that other futures don't starve. // @@ -181,10 +180,10 @@ where trace!("poll_loop yielding (self = {:p})", self); - task::yield_now(cx).map(|never| match never {}) + common::task::yield_now(cx).map(|never| match never {}) } - fn poll_read(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_read(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if self.is_closing { return Poll::Ready(Ok(())); @@ -238,7 +237,7 @@ where } } - fn poll_read_head(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_read_head(&mut self, cx: &mut Context<'_>) -> Poll> { // can dispatch receive, or does it still care about, an incoming message? match ready!(self.dispatch.poll_ready(cx)) { Ok(()) => (), @@ -262,7 +261,10 @@ where if wants.contains(Wants::UPGRADE) { let upgrade = self.conn.on_upgrade(); debug_assert!(!upgrade.is_none(), "empty upgrade"); - debug_assert!(head.extensions.get::().is_none(), "OnUpgrade already set"); + debug_assert!( + head.extensions.get::().is_none(), + "OnUpgrade already set" + ); head.extensions.insert(upgrade); } self.dispatch.recv_msg(Ok((head, body)))?; @@ -290,7 +292,7 @@ where } } - fn poll_write(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_write(&mut self, cx: &mut Context<'_>) -> Poll> { loop { if self.is_closing { return Poll::Ready(Ok(())); @@ -382,7 +384,7 @@ where } } - fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll> { self.conn.poll_flush(cx).map_err(|err| { debug!("error writing: {}", err); crate::Error::new_body_write(err) @@ -416,10 +418,10 @@ where impl Future for Dispatcher where D: Dispatch< - PollItem = MessageHead, - PollBody = Bs, - RecvItem = MessageHead, - > + Unpin, + PollItem = MessageHead, + PollBody = Bs, + RecvItem = MessageHead, + > + Unpin, D::PollError: Into>, I: AsyncRead + AsyncWrite + Unpin, T: Http1Transaction + Unpin, @@ -429,7 +431,7 @@ where type Output = crate::Result; #[inline] - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.poll_catch(cx, true) } } @@ -493,7 +495,7 @@ cfg_server! { fn poll_msg( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>> { let mut this = self.as_mut(); let ret = if let Some(ref mut fut) = this.in_flight.as_mut().as_pin_mut() { @@ -528,7 +530,7 @@ cfg_server! { Ok(()) } - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { if self.in_flight.is_some() { Poll::Pending } else { @@ -564,13 +566,13 @@ cfg_client! { { type PollItem = RequestHead; type PollBody = B; - type PollError = crate::common::Never; + type PollError = std::convert::Infallible; type RecvItem = crate::proto::ResponseHead; fn poll_msg( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, - ) -> Poll>> { + cx: &mut Context<'_>, + ) -> Poll>> { let mut this = self.as_mut(); debug_assert!(!this.rx_closed); match this.rx.poll_recv(cx) { @@ -640,7 +642,7 @@ cfg_client! { } } - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { match self.callback { Some(ref mut cb) => match cb.poll_canceled(cx) { Poll::Ready(()) => { diff --git a/src/proto/h1/io.rs b/src/proto/h1/io.rs index 1d251e2c84..02d8a4a9ec 100644 --- a/src/proto/h1/io.rs +++ b/src/proto/h1/io.rs @@ -5,6 +5,8 @@ use std::future::Future; use std::io::{self, IoSlice}; use std::marker::Unpin; use std::mem::MaybeUninit; +use std::pin::Pin; +use std::task::{Context, Poll}; #[cfg(all(feature = "server", feature = "runtime"))] use std::time::Duration; @@ -16,7 +18,6 @@ use tracing::{debug, trace}; use super::{Http1Transaction, ParseContext, ParsedMessage}; use crate::common::buf::BufList; -use crate::common::{task, Pin, Poll}; /// The initial buffer size allocated before trying to read from IO. pub(crate) const INIT_BUFFER_SIZE: usize = 8192; @@ -174,7 +175,7 @@ where pub(super) fn parse( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, parse_ctx: ParseContext<'_>, ) -> Poll>> where @@ -250,10 +251,7 @@ where } } - pub(crate) fn poll_read_from_io( - &mut self, - cx: &mut task::Context<'_>, - ) -> Poll> { + pub(crate) fn poll_read_from_io(&mut self, cx: &mut Context<'_>) -> Poll> { self.read_blocked = false; let next = self.read_buf_strategy.next(); if self.read_buf_remaining_mut() < next { @@ -296,7 +294,7 @@ where self.read_blocked } - pub(crate) fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub(crate) fn poll_flush(&mut self, cx: &mut Context<'_>) -> Poll> { if self.flush_pipeline && !self.read_buf.is_empty() { Poll::Ready(Ok(())) } else if self.write_buf.remaining() == 0 { @@ -336,7 +334,7 @@ where /// /// Since all buffered bytes are flattened into the single headers buffer, /// that skips some bookkeeping around using multiple buffers. - fn poll_flush_flattened(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_flush_flattened(&mut self, cx: &mut Context<'_>) -> Poll> { loop { let n = ready!(Pin::new(&mut self.io).poll_write(cx, self.write_buf.headers.chunk()))?; debug!("flushed {} bytes", n); @@ -366,7 +364,7 @@ impl Unpin for Buffered {} // TODO: This trait is old... at least rename to PollBytes or something... pub(crate) trait MemRead { - fn read_mem(&mut self, cx: &mut task::Context<'_>, len: usize) -> Poll>; + fn read_mem(&mut self, cx: &mut Context<'_>, len: usize) -> Poll>; } impl MemRead for Buffered @@ -374,7 +372,7 @@ where T: AsyncRead + AsyncWrite + Unpin, B: Buf, { - fn read_mem(&mut self, cx: &mut task::Context<'_>, len: usize) -> Poll> { + fn read_mem(&mut self, cx: &mut Context<'_>, len: usize) -> Poll> { if !self.read_buf.is_empty() { let n = std::cmp::min(len, self.read_buf.len()); Poll::Ready(Ok(self.read_buf.split_to(n).freeze())) diff --git a/src/proto/h1/role.rs b/src/proto/h1/role.rs index 6252207baf..7a4544d989 100644 --- a/src/proto/h1/role.rs +++ b/src/proto/h1/role.rs @@ -67,17 +67,12 @@ pub(super) fn parse_headers( where T: Http1Transaction, { - // If the buffer is empty, don't bother entering the span, it's just noise. - if bytes.is_empty() { - return Ok(None); - } - - let span = trace_span!("parse_headers"); - let _s = span.enter(); - #[cfg(all(feature = "server", feature = "runtime"))] if !*ctx.h1_header_read_timeout_running { if let Some(h1_header_read_timeout) = ctx.h1_header_read_timeout { + let span = trace_span!("parse_headers"); + let _s = span.enter(); + let deadline = Instant::now() + h1_header_read_timeout; *ctx.h1_header_read_timeout_running = true; match ctx.h1_header_read_timeout_fut { @@ -94,6 +89,14 @@ where } } + // If the buffer is empty, don't bother entering the span, it's just noise. + if bytes.is_empty() { + return Ok(None); + } + + let span = trace_span!("parse_headers"); + let _s = span.enter(); + T::parse(bytes, ctx) } diff --git a/src/proto/h2/client.rs b/src/proto/h2/client.rs index bac8eceb3a..8c2a4d2e0f 100644 --- a/src/proto/h2/client.rs +++ b/src/proto/h2/client.rs @@ -1,4 +1,9 @@ +use std::convert::Infallible; use std::error::Error as StdError; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; #[cfg(feature = "runtime")] use std::time::Duration; @@ -15,7 +20,7 @@ use tracing::{debug, trace, warn}; use super::{ping, H2Upgraded, PipeToSendStream, SendBuf}; use crate::body::HttpBody; use crate::client::dispatch::Callback; -use crate::common::{exec::Exec, task, Future, Never, Pin, Poll}; +use crate::common::exec::Exec; use crate::ext::Protocol; use crate::headers; use crate::proto::h2::UpgradedSendStream; @@ -28,11 +33,11 @@ type ClientRx = crate::client::dispatch::Receiver, Response> ///// An mpsc channel is used to help notify the `Connection` task when *all* ///// other handles to it have been dropped, so that it can shutdown. -type ConnDropRef = mpsc::Sender; +type ConnDropRef = mpsc::Sender; ///// A oneshot channel watches the `Connection` task, and when it completes, ///// the "dispatch" task will be notified and can shutdown sooner. -type ConnEof = oneshot::Receiver; +type ConnEof = oneshot::Receiver; // Our defaults are chosen for the "majority" case, which usually are not // resource constrained, and so the spec default of 64kb can be too limiting @@ -177,7 +182,7 @@ where }) } -async fn conn_task(conn: C, drop_rx: D, cancel_tx: oneshot::Sender) +async fn conn_task(conn: C, drop_rx: D, cancel_tx: oneshot::Sender) where C: Future + Unpin, D: Future + Unpin, @@ -239,7 +244,7 @@ where B::Data: Send, B::Error: Into>, { - fn poll_pipe(&mut self, f: FutCtx, cx: &mut task::Context<'_>) { + fn poll_pipe(&mut self, f: FutCtx, cx: &mut Context<'_>) { let ping = self.ping.clone(); let send_stream = if !f.is_connect { if !f.eos { @@ -334,7 +339,7 @@ where { type Output = crate::Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { match ready!(self.h2_tx.poll_ready(cx)) { Ok(()) => (), diff --git a/src/proto/h2/mod.rs b/src/proto/h2/mod.rs index 5857c919d1..d50850d0a0 100644 --- a/src/proto/h2/mod.rs +++ b/src/proto/h2/mod.rs @@ -4,14 +4,15 @@ use http::header::{HeaderName, CONNECTION, TE, TRAILER, TRANSFER_ENCODING, UPGRA use http::HeaderMap; use pin_project_lite::pin_project; use std::error::Error as StdError; +use std::future::Future; use std::io::{self, Cursor, IoSlice}; use std::mem; -use std::task::Context; +use std::pin::Pin; +use std::task::{Context, Poll}; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; use tracing::{debug, trace, warn}; use crate::body::HttpBody; -use crate::common::{task, Future, Pin, Poll}; use crate::proto::h2::ping::Recorder; pub(crate) mod ping; @@ -116,7 +117,7 @@ where { type Output = crate::Result<()>; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut me = self.project(); loop { if !*me.data_done { @@ -383,14 +384,12 @@ where cx: &mut Context<'_>, ) -> Poll> { if self.send_stream.write(&[], true).is_ok() { - return Poll::Ready(Ok(())) + return Poll::Ready(Ok(())); } Poll::Ready(Err(h2_to_io_error( match ready!(self.send_stream.poll_reset(cx)) { - Ok(Reason::NO_ERROR) => { - return Poll::Ready(Ok(())) - } + Ok(Reason::NO_ERROR) => return Poll::Ready(Ok(())), Ok(Reason::CANCEL) | Ok(Reason::STREAM_CLOSED) => { return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into())) } diff --git a/src/proto/h2/ping.rs b/src/proto/h2/ping.rs index 1e8386497c..d830c93eda 100644 --- a/src/proto/h2/ping.rs +++ b/src/proto/h2/ping.rs @@ -328,7 +328,7 @@ impl Ponger { } } - if let Some(ref mut bdp) = self.bdp { + if let Some(ref mut bdp) = self.bdp { let bytes = locked.bytes.expect("bdp enabled implies bytes"); locked.bytes = Some(0); // reset trace!("received BDP ack; bytes = {}, rtt = {:?}", bytes, rtt); @@ -336,7 +336,7 @@ impl Ponger { let update = bdp.calculate(bytes, rtt); locked.next_bdp_at = Some(now + bdp.ping_delay); if let Some(update) = update { - return Poll::Ready(Ponged::SizeUpdate(update)) + return Poll::Ready(Ponged::SizeUpdate(update)); } } } diff --git a/src/proto/h2/server.rs b/src/proto/h2/server.rs index 4127387e71..b7bff590ff 100644 --- a/src/proto/h2/server.rs +++ b/src/proto/h2/server.rs @@ -1,5 +1,8 @@ use std::error::Error as StdError; +use std::future::Future; use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; #[cfg(feature = "runtime")] use std::time::Duration; @@ -13,8 +16,8 @@ use tracing::{debug, trace, warn}; use super::{ping, PipeToSendStream, SendBuf}; use crate::body::HttpBody; +use crate::common::date; use crate::common::exec::ConnStreamExec; -use crate::common::{date, task, Future, Pin, Poll}; use crate::ext::Protocol; use crate::headers; use crate::proto::h2::ping::Recorder; @@ -35,8 +38,8 @@ const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024; // 1mb const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 400; // 400kb -// 16 MB "sane default" taken from golang http2 -const DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: u32 = 16 << 20; +const DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: u32 = 16 << 20; // 16 MB "sane default" taken from golang http2 +const DEFAULT_MAX_LOCAL_ERROR_RESET_STREAMS: usize = 1024; #[derive(Clone, Debug)] pub(crate) struct Config { @@ -47,6 +50,7 @@ pub(crate) struct Config { pub(crate) enable_connect_protocol: bool, pub(crate) max_concurrent_streams: Option, pub(crate) max_pending_accept_reset_streams: Option, + pub(crate) max_local_error_reset_streams: Option, #[cfg(feature = "runtime")] pub(crate) keep_alive_interval: Option, #[cfg(feature = "runtime")] @@ -65,6 +69,7 @@ impl Default for Config { enable_connect_protocol: false, max_concurrent_streams: None, max_pending_accept_reset_streams: None, + max_local_error_reset_streams: Some(DEFAULT_MAX_LOCAL_ERROR_RESET_STREAMS), #[cfg(feature = "runtime")] keep_alive_interval: None, #[cfg(feature = "runtime")] @@ -123,6 +128,7 @@ where .initial_connection_window_size(config.initial_conn_window_size) .max_frame_size(config.max_frame_size) .max_header_list_size(config.max_header_list_size) + .max_local_error_reset_streams(config.max_local_error_reset_streams) .max_send_buffer_size(config.max_send_buffer_size); if let Some(max) = config.max_concurrent_streams { builder.max_concurrent_streams(max); @@ -193,7 +199,7 @@ where { type Output = crate::Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let me = &mut *self; loop { let next = match me.state { @@ -236,7 +242,7 @@ where { fn poll_server( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, service: &mut S, exec: &mut E, ) -> Poll> @@ -356,7 +362,7 @@ where Poll::Ready(Err(self.closing.take().expect("polled after error"))) } - fn poll_ping(&mut self, cx: &mut task::Context<'_>) { + fn poll_ping(&mut self, cx: &mut Context<'_>) { if let Some((_, ref mut estimator)) = self.ping { match estimator.poll(cx) { Poll::Ready(ping::Ponged::SizeUpdate(wnd)) => { @@ -447,7 +453,7 @@ where B::Error: Into>, E: Into>, { - fn poll2(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll2(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { let mut me = self.project(); loop { let next = match me.state.as_mut().project() { @@ -542,7 +548,7 @@ where { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.poll2(cx).map(|res| { if let Err(e) = res { debug!("stream error: {}", e); diff --git a/src/proto/mod.rs b/src/proto/mod.rs index f938bf532b..3628576dc1 100644 --- a/src/proto/mod.rs +++ b/src/proto/mod.rs @@ -50,7 +50,7 @@ pub(crate) enum BodyLength { Unknown, } -/// Status of when a Disaptcher future completes. +/// Status of when a Dispatcher future completes. pub(crate) enum Dispatched { /// Dispatcher completely shutdown connection. Shutdown, diff --git a/src/server/accept.rs b/src/server/accept.rs index 4b7a1487dd..07dcd62524 100644 --- a/src/server/accept.rs +++ b/src/server/accept.rs @@ -6,16 +6,14 @@ //! connections. //! - Utilities like `poll_fn` to ease creating a custom `Accept`. +use std::pin::Pin; +use std::task::{Context, Poll}; + #[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. @@ -26,7 +24,7 @@ pub trait Accept { /// Poll to accept the next connection. fn poll_accept( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>>; } @@ -51,7 +49,7 @@ pub trait Accept { /// ``` pub fn poll_fn(func: F) -> impl Accept where - F: FnMut(&mut task::Context<'_>) -> Poll>>, + F: FnMut(&mut Context<'_>) -> Poll>>, { struct PollFn(F); @@ -60,13 +58,13 @@ where impl Accept for PollFn where - F: FnMut(&mut task::Context<'_>) -> Poll>>, + F: FnMut(&mut Context<'_>) -> Poll>>, { type Conn = IO; type Error = E; fn poll_accept( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>> { (self.get_mut().0)(cx) } @@ -101,7 +99,7 @@ where type Error = E; fn poll_accept( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>> { self.project().stream.poll_next(cx) } diff --git a/src/server/conn.rs b/src/server/conn.rs index dfe2172457..951c9ee5cd 100644 --- a/src/server/conn.rs +++ b/src/server/conn.rs @@ -68,6 +68,12 @@ cfg_feature! { use std::error::Error as StdError; use std::fmt; + use std::task::{Context, Poll}; + use std::pin::Pin; + use std::future::Future; + use std::marker::Unpin; + #[cfg(not(all(feature = "http1", feature = "http2")))] + use std::convert::Infallible; use bytes::Bytes; use pin_project_lite::pin_project; @@ -76,9 +82,6 @@ cfg_feature! { pub use super::server::Connecting; use crate::body::{Body, HttpBody}; - use crate::common::{task, Future, Pin, Poll, Unpin}; - #[cfg(not(all(feature = "http1", feature = "http2")))] - use crate::common::Never; use crate::common::exec::{ConnStreamExec, Exec}; use crate::proto; use crate::service::HttpService; @@ -156,14 +159,14 @@ type Http1Dispatcher = proto::h1::Dispatcher, B, T, proto::ServerTransaction>; #[cfg(all(not(feature = "http1"), feature = "http2"))] -type Http1Dispatcher = (Never, PhantomData<(T, Box>, Box>)>); +type Http1Dispatcher = (Infallible, PhantomData<(T, Box>, Box>)>); #[cfg(feature = "http2")] type Http2Server = proto::h2::Server, S, B, E>; #[cfg(all(not(feature = "http2"), feature = "http1"))] type Http2Server = ( - Never, + Infallible, PhantomData<(T, Box>, Box>, Box>)>, ); @@ -411,6 +414,23 @@ impl Http { self } + /// Configures the maximum number of pending reset streams allowed before a GOAWAY will be sent. + /// + /// This will default to the default value set by the [`h2` crate](https://crates.io/crates/h2). + /// As of v0.3.17, it is 20. + /// + /// See for more information. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] + pub fn http2_max_local_error_reset_streams( + &mut self, + max: impl Into>, + ) -> &mut Self { + self.h2_builder.max_local_error_reset_streams = max.into(); + + self + } + /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 /// stream-level flow control. /// @@ -805,7 +825,7 @@ where /// upgrade. Once the upgrade is completed, the connection would be "done", /// but it is not desired to actually shutdown the IO object. Instead you /// would take it back using `into_parts`. - pub fn poll_without_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> { + pub fn poll_without_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> { loop { match *self.conn.as_mut().unwrap() { #[cfg(feature = "http1")] @@ -901,7 +921,7 @@ where { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { match ready!(Pin::new(self.conn.as_mut().unwrap()).poll(cx)) { Ok(done) => { @@ -980,7 +1000,7 @@ where { type Output = crate::Result; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.project() { #[cfg(feature = "http1")] ProtoServerProj::H1 { h1, .. } => h1.poll(cx), @@ -1041,7 +1061,7 @@ mod upgrades { { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { loop { match ready!(Pin::new(self.inner.conn.as_mut().unwrap()).poll(cx)) { Ok(proto::Dispatched::Shutdown) => return Poll::Ready(Ok(())), diff --git a/src/server/conn/http1.rs b/src/server/conn/http1.rs index b2e54976e7..ab833b938b 100644 --- a/src/server/conn/http1.rs +++ b/src/server/conn/http1.rs @@ -2,13 +2,16 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; use bytes::Bytes; use tokio::io::{AsyncRead, AsyncWrite}; use crate::body::{Body as IncomingBody, HttpBody as Body}; -use crate::common::{task, Future, Pin, Poll, Unpin}; use crate::proto; use crate::service::HttpService; @@ -126,7 +129,7 @@ where /// upgrade. Once the upgrade is completed, the connection would be "done", /// but it is not desired to actually shutdown the IO object. Instead you /// would take it back using `into_parts`. - pub fn poll_without_shutdown(&mut self, cx: &mut task::Context<'_>) -> Poll> + pub fn poll_without_shutdown(&mut self, cx: &mut Context<'_>) -> Poll> where S: Unpin, S::Future: Unpin, @@ -175,7 +178,7 @@ where { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match ready!(Pin::new(&mut self.conn).poll(cx)) { Ok(done) => { match done { @@ -431,7 +434,7 @@ mod upgrades { { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match ready!(Pin::new(&mut self.inner.as_mut().unwrap().conn).poll(cx)) { Ok(proto::Dispatched::Shutdown) => Poll::Ready(Ok(())), Ok(proto::Dispatched::Upgrade(pending)) => { diff --git a/src/server/conn/http2.rs b/src/server/conn/http2.rs index 978c646e10..4f7df823ae 100644 --- a/src/server/conn/http2.rs +++ b/src/server/conn/http2.rs @@ -2,6 +2,10 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; use pin_project_lite::pin_project; @@ -9,7 +13,6 @@ use tokio::io::{AsyncRead, AsyncWrite}; use crate::body::{Body as IncomingBody, HttpBody as Body}; use crate::common::exec::ConnStreamExec; -use crate::common::{task, Future, Pin, Poll, Unpin}; use crate::proto; use crate::service::HttpService; @@ -79,7 +82,7 @@ where { type Output = crate::Result<()>; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match ready!(Pin::new(&mut self.conn).poll(cx)) { Ok(_done) => { //TODO: the proto::h2::Server no longer needs to return diff --git a/src/server/server.rs b/src/server/server.rs index c90eac3e53..4cccedd98a 100644 --- a/src/server/server.rs +++ b/src/server/server.rs @@ -1,8 +1,11 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::marker::Unpin; #[cfg(feature = "tcp")] use std::net::{SocketAddr, TcpListener as StdTcpListener}; - +use std::pin::Pin; +use std::task::{Context, Poll}; #[cfg(feature = "tcp")] use std::time::Duration; @@ -17,7 +20,6 @@ use super::tcp::AddrIncoming; use crate::body::{Body, HttpBody}; use crate::common::exec::Exec; use crate::common::exec::{ConnStreamExec, NewSvcExec}; -use crate::common::{task, Future, Pin, Poll, Unpin}; // Renamed `Http` as `Http_` for now so that people upgrading don't see an // error that `hyper::server::Http` is private... use super::conn::{Connection, Http as Http_, UpgradeableConnection}; @@ -162,7 +164,7 @@ where fn poll_next_( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>>> { let me = self.project(); match ready!(me.make_service.poll_ready_ref(cx)) { @@ -188,7 +190,7 @@ where pub(super) fn poll_watch( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, watcher: &W, ) -> Poll> where @@ -221,7 +223,7 @@ where { type Output = crate::Result<()>; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { self.poll_watch(cx, &NoopWatcher) } } @@ -385,6 +387,21 @@ impl Builder { self } + /// Configures the maximum number of local reset streams allowed before a GOAWAY will be sent. + /// + /// If not set, hyper will use a default, currently of 1024. + /// + /// If `None` is supplied, hyper will not apply any limit. + /// This is not advised, as it can potentially expose servers to DOS vulnerabilities. + /// + /// See for more information. + #[cfg(feature = "http2")] + #[cfg_attr(docsrs, doc(cfg(feature = "http2")))] + pub fn http2_max_local_error_reset_streams(mut self, max: impl Into>) -> Self { + self.protocol.http2_max_local_error_reset_streams(max); + self + } + /// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2 /// stream-level flow control. /// @@ -629,6 +646,14 @@ impl Builder { self.incoming.set_sleep_on_errors(val); self } + + /// Returns the local address that the server will be bound to. + /// + /// This might be useful when knowing the address is required before calling `Builder::serve`, + /// but the address is not otherwise available (for e.g. when binding to port 0). + pub fn local_addr(&self) -> SocketAddr { + self.incoming.local_addr() + } } // Used by `Server` to optionally watch a `Connection` future. @@ -667,13 +692,17 @@ where // used by exec.rs pub(crate) mod new_svc { use std::error::Error as StdError; + use std::future::Future; + use std::marker::Unpin; + use std::pin::Pin; + use std::task::{Context, Poll}; + use tokio::io::{AsyncRead, AsyncWrite}; use tracing::debug; use super::{Connecting, Watcher}; 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; @@ -734,7 +763,7 @@ pub(crate) mod new_svc { { type Output = (); - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { // 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. @@ -802,7 +831,7 @@ where { type Output = Result, FE>; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut me = self.project(); let service = ready!(me.future.poll(cx))?; let io = Option::take(&mut me.io).expect("polled after complete"); diff --git a/src/server/shutdown.rs b/src/server/shutdown.rs index 96937d0827..be858481c5 100644 --- a/src/server/shutdown.rs +++ b/src/server/shutdown.rs @@ -1,4 +1,8 @@ use std::error::Error as StdError; +use std::future::Future; +use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use pin_project_lite::pin_project; use tokio::io::{AsyncRead, AsyncWrite}; @@ -10,7 +14,6 @@ use super::server::{Server, 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! { @@ -63,7 +66,7 @@ where { type Output = crate::Result<()>; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut me = self.project(); loop { let next = { diff --git a/src/server/tcp.rs b/src/server/tcp.rs index 3f937154be..edbddcd94b 100644 --- a/src/server/tcp.rs +++ b/src/server/tcp.rs @@ -1,15 +1,16 @@ +use socket2::TcpKeepalive; use std::fmt; +use std::future::Future; use std::io; use std::net::{SocketAddr, TcpListener as StdTcpListener}; +use std::pin::Pin; +use std::task::{Context, Poll}; use std::time::Duration; -use socket2::TcpKeepalive; 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; @@ -71,7 +72,7 @@ impl TcpKeepaliveConfig { windows, )))] fn ka_with_interval(ka: TcpKeepalive, _: Duration, _: &mut bool) -> TcpKeepalive { - ka // no-op as keepalive interval is not supported on this platform + ka // no-op as keepalive interval is not supported on this platform } #[cfg(any( @@ -100,7 +101,7 @@ impl TcpKeepaliveConfig { target_vendor = "apple", )))] fn ka_with_retries(ka: TcpKeepalive, _: u32, _: &mut bool) -> TcpKeepalive { - ka // no-op as keepalive retries is not supported on this platform + ka // no-op as keepalive retries is not supported on this platform } } @@ -200,7 +201,7 @@ impl AddrIncoming { self.sleep_on_errors = val; } - fn poll_next_(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_next_(&mut self, cx: &mut Context<'_>) -> Poll> { // Check if a previous timeout is active that was set by IO errors. if let Some(ref mut to) = self.timeout { ready!(Pin::new(to).poll(cx)); @@ -261,7 +262,7 @@ impl Accept for AddrIncoming { fn poll_accept( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, ) -> Poll>> { let result = ready!(self.poll_next_(cx)); Poll::Ready(Some(result)) @@ -300,11 +301,11 @@ mod addr_stream { use std::net::SocketAddr; #[cfg(unix)] use std::os::unix::io::{AsRawFd, RawFd}; + use std::pin::Pin; + use std::task::{Context, Poll}; 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)] @@ -352,7 +353,7 @@ mod addr_stream { /// not yet available. pub fn poll_peek( &mut self, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &mut tokio::io::ReadBuf<'_>, ) -> Poll> { self.inner.poll_peek(cx, buf) @@ -363,7 +364,7 @@ mod addr_stream { #[inline] fn poll_read( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { self.project().inner.poll_read(cx, buf) @@ -374,7 +375,7 @@ mod addr_stream { #[inline] fn poll_write( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { self.project().inner.poll_write(cx, buf) @@ -383,20 +384,20 @@ mod addr_stream { #[inline] fn poll_write_vectored( self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { self.project().inner.poll_write_vectored(cx, bufs) } #[inline] - fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { // TCP flush is a noop Poll::Ready(Ok(())) } #[inline] - fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { self.project().inner.poll_shutdown(cx) } @@ -420,8 +421,8 @@ mod addr_stream { #[cfg(test)] mod tests { - use std::time::Duration; use crate::server::tcp::TcpKeepaliveConfig; + use std::time::Duration; #[test] fn no_tcp_keepalive_config() { diff --git a/src/service/http.rs b/src/service/http.rs index 81a20c80b5..d0586d8bd2 100644 --- a/src/service/http.rs +++ b/src/service/http.rs @@ -1,7 +1,8 @@ use std::error::Error as StdError; +use std::future::Future; +use std::task::{Context, Poll}; use crate::body::HttpBody; -use crate::common::{task, Future, Poll}; use crate::{Request, Response}; /// An asynchronous function from `Request` to `Response`. @@ -20,7 +21,7 @@ pub trait HttpService: sealed::Sealed { type Future: Future, Self::Error>>; #[doc(hidden)] - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll>; #[doc(hidden)] fn call(&mut self, req: Request) -> Self::Future; @@ -37,7 +38,7 @@ where type Error = T::Error; type Future = T::Future; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { tower_service::Service::poll_ready(self, cx) } diff --git a/src/service/make.rs b/src/service/make.rs index 63e6f298f1..188e4f4c32 100644 --- a/src/service/make.rs +++ b/src/service/make.rs @@ -1,11 +1,12 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; +use std::task::{Context, Poll}; 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. @@ -14,7 +15,7 @@ pub trait MakeConnection: self::sealed::Sealed<(Target,)> { type Error; type Future: Future>; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll>; + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll>; fn make_connection(&mut self, target: Target) -> Self::Future; } @@ -29,7 +30,7 @@ where type Error = S::Error; type Future = S::Future; - fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, cx: &mut Context<'_>) -> Poll> { Service::poll_ready(self, cx) } @@ -58,7 +59,7 @@ pub trait MakeServiceRef: self::sealed::Sealed<(Target, ReqBody // if necessary. type __DontNameMe: self::sealed::CantImpl; - fn poll_ready_ref(&mut self, cx: &mut task::Context<'_>) -> Poll>; + fn poll_ready_ref(&mut self, cx: &mut Context<'_>) -> Poll>; fn make_service_ref(&mut self, target: &Target) -> Self::Future; } @@ -81,7 +82,7 @@ where type __DontNameMe = self::sealed::CantName; - fn poll_ready_ref(&mut self, cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready_ref(&mut self, cx: &mut Context<'_>) -> Poll> { self.poll_ready(cx) } @@ -159,7 +160,7 @@ where type Response = Svc; type Future = Ret; - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } diff --git a/src/service/oneshot.rs b/src/service/oneshot.rs index 2697af8f4c..5e2ca47630 100644 --- a/src/service/oneshot.rs +++ b/src/service/oneshot.rs @@ -1,10 +1,12 @@ // TODO: Eventually to be replaced with tower_util::Oneshot. +use std::future::Future; +use std::pin::Pin; +use std::task::{Context, Poll}; + use pin_project_lite::pin_project; use tower_service::Service; -use crate::common::{task, Future, Pin, Poll}; - pub(crate) fn oneshot(svc: S, req: Req) -> Oneshot where S: Service, @@ -47,7 +49,7 @@ where { type Output = Result; - fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { let mut me = self.project(); loop { diff --git a/src/service/util.rs b/src/service/util.rs index 7cba1206f1..59760a6858 100644 --- a/src/service/util.rs +++ b/src/service/util.rs @@ -1,9 +1,10 @@ use std::error::Error as StdError; use std::fmt; +use std::future::Future; use std::marker::PhantomData; +use std::task::{Context, Poll}; use crate::body::HttpBody; -use crate::common::{task, Future, Poll}; use crate::{Request, Response}; /// Create a `Service` from a function. @@ -54,7 +55,7 @@ where type Error = E; type Future = Ret; - fn poll_ready(&mut self, _cx: &mut task::Context<'_>) -> Poll> { + fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } diff --git a/src/upgrade.rs b/src/upgrade.rs index 1c7b5b01cd..a46a8d224d 100644 --- a/src/upgrade.rs +++ b/src/upgrade.rs @@ -42,8 +42,11 @@ use std::any::TypeId; use std::error::Error as StdError; use std::fmt; +use std::future::Future; use std::io; use std::marker::Unpin; +use std::pin::Pin; +use std::task::{Context, Poll}; use bytes::Bytes; use tokio::io::{AsyncRead, AsyncWrite, ReadBuf}; @@ -52,7 +55,6 @@ use tokio::sync::oneshot; use tracing::trace; use crate::common::io::Rewind; -use crate::common::{task, Future, Pin, Poll}; /// An upgraded HTTP connection. /// @@ -151,7 +153,7 @@ impl Upgraded { impl AsyncRead for Upgraded { fn poll_read( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &mut ReadBuf<'_>, ) -> Poll> { Pin::new(&mut self.io).poll_read(cx, buf) @@ -161,7 +163,7 @@ impl AsyncRead for Upgraded { impl AsyncWrite for Upgraded { fn poll_write( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, buf: &[u8], ) -> Poll> { Pin::new(&mut self.io).poll_write(cx, buf) @@ -169,17 +171,17 @@ impl AsyncWrite for Upgraded { fn poll_write_vectored( mut self: Pin<&mut Self>, - cx: &mut task::Context<'_>, + cx: &mut Context<'_>, bufs: &[io::IoSlice<'_>], ) -> Poll> { Pin::new(&mut self.io).poll_write_vectored(cx, bufs) } - fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_flush(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.io).poll_flush(cx) } - fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll> { + fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll> { Pin::new(&mut self.io).poll_shutdown(cx) } @@ -210,7 +212,7 @@ impl OnUpgrade { impl Future for OnUpgrade { type Output = Result; - fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll { + fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll { match self.rx { Some(ref mut rx) => Pin::new(rx).poll(cx).map(|res| match res { Ok(Ok(upgraded)) => Ok(upgraded), @@ -351,7 +353,7 @@ mod tests { impl AsyncRead for Mock { fn poll_read( self: Pin<&mut Self>, - _cx: &mut task::Context<'_>, + _cx: &mut Context<'_>, _buf: &mut ReadBuf<'_>, ) -> Poll> { unreachable!("Mock::poll_read") @@ -361,21 +363,18 @@ mod tests { impl AsyncWrite for Mock { fn poll_write( self: Pin<&mut Self>, - _: &mut task::Context<'_>, + _: &mut Context<'_>, buf: &[u8], ) -> Poll> { // panic!("poll_write shouldn't be called"); Poll::Ready(Ok(buf.len())) } - fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll> { + fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { unreachable!("Mock::poll_flush") } - fn poll_shutdown( - self: Pin<&mut Self>, - _cx: &mut task::Context<'_>, - ) -> Poll> { + fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll> { unreachable!("Mock::poll_shutdown") } } diff --git a/tests/client.rs b/tests/client.rs index 2953313798..9d7bf08335 100644 --- a/tests/client.rs +++ b/tests/client.rs @@ -12,6 +12,7 @@ use std::task::{Context, Poll}; use std::thread; use std::time::Duration; +#[allow(deprecated)] use hyper::body::to_bytes as concat; use hyper::{Body, Client, Method, Request, StatusCode}; @@ -3154,6 +3155,61 @@ mod conn { .expect("client should be open"); } + #[tokio::test] + async fn http2_responds_before_consuming_request_body() { + // Test that a early-response from server works correctly (request body wasn't fully consumed). + // https://github.com/hyperium/hyper/issues/2872 + use hyper::service::service_fn; + + let _ = pretty_env_logger::try_init(); + + let listener = TkTcpListener::bind(SocketAddr::from(([127, 0, 0, 1], 0))) + .await + .unwrap(); + let addr = listener.local_addr().unwrap(); + + // Spawn an HTTP2 server that responds before reading the whole request body. + // It's normal case to decline the request due to headers or size of the body. + tokio::spawn(async move { + let sock = listener.accept().await.unwrap().0; + hyper::server::conn::Http::new() + .http2_only(true) + .serve_connection( + sock, + service_fn(|_req| async move { + Ok::<_, hyper::Error>(http::Response::new(hyper::Body::from( + "No bread for you!", + ))) + }), + ) + .await + .expect("serve_connection"); + }); + + let io = tcp_connect(&addr).await.expect("tcp connect"); + let (mut client, conn) = conn::Builder::new() + .http2_only(true) + .handshake::<_, Body>(io) + .await + .expect("http handshake"); + + tokio::spawn(async move { + conn.await.expect("client conn shouldn't error"); + }); + + // Use a channel to keep request stream open + let (_tx, body) = hyper::Body::channel(); + let req = Request::post("/a").body(body).unwrap(); + let resp = client.send_request(req).await.expect("send_request"); + assert!(resp.status().is_success()); + + let body = concat(resp.into_body()) + .await + .expect("get response body with no error"); + + assert_eq!(body.as_ref(), b"No bread for you!"); + } + #[tokio::test] async fn h2_connect() { let _ = pretty_env_logger::try_init(); diff --git a/tests/integration.rs b/tests/integration.rs index 2deee443f8..9e094cc713 100644 --- a/tests/integration.rs +++ b/tests/integration.rs @@ -305,6 +305,48 @@ t! { ; } +t! { + h2_connect_authority_form, + client: + request: + method: "CONNECT", + // http2 should strip scheme and path from URI (authority-form) + uri: "/connect_normal", + ; + response: + ; + server: + request: + method: "CONNECT", + // path should be stripped + uri: "", + ; + response: + ; +} + +t! { + h2_only; + h2_extended_connect_full_uri, + client: + request: + method: "CONNECT", + // http2 should not strip scheme and path from URI for extended CONNECT requests + uri: "/connect_extended", + protocol: "the-bread-protocol", + ; + response: + ; + server: + request: + method: "CONNECT", + // path should not be stripped + uri: "/connect_extended", + ; + response: + ; +} + t! { get_2, client: diff --git a/tests/support/mod.rs b/tests/support/mod.rs index 6b3c8f4472..5a641faaf8 100644 --- a/tests/support/mod.rs +++ b/tests/support/mod.rs @@ -13,14 +13,18 @@ use hyper::{Body, Client, Request, Response, Server, Version}; pub use futures_util::{ future, FutureExt as _, StreamExt as _, TryFutureExt as _, TryStreamExt as _, }; -pub use hyper::{HeaderMap, StatusCode}; +pub use hyper::{ext::Protocol, HeaderMap}; +#[allow(unused_imports)] +pub use hyper::{http::Extensions, StatusCode}; pub use std::net::SocketAddr; #[allow(unused_macros)] macro_rules! t { ( + @impl $name:ident, - parallel: $range:expr + parallel: $range:expr, + $(h2_only: $_h2_only:expr)? ) => ( #[test] fn $name() { @@ -75,6 +79,7 @@ macro_rules! t { } ); ( + @impl $name:ident, client: $( request: $( @@ -91,7 +96,8 @@ macro_rules! t { response: $( $s_res_prop:ident: $s_res_val:tt, )*; - )* + )*, + h2_only: $h2_only:expr ) => ( #[test] fn $name() { @@ -116,15 +122,17 @@ macro_rules! t { } ),)*]; - __run_test(__TestConfig { - client_version: 1, - client_msgs: c.clone(), - server_version: 1, - server_msgs: s.clone(), - parallel: false, - connections: 1, - proxy: false, - }); + if !$h2_only { + __run_test(__TestConfig { + client_version: 1, + client_msgs: c.clone(), + server_version: 1, + server_msgs: s.clone(), + parallel: false, + connections: 1, + proxy: false, + }); + } __run_test(__TestConfig { client_version: 2, @@ -136,15 +144,17 @@ macro_rules! t { proxy: false, }); - __run_test(__TestConfig { - client_version: 1, - client_msgs: c.clone(), - server_version: 1, - server_msgs: s.clone(), - parallel: false, - connections: 1, - proxy: true, - }); + if !$h2_only { + __run_test(__TestConfig { + client_version: 1, + client_msgs: c.clone(), + server_version: 1, + server_msgs: s.clone(), + parallel: false, + connections: 1, + proxy: true, + }); + } __run_test(__TestConfig { client_version: 2, @@ -157,6 +167,12 @@ macro_rules! t { }); } ); + (h2_only; $($t:tt)*) => { + t!(@impl $($t)*, h2_only: true); + }; + ($($t:tt)*) => { + t!(@impl $($t)*, h2_only: false); + }; } macro_rules! __internal_map_prop { @@ -245,6 +261,7 @@ pub struct __CReq { pub uri: &'static str, pub headers: HeaderMap, pub body: Vec, + pub protocol: Option<&'static str>, } impl Default for __CReq { @@ -254,6 +271,7 @@ impl Default for __CReq { uri: "/", headers: HeaderMap::new(), body: Vec::new(), + protocol: None, } } } @@ -356,6 +374,7 @@ async fn async_test(cfg: __TestConfig) { func(&req.headers()); } let sbody = sreq.body; + #[allow(deprecated)] hyper::body::to_bytes(req).map_ok(move |body| { assert_eq!(body.as_ref(), sbody.as_slice(), "client body"); @@ -371,6 +390,7 @@ async fn async_test(cfg: __TestConfig) { let server = hyper::Server::bind(&SocketAddr::from(([127, 0, 0, 1], 0))) .http2_only(cfg.server_version == 2) + .http2_enable_connect_protocol() .serve(new_service); let mut addr = server.local_addr(); @@ -398,6 +418,9 @@ async fn async_test(cfg: __TestConfig) { //.headers(creq.headers) .body(creq.body.into()) .expect("Request::build"); + if let Some(protocol) = creq.protocol { + req.extensions_mut().insert(Protocol::from_static(protocol)); + } *req.headers_mut() = creq.headers; let cstatus = cres.status; let cheaders = cres.headers; @@ -411,6 +434,7 @@ async fn async_test(cfg: __TestConfig) { for func in &cheaders { func(&res.headers()); } + #[allow(deprecated)] hyper::body::to_bytes(res) }) .map_ok(move |body| { @@ -458,18 +482,20 @@ fn naive_proxy(cfg: ProxyConfig) -> (SocketAddr, impl Future) { let max_connections = cfg.connections; let counter = AtomicUsize::new(0); - let srv = Server::bind(&([127, 0, 0, 1], 0).into()).serve(make_service_fn(move |_| { - let prev = counter.fetch_add(1, Ordering::Relaxed); - assert!(max_connections > prev, "proxy max connections"); - let client = client.clone(); - future::ok::<_, hyper::Error>(service_fn(move |mut req| { - let uri = format!("http://{}{}", dst_addr, req.uri().path()) - .parse() - .expect("proxy new uri parse"); - *req.uri_mut() = uri; - client.request(req) - })) - })); + let srv = Server::bind(&([127, 0, 0, 1], 0).into()) + .http2_enable_connect_protocol() + .serve(make_service_fn(move |_| { + let prev = counter.fetch_add(1, Ordering::Relaxed); + assert!(max_connections > prev, "proxy max connections"); + let client = client.clone(); + future::ok::<_, hyper::Error>(service_fn(move |mut req| { + let uri = format!("http://{}{}", dst_addr, req.uri().path()) + .parse() + .expect("proxy new uri parse"); + *req.uri_mut() = uri; + client.request(req) + })) + })); let proxy_addr = srv.local_addr(); (proxy_addr, srv.map(|res| res.expect("proxy error"))) }