mirror of
https://github.com/Noratrieb/icefun.git
synced 2026-03-14 16:16:03 +01:00
private
This commit is contained in:
parent
25adea4103
commit
7af1274587
160 changed files with 38999 additions and 4 deletions
4
Cargo.lock
generated
4
Cargo.lock
generated
|
|
@ -271,8 +271,6 @@ checksum = "c4a1e36c821dbe04574f602848a19f742f4fb3c98d40449f11bcad18d6b17421"
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "hyper"
|
name = "hyper"
|
||||||
version = "0.14.24"
|
version = "0.14.24"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "5e011372fa0b68db8350aa7a248930ecc7839bf46d8485577d69f117a75f164c"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
|
@ -634,8 +632,6 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "warp"
|
name = "warp"
|
||||||
version = "0.3.3"
|
version = "0.3.3"
|
||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
|
||||||
checksum = "ed7b8be92646fc3d18b06147664ebc5f48d222686cb11a8755e561a735aacc6d"
|
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bytes",
|
"bytes",
|
||||||
"futures-channel",
|
"futures-channel",
|
||||||
|
|
|
||||||
|
|
@ -10,3 +10,8 @@ futures = "0"
|
||||||
hyper = { version = "0", default-features = false }
|
hyper = { version = "0", default-features = false }
|
||||||
tokio = { version = "1", default-features = false }
|
tokio = { version = "1", default-features = false }
|
||||||
warp = { version = "0", default-features = false }
|
warp = { version = "0", default-features = false }
|
||||||
|
|
||||||
|
|
||||||
|
[patch.crates-io]
|
||||||
|
warp = { path = "./warp" }
|
||||||
|
hyper = { path = "./hyper" }
|
||||||
28
hyper/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
28
hyper/.github/ISSUE_TEMPLATE/bug_report.md
vendored
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
---
|
||||||
|
name: "Bug report \U0001F41B"
|
||||||
|
about: Create a report to help us improve
|
||||||
|
title: ''
|
||||||
|
labels: S-bug
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Version**
|
||||||
|
List the version(s) of `hyper`, and any relevant hyper dependency (such as `h2` if this is related to HTTP/2).
|
||||||
|
|
||||||
|
**Platform**
|
||||||
|
The output of `uname -a` (UNIX), or version and 32 or 64-bit (Windows)
|
||||||
|
|
||||||
|
**Description**
|
||||||
|
Enter your issue details here.
|
||||||
|
One way to structure the description:
|
||||||
|
|
||||||
|
[short summary of the bug]
|
||||||
|
|
||||||
|
I tried this code:
|
||||||
|
|
||||||
|
[code sample that causes the bug]
|
||||||
|
|
||||||
|
I expected to see this happen: [explanation]
|
||||||
|
|
||||||
|
Instead, this happened: [explanation]
|
||||||
20
hyper/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
20
hyper/.github/ISSUE_TEMPLATE/feature_request.md
vendored
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
---
|
||||||
|
name: "Feature request \U0001F4A1"
|
||||||
|
about: Suggest an idea for this project
|
||||||
|
title: ''
|
||||||
|
labels: S-feature
|
||||||
|
assignees: ''
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**Is your feature request related to a problem? Please describe.**
|
||||||
|
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
|
||||||
|
|
||||||
|
**Describe the solution you'd like**
|
||||||
|
A clear and concise description of what you want to happen.
|
||||||
|
|
||||||
|
**Describe alternatives you've considered**
|
||||||
|
A clear and concise description of any alternative solutions or features you've considered.
|
||||||
|
|
||||||
|
**Additional context**
|
||||||
|
Add any other context or screenshots about the feature request here.
|
||||||
0
hyper/.github/PULL_REQUEST_TEMPLATE
vendored
Normal file
0
hyper/.github/PULL_REQUEST_TEMPLATE
vendored
Normal file
262
hyper/.github/workflows/CI.yml
vendored
Normal file
262
hyper/.github/workflows/CI.yml
vendored
Normal file
|
|
@ -0,0 +1,262 @@
|
||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
env:
|
||||||
|
RUST_BACKTRACE: 1
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci-pass:
|
||||||
|
name: CI is green
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
needs:
|
||||||
|
- style
|
||||||
|
- test
|
||||||
|
- msrv
|
||||||
|
- miri
|
||||||
|
- features
|
||||||
|
- ffi
|
||||||
|
- ffi-header
|
||||||
|
- doc
|
||||||
|
steps:
|
||||||
|
- run: exit 0
|
||||||
|
|
||||||
|
style:
|
||||||
|
name: Check Style
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: stable
|
||||||
|
override: true
|
||||||
|
components: rustfmt
|
||||||
|
|
||||||
|
- name: cargo fmt --check
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: fmt
|
||||||
|
args: --all -- --check
|
||||||
|
|
||||||
|
test:
|
||||||
|
name: Test ${{ matrix.rust }} on ${{ matrix.os }}
|
||||||
|
needs: [style]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- stable
|
||||||
|
- beta
|
||||||
|
- nightly
|
||||||
|
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
- windows-latest
|
||||||
|
- macOS-latest
|
||||||
|
|
||||||
|
include:
|
||||||
|
- rust: stable
|
||||||
|
features: "--features full"
|
||||||
|
- rust: beta
|
||||||
|
features: "--features full"
|
||||||
|
- rust: nightly
|
||||||
|
features: "--features full,nightly"
|
||||||
|
benches: true
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust (${{ matrix.rust }})
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: ${{ matrix.features }}
|
||||||
|
|
||||||
|
- name: Test all benches
|
||||||
|
if: matrix.benches
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: test
|
||||||
|
args: --benches ${{ matrix.features }}
|
||||||
|
|
||||||
|
msrv:
|
||||||
|
name: Check MSRV (${{ matrix.rust }})
|
||||||
|
needs: [style]
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
rust:
|
||||||
|
- 1.56 # keep in sync with MSRV.md dev doc
|
||||||
|
|
||||||
|
os:
|
||||||
|
- ubuntu-latest
|
||||||
|
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust (${{ matrix.rust }})
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: ${{ matrix.rust }}
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Check
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: check
|
||||||
|
args: --features full
|
||||||
|
|
||||||
|
miri:
|
||||||
|
name: Test with Miri
|
||||||
|
needs: [style]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
components: miri
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Test
|
||||||
|
# Can't enable tcp feature since Miri does not support the tokio runtime
|
||||||
|
run: MIRIFLAGS="-Zmiri-disable-isolation" cargo miri test --features http1,http2,client,server,stream,nightly
|
||||||
|
|
||||||
|
features:
|
||||||
|
name: features
|
||||||
|
needs: [style]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install cargo-hack
|
||||||
|
run: cargo install cargo-hack
|
||||||
|
|
||||||
|
- name: check --feature-powerset
|
||||||
|
run: cargo hack check --feature-powerset --depth 2 --skip ffi -Z avoid-dev-deps
|
||||||
|
|
||||||
|
ffi:
|
||||||
|
name: Test C API (FFI)
|
||||||
|
needs: [style]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- name: Install cbindgen
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cbindgen
|
||||||
|
|
||||||
|
- name: Build FFI
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
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
|
||||||
|
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
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
default: true
|
||||||
|
override: true
|
||||||
|
components: cargo
|
||||||
|
|
||||||
|
- name: Install cbindgen
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
with:
|
||||||
|
command: install
|
||||||
|
args: cbindgen
|
||||||
|
|
||||||
|
- name: Build FFI
|
||||||
|
uses: actions-rs/cargo@v1
|
||||||
|
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
|
||||||
|
|
||||||
|
doc:
|
||||||
|
name: Build docs
|
||||||
|
needs: [style, test]
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v1
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
|
||||||
|
- 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
|
||||||
58
hyper/.github/workflows/bench.yml
vendored
Normal file
58
hyper/.github/workflows/bench.yml
vendored
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
name: Benchmark
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
benchmark:
|
||||||
|
name: Benchmark
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
bench:
|
||||||
|
- connect
|
||||||
|
- end_to_end
|
||||||
|
- pipeline
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
|
- name: Install Rust
|
||||||
|
uses: actions-rs/toolchain@v1
|
||||||
|
with:
|
||||||
|
profile: minimal
|
||||||
|
toolchain: nightly
|
||||||
|
override: true
|
||||||
|
|
||||||
|
# Run benchmark and stores the output to a file
|
||||||
|
- name: Run benchmark
|
||||||
|
run: cargo bench --features full --bench ${{ matrix.bench }} | tee output.txt
|
||||||
|
|
||||||
|
# Download previous benchmark result from cache (if exists)
|
||||||
|
- name: Download previous benchmark data
|
||||||
|
uses: actions/cache@v1
|
||||||
|
with:
|
||||||
|
path: ./cache
|
||||||
|
key: ${{ runner.os }}-benchmark
|
||||||
|
|
||||||
|
# Run `github-action-benchmark` action
|
||||||
|
- name: Store benchmark result
|
||||||
|
uses: seanmonstar/github-action-benchmark@v1-patch-1
|
||||||
|
with:
|
||||||
|
name: ${{ matrix.bench }}
|
||||||
|
# What benchmark tool the output.txt came from
|
||||||
|
tool: 'cargo'
|
||||||
|
# Where the output from the benchmark tool is stored
|
||||||
|
output-file-path: output.txt
|
||||||
|
# # Where the previous data file is stored
|
||||||
|
# external-data-json-path: ./cache/benchmark-data.json
|
||||||
|
# Workflow will fail when an alert happens
|
||||||
|
fail-on-alert: true
|
||||||
|
# GitHub API token to make a commit comment
|
||||||
|
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
# Enable alert commit comment
|
||||||
|
comment-on-alert: true
|
||||||
|
#alert-comment-cc-users: '@seanmonstar'
|
||||||
|
auto-push: true
|
||||||
|
|
||||||
|
# Upload the updated cache file for the next job by actions/cache
|
||||||
2
hyper/.gitignore
vendored
Normal file
2
hyper/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
target
|
||||||
|
Cargo.lock
|
||||||
3031
hyper/CHANGELOG.md
Normal file
3031
hyper/CHANGELOG.md
Normal file
File diff suppressed because it is too large
Load diff
11
hyper/CONTRIBUTING.md
Normal file
11
hyper/CONTRIBUTING.md
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
# Contributing to Hyper
|
||||||
|
|
||||||
|
You want to contribute? You're awesome! Don't know where to start? Check the [list of easy issues](https://github.com/hyperium/hyper/issues?q=is%3Aopen+is%3Aissue+label%3AE-easy).
|
||||||
|
|
||||||
|
[easy tag]: https://github.com/hyperium/hyper/issues?q=label%3AE-easy+is%3Aopen
|
||||||
|
|
||||||
|
|
||||||
|
## [Pull Requests](./docs/PULL_REQUESTS.md)
|
||||||
|
|
||||||
|
- [Submitting a Pull Request](./docs/PULL_REQUESTS.md#submitting-a-pull-request)
|
||||||
|
- [Commit Guidelines](./docs/COMMITS.md)
|
||||||
252
hyper/Cargo.toml
Normal file
252
hyper/Cargo.toml
Normal file
|
|
@ -0,0 +1,252 @@
|
||||||
|
[package]
|
||||||
|
name = "hyper"
|
||||||
|
version = "0.14.24"
|
||||||
|
description = "A fast and correct HTTP library."
|
||||||
|
readme = "README.md"
|
||||||
|
homepage = "https://hyper.rs"
|
||||||
|
documentation = "https://docs.rs/hyper"
|
||||||
|
repository = "https://github.com/hyperium/hyper"
|
||||||
|
license = "MIT"
|
||||||
|
authors = ["Sean McArthur <sean@seanmonstar.com>"]
|
||||||
|
keywords = ["http", "hyper", "hyperium"]
|
||||||
|
categories = ["network-programming", "web-programming::http-client", "web-programming::http-server"]
|
||||||
|
edition = "2018"
|
||||||
|
|
||||||
|
include = [
|
||||||
|
"Cargo.toml",
|
||||||
|
"LICENSE",
|
||||||
|
"src/**/*",
|
||||||
|
#"build.rs",
|
||||||
|
]
|
||||||
|
|
||||||
|
[dependencies]
|
||||||
|
bytes = "1"
|
||||||
|
futures-core = { version = "0.3", default-features = false }
|
||||||
|
futures-channel = "0.3"
|
||||||
|
futures-util = { version = "0.3", default-features = false }
|
||||||
|
http = "0.2"
|
||||||
|
http-body = "0.4"
|
||||||
|
httpdate = "1.0"
|
||||||
|
httparse = "1.8"
|
||||||
|
h2 = { version = "0.3.9", optional = true }
|
||||||
|
itoa = "1"
|
||||||
|
tracing = { version = "0.1", default-features = false, features = ["std"] }
|
||||||
|
pin-project-lite = "0.2.4"
|
||||||
|
tower-service = "0.3"
|
||||||
|
tokio = { version = "1", features = ["sync"] }
|
||||||
|
want = "0.3"
|
||||||
|
|
||||||
|
# Optional
|
||||||
|
|
||||||
|
libc = { version = "0.2", optional = true }
|
||||||
|
socket2 = { version = "0.4.7", optional = true, features = ["all"] }
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
futures-util = { version = "0.3", default-features = false, features = ["alloc"] }
|
||||||
|
matches = "0.1"
|
||||||
|
num_cpus = "1.0"
|
||||||
|
pretty_env_logger = "0.4"
|
||||||
|
spmc = "0.3"
|
||||||
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
|
serde_json = "1.0"
|
||||||
|
tokio = { version = "1", features = [
|
||||||
|
"fs",
|
||||||
|
"macros",
|
||||||
|
"io-std",
|
||||||
|
"io-util",
|
||||||
|
"rt",
|
||||||
|
"rt-multi-thread", # so examples can use #[tokio::main]
|
||||||
|
"sync",
|
||||||
|
"time",
|
||||||
|
"test-util",
|
||||||
|
] }
|
||||||
|
tokio-test = "0.4"
|
||||||
|
tokio-util = { version = "0.7", features = ["codec"] }
|
||||||
|
tower = { version = "0.4", features = ["make", "util"] }
|
||||||
|
url = "2.2"
|
||||||
|
|
||||||
|
[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dev-dependencies]
|
||||||
|
pnet_datalink = "0.27.2"
|
||||||
|
|
||||||
|
[features]
|
||||||
|
# Nothing by default
|
||||||
|
default = []
|
||||||
|
|
||||||
|
# Easily turn it all on
|
||||||
|
full = [
|
||||||
|
"client",
|
||||||
|
"http1",
|
||||||
|
"http2",
|
||||||
|
"server",
|
||||||
|
"stream",
|
||||||
|
"runtime",
|
||||||
|
]
|
||||||
|
|
||||||
|
# HTTP versions
|
||||||
|
http1 = []
|
||||||
|
http2 = ["h2"]
|
||||||
|
|
||||||
|
# Client/Server
|
||||||
|
client = []
|
||||||
|
server = []
|
||||||
|
|
||||||
|
# `impl Stream` for things
|
||||||
|
stream = []
|
||||||
|
|
||||||
|
# Tokio support
|
||||||
|
runtime = [
|
||||||
|
"tcp",
|
||||||
|
"tokio/rt",
|
||||||
|
"tokio/time",
|
||||||
|
]
|
||||||
|
tcp = [
|
||||||
|
"socket2",
|
||||||
|
"tokio/net",
|
||||||
|
"tokio/rt",
|
||||||
|
"tokio/time",
|
||||||
|
]
|
||||||
|
|
||||||
|
# C-API support (currently unstable (no semver))
|
||||||
|
ffi = ["libc"]
|
||||||
|
|
||||||
|
# internal features used in CI
|
||||||
|
nightly = []
|
||||||
|
__internal_happy_eyeballs_tests = []
|
||||||
|
|
||||||
|
[package.metadata.docs.rs]
|
||||||
|
features = ["ffi", "full"]
|
||||||
|
rustdoc-args = ["--cfg", "docsrs", "--cfg", "hyper_unstable_ffi"]
|
||||||
|
|
||||||
|
[package.metadata.playground]
|
||||||
|
features = ["full"]
|
||||||
|
|
||||||
|
[profile.release]
|
||||||
|
codegen-units = 1
|
||||||
|
incremental = false
|
||||||
|
|
||||||
|
[profile.bench]
|
||||||
|
codegen-units = 1
|
||||||
|
incremental = false
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "client"
|
||||||
|
path = "examples/client.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "client_json"
|
||||||
|
path = "examples/client_json.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "echo"
|
||||||
|
path = "examples/echo.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "gateway"
|
||||||
|
path = "examples/gateway.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "hello"
|
||||||
|
path = "examples/hello.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "http_proxy"
|
||||||
|
path = "examples/http_proxy.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "multi_server"
|
||||||
|
path = "examples/multi_server.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "params"
|
||||||
|
path = "examples/params.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "send_file"
|
||||||
|
path = "examples/send_file.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "service_struct_impl"
|
||||||
|
path = "examples/service_struct_impl.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "single_threaded"
|
||||||
|
path = "examples/single_threaded.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "state"
|
||||||
|
path = "examples/state.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "tower_client"
|
||||||
|
path = "examples/tower_client.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "tower_server"
|
||||||
|
path = "examples/tower_server.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "upgrades"
|
||||||
|
path = "examples/upgrades.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
|
||||||
|
[[example]]
|
||||||
|
name = "web_api"
|
||||||
|
path = "examples/web_api.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "body"
|
||||||
|
path = "benches/body.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "connect"
|
||||||
|
path = "benches/connect.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "end_to_end"
|
||||||
|
path = "benches/end_to_end.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "pipeline"
|
||||||
|
path = "benches/pipeline.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[bench]]
|
||||||
|
name = "server"
|
||||||
|
path = "benches/server.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "client"
|
||||||
|
path = "tests/client.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "integration"
|
||||||
|
path = "tests/integration.rs"
|
||||||
|
required-features = ["full"]
|
||||||
|
|
||||||
|
[[test]]
|
||||||
|
name = "server"
|
||||||
|
path = "tests/server.rs"
|
||||||
|
required-features = ["full"]
|
||||||
19
hyper/LICENSE
Normal file
19
hyper/LICENSE
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
Copyright (c) 2014-2021 Sean McArthur
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in
|
||||||
|
all copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
||||||
|
THE SOFTWARE.
|
||||||
41
hyper/README.md
Normal file
41
hyper/README.md
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
# [hyper](https://hyper.rs)
|
||||||
|
|
||||||
|
[](https://crates.io/crates/hyper)
|
||||||
|
[](https://docs.rs/hyper)
|
||||||
|
[](./LICENSE)
|
||||||
|
[](https://github.com/hyperium/hyper/actions?query=workflow%3ACI)
|
||||||
|
[![Discord chat][discord-badge]][discord-url]
|
||||||
|
|
||||||
|
A **fast** and **correct** HTTP implementation for Rust.
|
||||||
|
|
||||||
|
- HTTP/1 and HTTP/2
|
||||||
|
- Asynchronous design
|
||||||
|
- Leading in performance
|
||||||
|
- Tested and **correct**
|
||||||
|
- Extensive production use
|
||||||
|
- Client and Server APIs
|
||||||
|
|
||||||
|
**Get started** by looking over the [guides](https://hyper.rs/guides).
|
||||||
|
|
||||||
|
## "Low-level"
|
||||||
|
|
||||||
|
hyper is a relatively low-level library, meant to be a building block for
|
||||||
|
libraries and applications.
|
||||||
|
|
||||||
|
If you are looking for a convenient HTTP client, then you may wish to consider
|
||||||
|
[reqwest](https://github.com/seanmonstar/reqwest). If you are looking for a
|
||||||
|
convenient HTTP server, then you may wish to consider [warp](https://github.com/seanmonstar/warp).
|
||||||
|
Both are built on top of this library.
|
||||||
|
|
||||||
|
## Contributing
|
||||||
|
|
||||||
|
To get involved, take a look at [CONTRIBUTING](CONTRIBUTING.md).
|
||||||
|
|
||||||
|
If you prefer chatting, there is an active community in the [Discord server][discord-url].
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
hyper is provided under the MIT license. See [LICENSE](LICENSE).
|
||||||
|
|
||||||
|
[discord-badge]: https://img.shields.io/discord/500028886025895936.svg?logo=discord
|
||||||
|
[discord-url]: https://discord.gg/kkwpueZ
|
||||||
9
hyper/SECURITY.md
Normal file
9
hyper/SECURITY.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Security Policy
|
||||||
|
|
||||||
|
hyper (and related projects in hyperium) use the same security policy as the [Tokio project][tokio-security].
|
||||||
|
|
||||||
|
## Report a security issue
|
||||||
|
|
||||||
|
The process for reporting an issue is the same as the [Tokio project][tokio-security]. This includes private reporting via security@tokio.rs.
|
||||||
|
|
||||||
|
[tokio-security]: https://github.com/tokio-rs/tokio/security/policy
|
||||||
88
hyper/benches/body.rs
Normal file
88
hyper/benches/body.rs
Normal file
|
|
@ -0,0 +1,88 @@
|
||||||
|
#![feature(test)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use bytes::Buf;
|
||||||
|
use futures_util::stream;
|
||||||
|
use futures_util::StreamExt;
|
||||||
|
use hyper::body::Body;
|
||||||
|
|
||||||
|
macro_rules! bench_stream {
|
||||||
|
($bencher:ident, bytes: $bytes:expr, count: $count:expr, $total_ident:ident, $body_pat:pat, $block:expr) => {{
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.build()
|
||||||
|
.expect("rt build");
|
||||||
|
|
||||||
|
let $total_ident: usize = $bytes * $count;
|
||||||
|
$bencher.bytes = $total_ident as u64;
|
||||||
|
let __s: &'static [&'static [u8]] = &[&[b'x'; $bytes] as &[u8]; $count] as _;
|
||||||
|
|
||||||
|
$bencher.iter(|| {
|
||||||
|
rt.block_on(async {
|
||||||
|
let $body_pat = Body::wrap_stream(
|
||||||
|
stream::iter(__s.iter()).map(|&s| Ok::<_, std::convert::Infallible>(s)),
|
||||||
|
);
|
||||||
|
$block;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! benches {
|
||||||
|
($($name:ident, $bytes:expr, $count:expr;)+) => (
|
||||||
|
mod aggregate {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[bench]
|
||||||
|
fn $name(b: &mut test::Bencher) {
|
||||||
|
bench_stream!(b, bytes: $bytes, count: $count, total, body, {
|
||||||
|
let buf = hyper::body::aggregate(body).await.unwrap();
|
||||||
|
assert_eq!(buf.remaining(), total);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
|
||||||
|
mod manual_into_vec {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[bench]
|
||||||
|
fn $name(b: &mut test::Bencher) {
|
||||||
|
bench_stream!(b, bytes: $bytes, count: $count, total, mut body, {
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
while let Some(chunk) = body.next().await {
|
||||||
|
vec.extend_from_slice(&chunk.unwrap());
|
||||||
|
}
|
||||||
|
assert_eq!(vec.len(), total);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
|
||||||
|
mod to_bytes {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
$(
|
||||||
|
#[bench]
|
||||||
|
fn $name(b: &mut test::Bencher) {
|
||||||
|
bench_stream!(b, bytes: $bytes, count: $count, total, body, {
|
||||||
|
let bytes = hyper::body::to_bytes(body).await.unwrap();
|
||||||
|
assert_eq!(bytes.len(), total);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
)+
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== Actual Benchmarks =====
|
||||||
|
|
||||||
|
benches! {
|
||||||
|
bytes_1_000_count_2, 1_000, 2;
|
||||||
|
bytes_1_000_count_10, 1_000, 10;
|
||||||
|
bytes_10_000_count_1, 10_000, 1;
|
||||||
|
bytes_10_000_count_10, 10_000, 10;
|
||||||
|
}
|
||||||
37
hyper/benches/connect.rs
Normal file
37
hyper/benches/connect.rs
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
#![feature(test)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use http::Uri;
|
||||||
|
use hyper::client::connect::HttpConnector;
|
||||||
|
use hyper::service::Service;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
use tokio::net::TcpListener;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http_connector(b: &mut test::Bencher) {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("rt build");
|
||||||
|
let listener = rt
|
||||||
|
.block_on(TcpListener::bind(&SocketAddr::from(([127, 0, 0, 1], 0))))
|
||||||
|
.expect("bind");
|
||||||
|
let addr = listener.local_addr().expect("local_addr");
|
||||||
|
let dst: Uri = format!("http://{}/", addr).parse().expect("uri parse");
|
||||||
|
let mut connector = HttpConnector::new();
|
||||||
|
|
||||||
|
rt.spawn(async move {
|
||||||
|
loop {
|
||||||
|
let _ = listener.accept().await;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
rt.block_on(async {
|
||||||
|
connector.call(dst.clone()).await.expect("connect");
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
382
hyper/benches/end_to_end.rs
Normal file
382
hyper/benches/end_to_end.rs
Normal file
|
|
@ -0,0 +1,382 @@
|
||||||
|
#![feature(test)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
|
||||||
|
use futures_util::future::join_all;
|
||||||
|
|
||||||
|
use hyper::client::HttpConnector;
|
||||||
|
use hyper::{body::HttpBody as _, Body, Method, Request, Response, Server};
|
||||||
|
|
||||||
|
// HTTP1
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||||
|
opts().bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||||
|
opts()
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(&[b's'; 10])
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_consecutive_x1_both_100kb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 100];
|
||||||
|
opts()
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(body)
|
||||||
|
.response_body(body)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_consecutive_x1_both_10mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(body)
|
||||||
|
.response_body(body)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_parallel_x10_empty(b: &mut test::Bencher) {
|
||||||
|
opts().parallel(10).bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.parallel(10)
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(body)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.parallel(10)
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_chunks(body, 100)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 1];
|
||||||
|
opts().parallel(10).response_body(body).bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http1_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
|
opts().parallel(10).response_body(body).bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HTTP2
|
||||||
|
|
||||||
|
const HTTP2_MAX_WINDOW: u32 = std::u32::MAX >> 1;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_consecutive_x1_empty(b: &mut test::Bencher) {
|
||||||
|
opts().http2().bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_consecutive_x1_req_10b(b: &mut test::Bencher) {
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(&[b's'; 10])
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_consecutive_x1_req_100kb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 100];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(body)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_empty(b: &mut test::Bencher) {
|
||||||
|
opts().http2().parallel(10).bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_req_10mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.parallel(10)
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_body(body)
|
||||||
|
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
|
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_req_10kb_100_chunks(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.parallel(10)
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_chunks(body, 100)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_req_10kb_100_chunks_adaptive_window(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.parallel(10)
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_chunks(body, 100)
|
||||||
|
.http2_adaptive_window()
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_req_10kb_100_chunks_max_window(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.parallel(10)
|
||||||
|
.method(Method::POST)
|
||||||
|
.request_chunks(body, 100)
|
||||||
|
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
|
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_res_1mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 1];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.parallel(10)
|
||||||
|
.response_body(body)
|
||||||
|
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
|
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn http2_parallel_x10_res_10mb(b: &mut test::Bencher) {
|
||||||
|
let body = &[b'x'; 1024 * 1024 * 10];
|
||||||
|
opts()
|
||||||
|
.http2()
|
||||||
|
.parallel(10)
|
||||||
|
.response_body(body)
|
||||||
|
.http2_stream_window(HTTP2_MAX_WINDOW)
|
||||||
|
.http2_conn_window(HTTP2_MAX_WINDOW)
|
||||||
|
.bench(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ==== Benchmark Options =====
|
||||||
|
|
||||||
|
struct Opts {
|
||||||
|
http2: bool,
|
||||||
|
http2_stream_window: Option<u32>,
|
||||||
|
http2_conn_window: Option<u32>,
|
||||||
|
http2_adaptive_window: bool,
|
||||||
|
parallel_cnt: u32,
|
||||||
|
request_method: Method,
|
||||||
|
request_body: Option<&'static [u8]>,
|
||||||
|
request_chunks: usize,
|
||||||
|
response_body: &'static [u8],
|
||||||
|
}
|
||||||
|
|
||||||
|
fn opts() -> Opts {
|
||||||
|
Opts {
|
||||||
|
http2: false,
|
||||||
|
http2_stream_window: None,
|
||||||
|
http2_conn_window: None,
|
||||||
|
http2_adaptive_window: false,
|
||||||
|
parallel_cnt: 1,
|
||||||
|
request_method: Method::GET,
|
||||||
|
request_body: None,
|
||||||
|
request_chunks: 0,
|
||||||
|
response_body: b"",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Opts {
|
||||||
|
fn http2(mut self) -> Self {
|
||||||
|
self.http2 = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http2_stream_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||||
|
assert!(!self.http2_adaptive_window);
|
||||||
|
self.http2_stream_window = sz.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http2_conn_window(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||||
|
assert!(!self.http2_adaptive_window);
|
||||||
|
self.http2_conn_window = sz.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn http2_adaptive_window(mut self) -> Self {
|
||||||
|
assert!(self.http2_stream_window.is_none());
|
||||||
|
assert!(self.http2_conn_window.is_none());
|
||||||
|
self.http2_adaptive_window = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn method(mut self, m: Method) -> Self {
|
||||||
|
self.request_method = m;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_body(mut self, body: &'static [u8]) -> Self {
|
||||||
|
self.request_body = Some(body);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn request_chunks(mut self, chunk: &'static [u8], cnt: usize) -> Self {
|
||||||
|
assert!(cnt > 0);
|
||||||
|
self.request_body = Some(chunk);
|
||||||
|
self.request_chunks = cnt;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn response_body(mut self, body: &'static [u8]) -> Self {
|
||||||
|
self.response_body = body;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parallel(mut self, cnt: u32) -> Self {
|
||||||
|
assert!(cnt > 0, "parallel count must be larger than 0");
|
||||||
|
self.parallel_cnt = cnt;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
fn bench(self, b: &mut test::Bencher) {
|
||||||
|
use std::sync::Arc;
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
// Create a runtime of current thread.
|
||||||
|
let rt = Arc::new(
|
||||||
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("rt build"),
|
||||||
|
);
|
||||||
|
let exec = rt.clone();
|
||||||
|
|
||||||
|
let req_len = self.request_body.map(|b| b.len()).unwrap_or(0) as u64;
|
||||||
|
let req_len = if self.request_chunks > 0 {
|
||||||
|
req_len * self.request_chunks as u64
|
||||||
|
} else {
|
||||||
|
req_len
|
||||||
|
};
|
||||||
|
let bytes_per_iter = (req_len + self.response_body.len() as u64) * self.parallel_cnt as u64;
|
||||||
|
b.bytes = bytes_per_iter;
|
||||||
|
|
||||||
|
let addr = spawn_server(&rt, &self);
|
||||||
|
|
||||||
|
let connector = HttpConnector::new();
|
||||||
|
let client = hyper::Client::builder()
|
||||||
|
.http2_only(self.http2)
|
||||||
|
.http2_initial_stream_window_size(self.http2_stream_window)
|
||||||
|
.http2_initial_connection_window_size(self.http2_conn_window)
|
||||||
|
.http2_adaptive_window(self.http2_adaptive_window)
|
||||||
|
.build::<_, Body>(connector);
|
||||||
|
|
||||||
|
let url: hyper::Uri = format!("http://{}/hello", addr).parse().unwrap();
|
||||||
|
|
||||||
|
let make_request = || {
|
||||||
|
let chunk_cnt = self.request_chunks;
|
||||||
|
let body = if chunk_cnt > 0 {
|
||||||
|
let (mut tx, body) = Body::channel();
|
||||||
|
let chunk = self
|
||||||
|
.request_body
|
||||||
|
.expect("request_chunks means request_body");
|
||||||
|
exec.spawn(async move {
|
||||||
|
for _ in 0..chunk_cnt {
|
||||||
|
tx.send_data(chunk.into()).await.expect("send_data");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
body
|
||||||
|
} else {
|
||||||
|
self.request_body
|
||||||
|
.map(Body::from)
|
||||||
|
.unwrap_or_else(Body::empty)
|
||||||
|
};
|
||||||
|
let mut req = Request::new(body);
|
||||||
|
*req.method_mut() = self.request_method.clone();
|
||||||
|
*req.uri_mut() = url.clone();
|
||||||
|
req
|
||||||
|
};
|
||||||
|
|
||||||
|
let send_request = |req: Request<Body>| {
|
||||||
|
let fut = client.request(req);
|
||||||
|
async {
|
||||||
|
let res = fut.await.expect("client wait");
|
||||||
|
let mut body = res.into_body();
|
||||||
|
while let Some(_chunk) = body.data().await {}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if self.parallel_cnt == 1 {
|
||||||
|
b.iter(|| {
|
||||||
|
let req = make_request();
|
||||||
|
rt.block_on(send_request(req));
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
b.iter(|| {
|
||||||
|
let futs = (0..self.parallel_cnt).map(|_| {
|
||||||
|
let req = make_request();
|
||||||
|
send_request(req)
|
||||||
|
});
|
||||||
|
// Await all spawned futures becoming completed.
|
||||||
|
rt.block_on(join_all(futs));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn spawn_server(rt: &tokio::runtime::Runtime, opts: &Opts) -> SocketAddr {
|
||||||
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
|
|
||||||
|
let body = opts.response_body;
|
||||||
|
let srv = rt.block_on(async move {
|
||||||
|
Server::bind(&addr)
|
||||||
|
.http2_only(opts.http2)
|
||||||
|
.http2_initial_stream_window_size(opts.http2_stream_window)
|
||||||
|
.http2_initial_connection_window_size(opts.http2_conn_window)
|
||||||
|
.http2_adaptive_window(opts.http2_adaptive_window)
|
||||||
|
.serve(make_service_fn(move |_| async move {
|
||||||
|
Ok::<_, hyper::Error>(service_fn(move |req: Request<Body>| async move {
|
||||||
|
let mut req_body = req.into_body();
|
||||||
|
while let Some(_chunk) = req_body.data().await {}
|
||||||
|
Ok::<_, hyper::Error>(Response::new(Body::from(body)))
|
||||||
|
}))
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
let addr = srv.local_addr();
|
||||||
|
rt.spawn(async {
|
||||||
|
if let Err(err) = srv.await {
|
||||||
|
panic!("server error: {}", err);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
addr
|
||||||
|
}
|
||||||
86
hyper/benches/pipeline.rs
Normal file
86
hyper/benches/pipeline.rs
Normal file
|
|
@ -0,0 +1,86 @@
|
||||||
|
#![feature(test)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::TcpStream;
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
use hyper::{Body, Response, Server};
|
||||||
|
|
||||||
|
const PIPELINED_REQUESTS: usize = 16;
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn hello_world_16(b: &mut test::Bencher) {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let (_until_tx, until_rx) = oneshot::channel::<()>();
|
||||||
|
|
||||||
|
let addr = {
|
||||||
|
let (addr_tx, addr_rx) = mpsc::channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
|
|
||||||
|
let make_svc = make_service_fn(|_| async {
|
||||||
|
Ok::<_, hyper::Error>(service_fn(|_| async {
|
||||||
|
Ok::<_, hyper::Error>(Response::new(Body::from("Hello, World!")))
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("rt build");
|
||||||
|
let srv = rt.block_on(async move {
|
||||||
|
Server::bind(&addr)
|
||||||
|
.http1_pipeline_flush(true)
|
||||||
|
.serve(make_svc)
|
||||||
|
});
|
||||||
|
|
||||||
|
addr_tx.send(srv.local_addr()).unwrap();
|
||||||
|
|
||||||
|
let graceful = srv.with_graceful_shutdown(async {
|
||||||
|
until_rx.await.ok();
|
||||||
|
});
|
||||||
|
|
||||||
|
rt.block_on(async {
|
||||||
|
if let Err(e) = graceful.await {
|
||||||
|
panic!("server error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addr_rx.recv().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut pipelined_reqs = Vec::new();
|
||||||
|
for _ in 0..PIPELINED_REQUESTS {
|
||||||
|
pipelined_reqs.extend_from_slice(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
let total_bytes = {
|
||||||
|
let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
|
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
tcp.read_to_end(&mut buf).unwrap()
|
||||||
|
} * PIPELINED_REQUESTS;
|
||||||
|
|
||||||
|
let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
|
tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
|
||||||
|
let mut buf = [0u8; 8192];
|
||||||
|
|
||||||
|
b.bytes = (pipelined_reqs.len() + total_bytes) as u64;
|
||||||
|
b.iter(|| {
|
||||||
|
tcp.write_all(&pipelined_reqs).unwrap();
|
||||||
|
let mut sum = 0;
|
||||||
|
while sum < total_bytes {
|
||||||
|
sum += tcp.read(&mut buf).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(sum, total_bytes);
|
||||||
|
});
|
||||||
|
}
|
||||||
212
hyper/benches/server.rs
Normal file
212
hyper/benches/server.rs
Normal file
|
|
@ -0,0 +1,212 @@
|
||||||
|
#![feature(test)]
|
||||||
|
#![deny(warnings)]
|
||||||
|
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use std::io::{Read, Write};
|
||||||
|
use std::net::{TcpListener, TcpStream};
|
||||||
|
use std::sync::mpsc;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use futures_util::{stream, StreamExt};
|
||||||
|
use tokio::sync::oneshot;
|
||||||
|
|
||||||
|
use hyper::service::{make_service_fn, service_fn};
|
||||||
|
use hyper::{Body, Response, Server};
|
||||||
|
|
||||||
|
macro_rules! bench_server {
|
||||||
|
($b:ident, $header:expr, $body:expr) => {{
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let (_until_tx, until_rx) = oneshot::channel::<()>();
|
||||||
|
let addr = {
|
||||||
|
let (addr_tx, addr_rx) = mpsc::channel();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let addr = "127.0.0.1:0".parse().unwrap();
|
||||||
|
let make_svc = make_service_fn(|_| async {
|
||||||
|
Ok::<_, hyper::Error>(service_fn(|_| async {
|
||||||
|
Ok::<_, hyper::Error>(
|
||||||
|
Response::builder()
|
||||||
|
.header($header.0, $header.1)
|
||||||
|
.header("content-type", "text/plain")
|
||||||
|
.body($body())
|
||||||
|
.unwrap(),
|
||||||
|
)
|
||||||
|
}))
|
||||||
|
});
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("rt build");
|
||||||
|
|
||||||
|
let srv = rt.block_on(async move { Server::bind(&addr).serve(make_svc) });
|
||||||
|
|
||||||
|
addr_tx.send(srv.local_addr()).unwrap();
|
||||||
|
|
||||||
|
let graceful = srv.with_graceful_shutdown(async {
|
||||||
|
until_rx.await.ok();
|
||||||
|
});
|
||||||
|
rt.block_on(async move {
|
||||||
|
if let Err(e) = graceful.await {
|
||||||
|
panic!("server error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
addr_rx.recv().unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let total_bytes = {
|
||||||
|
let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
|
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let mut buf = Vec::new();
|
||||||
|
tcp.read_to_end(&mut buf).unwrap()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
|
tcp.set_read_timeout(Some(Duration::from_secs(3))).unwrap();
|
||||||
|
let mut buf = [0u8; 8192];
|
||||||
|
|
||||||
|
$b.bytes = 35 + total_bytes as u64;
|
||||||
|
$b.iter(|| {
|
||||||
|
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let mut sum = 0;
|
||||||
|
while sum < total_bytes {
|
||||||
|
sum += tcp.read(&mut buf).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(sum, total_bytes);
|
||||||
|
});
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
fn body(b: &'static [u8]) -> hyper::Body {
|
||||||
|
b.into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn throughput_fixedsize_small_payload(b: &mut test::Bencher) {
|
||||||
|
bench_server!(b, ("content-length", "13"), || body(b"Hello, World!"))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn throughput_fixedsize_large_payload(b: &mut test::Bencher) {
|
||||||
|
bench_server!(b, ("content-length", "1000000"), || body(
|
||||||
|
&[b'x'; 1_000_000]
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn throughput_fixedsize_many_chunks(b: &mut test::Bencher) {
|
||||||
|
bench_server!(b, ("content-length", "1000000"), || {
|
||||||
|
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
||||||
|
Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn throughput_chunked_small_payload(b: &mut test::Bencher) {
|
||||||
|
bench_server!(b, ("transfer-encoding", "chunked"), || body(
|
||||||
|
b"Hello, World!"
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn throughput_chunked_large_payload(b: &mut test::Bencher) {
|
||||||
|
bench_server!(b, ("transfer-encoding", "chunked"), || body(
|
||||||
|
&[b'x'; 1_000_000]
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn throughput_chunked_many_chunks(b: &mut test::Bencher) {
|
||||||
|
bench_server!(b, ("transfer-encoding", "chunked"), || {
|
||||||
|
static S: &[&[u8]] = &[&[b'x'; 1_000] as &[u8]; 1_000] as _;
|
||||||
|
Body::wrap_stream(stream::iter(S.iter()).map(|&s| Ok::<_, String>(s)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn raw_tcp_throughput_small_payload(b: &mut test::Bencher) {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut sock = listener.accept().unwrap().0;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 8192];
|
||||||
|
while rx.try_recv().is_err() {
|
||||||
|
sock.read(&mut buf).unwrap();
|
||||||
|
sock.write_all(
|
||||||
|
b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
Content-Length: 13\r\n\
|
||||||
|
Content-Type: text/plain; charset=utf-8\r\n\
|
||||||
|
Date: Fri, 12 May 2017 18:21:45 GMT\r\n\
|
||||||
|
\r\n\
|
||||||
|
Hello, World!\
|
||||||
|
",
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
|
let mut buf = [0u8; 4096];
|
||||||
|
|
||||||
|
b.bytes = 130 + 35;
|
||||||
|
b.iter(|| {
|
||||||
|
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let n = tcp.read(&mut buf).unwrap();
|
||||||
|
assert_eq!(n, 130);
|
||||||
|
});
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[bench]
|
||||||
|
fn raw_tcp_throughput_large_payload(b: &mut test::Bencher) {
|
||||||
|
let (tx, rx) = mpsc::channel();
|
||||||
|
let listener = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = listener.local_addr().unwrap();
|
||||||
|
|
||||||
|
let srv_head = b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
Content-Length: 1000000\r\n\
|
||||||
|
Content-Type: text/plain; charset=utf-8\r\n\
|
||||||
|
Date: Fri, 12 May 2017 18:21:45 GMT\r\n\
|
||||||
|
\r\n\
|
||||||
|
";
|
||||||
|
std::thread::spawn(move || {
|
||||||
|
let mut sock = listener.accept().unwrap().0;
|
||||||
|
|
||||||
|
let mut buf = [0u8; 8192];
|
||||||
|
while rx.try_recv().is_err() {
|
||||||
|
let r = sock.read(&mut buf).unwrap();
|
||||||
|
extern crate test;
|
||||||
|
if r == 0 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
sock.write_all(srv_head).unwrap();
|
||||||
|
sock.write_all(&[b'x'; 1_000_000]).unwrap();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tcp = TcpStream::connect(addr).unwrap();
|
||||||
|
let mut buf = [0u8; 8192];
|
||||||
|
|
||||||
|
let expect_read = srv_head.len() + 1_000_000;
|
||||||
|
b.bytes = expect_read as u64 + 35;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
tcp.write_all(b"GET / HTTP/1.1\r\nHost: localhost\r\n\r\n")
|
||||||
|
.unwrap();
|
||||||
|
let mut sum = 0;
|
||||||
|
while sum < expect_read {
|
||||||
|
sum += tcp.read(&mut buf).unwrap();
|
||||||
|
}
|
||||||
|
assert_eq!(sum, expect_read);
|
||||||
|
});
|
||||||
|
tx.send(()).unwrap();
|
||||||
|
}
|
||||||
17
hyper/capi/README.md
Normal file
17
hyper/capi/README.md
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
# C API for hyper
|
||||||
|
|
||||||
|
This provides auxiliary pieces for a C API to use the hyper library.
|
||||||
|
|
||||||
|
## Unstable
|
||||||
|
|
||||||
|
The C API of hyper is currently **unstable**, which means it's not part of the semver contract as the rest of the Rust API is.
|
||||||
|
|
||||||
|
Because of that, it's only accessible if `--cfg hyper_unstable_ffi` is passed to `rustc` when compiling. The easiest way to do that is setting the `RUSTFLAGS` environment variable.
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
The C API is part of the Rust library, but isn't compiled by default. Using a nightly release of `cargo`, starting with `nightly-2022-03-02`, it can be compiled with the following command:
|
||||||
|
|
||||||
|
```
|
||||||
|
RUSTFLAGS="--cfg hyper_unstable_ffi" cargo +nightly rustc --features client,http1,http2,ffi -Z unstable-options --crate-type cdylib
|
||||||
|
```
|
||||||
15
hyper/capi/cbindgen.toml
Normal file
15
hyper/capi/cbindgen.toml
Normal file
|
|
@ -0,0 +1,15 @@
|
||||||
|
# See https://github.com/eqrion/cbindgen/blob/master/docs.md#cbindgentoml for
|
||||||
|
# a list of possible configuration values.
|
||||||
|
language = "C"
|
||||||
|
header = """/*
|
||||||
|
* Copyright 2021 Sean McArthur. MIT License.
|
||||||
|
* Generated by gen_header.sh. Do not edit directly.
|
||||||
|
*/"""
|
||||||
|
include_guard = "_HYPER_H"
|
||||||
|
no_includes = true
|
||||||
|
sys_includes = ["stdint.h", "stddef.h"]
|
||||||
|
cpp_compat = true
|
||||||
|
documentation_style = "c"
|
||||||
|
|
||||||
|
[parse.expand]
|
||||||
|
crates = ["hyper-capi"]
|
||||||
25
hyper/capi/examples/Makefile
Normal file
25
hyper/capi/examples/Makefile
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
#
|
||||||
|
# Build the example client
|
||||||
|
#
|
||||||
|
|
||||||
|
TARGET = client
|
||||||
|
TARGET2 = upload
|
||||||
|
|
||||||
|
OBJS = client.o
|
||||||
|
OBJS2 = upload.o
|
||||||
|
|
||||||
|
RPATH=$(PWD)/../../target/debug
|
||||||
|
CFLAGS = -I../include
|
||||||
|
LDFLAGS = -L$(RPATH) -Wl,-rpath,$(RPATH)
|
||||||
|
LIBS = -lhyper
|
||||||
|
|
||||||
|
all: $(TARGET) $(TARGET2)
|
||||||
|
|
||||||
|
$(TARGET): $(OBJS)
|
||||||
|
$(CC) -o $(TARGET) $(OBJS) $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
|
$(TARGET2): $(OBJS2)
|
||||||
|
$(CC) -o $(TARGET2) $(OBJS2) $(LDFLAGS) $(LIBS)
|
||||||
|
|
||||||
|
clean:
|
||||||
|
rm -f $(OBJS) $(TARGET) $(OBJS2) $(TARGET2)
|
||||||
343
hyper/capi/examples/client.c
Normal file
343
hyper/capi/examples/client.c
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "hyper.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct conn_data {
|
||||||
|
int fd;
|
||||||
|
hyper_waker *read_waker;
|
||||||
|
hyper_waker *write_waker;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) {
|
||||||
|
struct conn_data *conn = (struct conn_data *)userdata;
|
||||||
|
ssize_t ret = read(conn->fd, buf, buf_len);
|
||||||
|
|
||||||
|
if (ret >= 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno != EAGAIN) {
|
||||||
|
// kaboom
|
||||||
|
return HYPER_IO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// would block, register interest
|
||||||
|
if (conn->read_waker != NULL) {
|
||||||
|
hyper_waker_free(conn->read_waker);
|
||||||
|
}
|
||||||
|
conn->read_waker = hyper_context_waker(ctx);
|
||||||
|
return HYPER_IO_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
|
||||||
|
struct conn_data *conn = (struct conn_data *)userdata;
|
||||||
|
ssize_t ret = write(conn->fd, buf, buf_len);
|
||||||
|
|
||||||
|
if (ret >= 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno != EAGAIN) {
|
||||||
|
// kaboom
|
||||||
|
return HYPER_IO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// would block, register interest
|
||||||
|
if (conn->write_waker != NULL) {
|
||||||
|
hyper_waker_free(conn->write_waker);
|
||||||
|
}
|
||||||
|
conn->write_waker = hyper_context_waker(ctx);
|
||||||
|
return HYPER_IO_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_conn_data(struct conn_data *conn) {
|
||||||
|
if (conn->read_waker) {
|
||||||
|
hyper_waker_free(conn->read_waker);
|
||||||
|
conn->read_waker = NULL;
|
||||||
|
}
|
||||||
|
if (conn->write_waker) {
|
||||||
|
hyper_waker_free(conn->write_waker);
|
||||||
|
conn->write_waker = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connect_to(const char *host, const char *port) {
|
||||||
|
struct addrinfo hints;
|
||||||
|
memset(&hints, 0, sizeof(struct addrinfo));
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
struct addrinfo *result, *rp;
|
||||||
|
if (getaddrinfo(host, port, &hints, &result) != 0) {
|
||||||
|
printf("dns failed for %s\n", host);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sfd;
|
||||||
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||||
|
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||||
|
if (sfd == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(result);
|
||||||
|
|
||||||
|
// no address succeeded
|
||||||
|
if (rp == NULL) {
|
||||||
|
printf("connect failed for %s\n", host);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int print_each_header(void *userdata,
|
||||||
|
const uint8_t *name,
|
||||||
|
size_t name_len,
|
||||||
|
const uint8_t *value,
|
||||||
|
size_t value_len) {
|
||||||
|
printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value);
|
||||||
|
return HYPER_ITER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int print_each_chunk(void *userdata, const hyper_buf *chunk) {
|
||||||
|
const uint8_t *buf = hyper_buf_bytes(chunk);
|
||||||
|
size_t len = hyper_buf_len(chunk);
|
||||||
|
|
||||||
|
write(1, buf, len);
|
||||||
|
|
||||||
|
return HYPER_ITER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
|
||||||
|
EXAMPLE_HANDSHAKE,
|
||||||
|
EXAMPLE_SEND,
|
||||||
|
EXAMPLE_RESP_BODY
|
||||||
|
} example_id;
|
||||||
|
|
||||||
|
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
const char *host = argc > 1 ? argv[1] : "httpbin.org";
|
||||||
|
const char *port = argc > 2 ? argv[2] : "80";
|
||||||
|
const char *path = argc > 3 ? argv[3] : "/";
|
||||||
|
printf("connecting to port %s on %s...\n", port, host);
|
||||||
|
|
||||||
|
int fd = connect_to(host, port);
|
||||||
|
if (fd < 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("connected to %s, now get %s\n", host, path);
|
||||||
|
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
|
||||||
|
printf("failed to set socket to non-blocking\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
fd_set fds_read;
|
||||||
|
fd_set fds_write;
|
||||||
|
fd_set fds_excep;
|
||||||
|
|
||||||
|
struct conn_data *conn = malloc(sizeof(struct conn_data));
|
||||||
|
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->read_waker = NULL;
|
||||||
|
conn->write_waker = NULL;
|
||||||
|
|
||||||
|
// Hookup the IO
|
||||||
|
hyper_io *io = hyper_io_new();
|
||||||
|
hyper_io_set_userdata(io, (void *)conn);
|
||||||
|
hyper_io_set_read(io, read_cb);
|
||||||
|
hyper_io_set_write(io, write_cb);
|
||||||
|
|
||||||
|
printf("http handshake (hyper v%s) ...\n", hyper_version());
|
||||||
|
|
||||||
|
// We need an executor generally to poll futures
|
||||||
|
const hyper_executor *exec = hyper_executor_new();
|
||||||
|
|
||||||
|
// Prepare client options
|
||||||
|
hyper_clientconn_options *opts = hyper_clientconn_options_new();
|
||||||
|
hyper_clientconn_options_exec(opts, exec);
|
||||||
|
|
||||||
|
hyper_task *handshake = hyper_clientconn_handshake(io, opts);
|
||||||
|
hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE);
|
||||||
|
|
||||||
|
// Let's wait for the handshake to finish...
|
||||||
|
hyper_executor_push(exec, handshake);
|
||||||
|
|
||||||
|
// In case a task errors...
|
||||||
|
hyper_error *err;
|
||||||
|
|
||||||
|
// The polling state machine!
|
||||||
|
while (1) {
|
||||||
|
// Poll all ready tasks and act on them...
|
||||||
|
while (1) {
|
||||||
|
hyper_task *task = hyper_executor_poll(exec);
|
||||||
|
if (!task) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
switch ((example_id) hyper_task_userdata(task)) {
|
||||||
|
case EXAMPLE_HANDSHAKE:
|
||||||
|
;
|
||||||
|
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
|
||||||
|
printf("handshake error!\n");
|
||||||
|
err = hyper_task_value(task);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
assert(hyper_task_type(task) == HYPER_TASK_CLIENTCONN);
|
||||||
|
|
||||||
|
printf("preparing http request ...\n");
|
||||||
|
|
||||||
|
hyper_clientconn *client = hyper_task_value(task);
|
||||||
|
hyper_task_free(task);
|
||||||
|
|
||||||
|
// Prepare the request
|
||||||
|
hyper_request *req = hyper_request_new();
|
||||||
|
if (hyper_request_set_method(req, STR_ARG("GET"))) {
|
||||||
|
printf("error setting method\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (hyper_request_set_uri(req, STR_ARG(path))) {
|
||||||
|
printf("error setting uri\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hyper_headers *req_headers = hyper_request_headers(req);
|
||||||
|
hyper_headers_set(req_headers, STR_ARG("Host"), STR_ARG(host));
|
||||||
|
|
||||||
|
// Send it!
|
||||||
|
hyper_task *send = hyper_clientconn_send(client, req);
|
||||||
|
hyper_task_set_userdata(send, (void *)EXAMPLE_SEND);
|
||||||
|
printf("sending ...\n");
|
||||||
|
hyper_executor_push(exec, send);
|
||||||
|
|
||||||
|
// For this example, no longer need the client
|
||||||
|
hyper_clientconn_free(client);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EXAMPLE_SEND:
|
||||||
|
;
|
||||||
|
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
|
||||||
|
printf("send error!\n");
|
||||||
|
err = hyper_task_value(task);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
assert(hyper_task_type(task) == HYPER_TASK_RESPONSE);
|
||||||
|
|
||||||
|
// Take the results
|
||||||
|
hyper_response *resp = hyper_task_value(task);
|
||||||
|
hyper_task_free(task);
|
||||||
|
|
||||||
|
uint16_t http_status = hyper_response_status(resp);
|
||||||
|
const uint8_t *rp = hyper_response_reason_phrase(resp);
|
||||||
|
size_t rp_len = hyper_response_reason_phrase_len(resp);
|
||||||
|
|
||||||
|
printf("\nResponse Status: %d %.*s\n", http_status, (int) rp_len, rp);
|
||||||
|
|
||||||
|
hyper_headers *headers = hyper_response_headers(resp);
|
||||||
|
hyper_headers_foreach(headers, print_each_header, NULL);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
hyper_body *resp_body = hyper_response_body(resp);
|
||||||
|
hyper_task *foreach = hyper_body_foreach(resp_body, print_each_chunk, NULL);
|
||||||
|
hyper_task_set_userdata(foreach, (void *)EXAMPLE_RESP_BODY);
|
||||||
|
hyper_executor_push(exec, foreach);
|
||||||
|
|
||||||
|
// No longer need the response
|
||||||
|
hyper_response_free(resp);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EXAMPLE_RESP_BODY:
|
||||||
|
;
|
||||||
|
if (hyper_task_type(task) == HYPER_TASK_ERROR) {
|
||||||
|
printf("body error!\n");
|
||||||
|
err = hyper_task_value(task);
|
||||||
|
goto fail;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(hyper_task_type(task) == HYPER_TASK_EMPTY);
|
||||||
|
|
||||||
|
printf("\n -- Done! -- \n");
|
||||||
|
|
||||||
|
// Cleaning up before exiting
|
||||||
|
hyper_task_free(task);
|
||||||
|
hyper_executor_free(exec);
|
||||||
|
free_conn_data(conn);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
case EXAMPLE_NOT_SET:
|
||||||
|
// A background task for hyper completed...
|
||||||
|
hyper_task_free(task);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All futures are pending on IO work, so select on the fds.
|
||||||
|
|
||||||
|
FD_ZERO(&fds_read);
|
||||||
|
FD_ZERO(&fds_write);
|
||||||
|
FD_ZERO(&fds_excep);
|
||||||
|
|
||||||
|
if (conn->read_waker) {
|
||||||
|
FD_SET(conn->fd, &fds_read);
|
||||||
|
}
|
||||||
|
if (conn->write_waker) {
|
||||||
|
FD_SET(conn->fd, &fds_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sel_ret = select(conn->fd + 1, &fds_read, &fds_write, &fds_excep, NULL);
|
||||||
|
|
||||||
|
if (sel_ret < 0) {
|
||||||
|
printf("select() error\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(conn->fd, &fds_read)) {
|
||||||
|
hyper_waker_wake(conn->read_waker);
|
||||||
|
conn->read_waker = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(conn->fd, &fds_write)) {
|
||||||
|
hyper_waker_wake(conn->write_waker);
|
||||||
|
conn->write_waker = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
fail:
|
||||||
|
if (err) {
|
||||||
|
printf("error code: %d\n", hyper_error_code(err));
|
||||||
|
// grab the error details
|
||||||
|
char errbuf [256];
|
||||||
|
size_t errlen = hyper_error_print(err, errbuf, sizeof(errbuf));
|
||||||
|
printf("details: %.*s\n", (int) errlen, errbuf);
|
||||||
|
|
||||||
|
// clean up the error
|
||||||
|
hyper_error_free(err);
|
||||||
|
}
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
406
hyper/capi/examples/upload.c
Normal file
406
hyper/capi/examples/upload.c
Normal file
|
|
@ -0,0 +1,406 @@
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <errno.h>
|
||||||
|
#include <sys/select.h>
|
||||||
|
#include <assert.h>
|
||||||
|
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <netdb.h>
|
||||||
|
#include <string.h>
|
||||||
|
|
||||||
|
#include "hyper.h"
|
||||||
|
|
||||||
|
|
||||||
|
struct conn_data {
|
||||||
|
int fd;
|
||||||
|
hyper_waker *read_waker;
|
||||||
|
hyper_waker *write_waker;
|
||||||
|
};
|
||||||
|
|
||||||
|
static size_t read_cb(void *userdata, hyper_context *ctx, uint8_t *buf, size_t buf_len) {
|
||||||
|
struct conn_data *conn = (struct conn_data *)userdata;
|
||||||
|
ssize_t ret = read(conn->fd, buf, buf_len);
|
||||||
|
|
||||||
|
if (ret >= 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno != EAGAIN) {
|
||||||
|
// kaboom
|
||||||
|
return HYPER_IO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// would block, register interest
|
||||||
|
if (conn->read_waker != NULL) {
|
||||||
|
hyper_waker_free(conn->read_waker);
|
||||||
|
}
|
||||||
|
conn->read_waker = hyper_context_waker(ctx);
|
||||||
|
return HYPER_IO_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static size_t write_cb(void *userdata, hyper_context *ctx, const uint8_t *buf, size_t buf_len) {
|
||||||
|
struct conn_data *conn = (struct conn_data *)userdata;
|
||||||
|
ssize_t ret = write(conn->fd, buf, buf_len);
|
||||||
|
|
||||||
|
if (ret >= 0) {
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (errno != EAGAIN) {
|
||||||
|
// kaboom
|
||||||
|
return HYPER_IO_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
// would block, register interest
|
||||||
|
if (conn->write_waker != NULL) {
|
||||||
|
hyper_waker_free(conn->write_waker);
|
||||||
|
}
|
||||||
|
conn->write_waker = hyper_context_waker(ctx);
|
||||||
|
return HYPER_IO_PENDING;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void free_conn_data(struct conn_data *conn) {
|
||||||
|
if (conn->read_waker) {
|
||||||
|
hyper_waker_free(conn->read_waker);
|
||||||
|
conn->read_waker = NULL;
|
||||||
|
}
|
||||||
|
if (conn->write_waker) {
|
||||||
|
hyper_waker_free(conn->write_waker);
|
||||||
|
conn->write_waker = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
free(conn);
|
||||||
|
}
|
||||||
|
|
||||||
|
static int connect_to(const char *host, const char *port) {
|
||||||
|
struct addrinfo hints;
|
||||||
|
memset(&hints, 0, sizeof(struct addrinfo));
|
||||||
|
hints.ai_family = AF_UNSPEC;
|
||||||
|
hints.ai_socktype = SOCK_STREAM;
|
||||||
|
|
||||||
|
struct addrinfo *result, *rp;
|
||||||
|
if (getaddrinfo(host, port, &hints, &result) != 0) {
|
||||||
|
printf("dns failed for %s\n", host);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
int sfd;
|
||||||
|
for (rp = result; rp != NULL; rp = rp->ai_next) {
|
||||||
|
sfd = socket(rp->ai_family, rp->ai_socktype, rp->ai_protocol);
|
||||||
|
if (sfd == -1) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sfd);
|
||||||
|
}
|
||||||
|
|
||||||
|
freeaddrinfo(result);
|
||||||
|
|
||||||
|
// no address succeeded
|
||||||
|
if (rp == NULL) {
|
||||||
|
printf("connect failed for %s\n", host);
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sfd;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct upload_body {
|
||||||
|
int fd;
|
||||||
|
char *buf;
|
||||||
|
size_t len;
|
||||||
|
};
|
||||||
|
|
||||||
|
static int poll_req_upload(void *userdata,
|
||||||
|
hyper_context *ctx,
|
||||||
|
hyper_buf **chunk) {
|
||||||
|
struct upload_body* upload = userdata;
|
||||||
|
|
||||||
|
ssize_t res = read(upload->fd, upload->buf, upload->len);
|
||||||
|
if (res > 0) {
|
||||||
|
*chunk = hyper_buf_copy(upload->buf, res);
|
||||||
|
return HYPER_POLL_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (res == 0) {
|
||||||
|
// All done!
|
||||||
|
*chunk = NULL;
|
||||||
|
return HYPER_POLL_READY;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Oh no!
|
||||||
|
printf("error reading upload file: %d", errno);
|
||||||
|
return HYPER_POLL_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
|
static int print_each_header(void *userdata,
|
||||||
|
const uint8_t *name,
|
||||||
|
size_t name_len,
|
||||||
|
const uint8_t *value,
|
||||||
|
size_t value_len) {
|
||||||
|
printf("%.*s: %.*s\n", (int) name_len, name, (int) value_len, value);
|
||||||
|
return HYPER_ITER_CONTINUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void print_informational(void *userdata, hyper_response *resp) {
|
||||||
|
uint16_t http_status = hyper_response_status(resp);
|
||||||
|
|
||||||
|
printf("\nInformational (1xx): %d\n", http_status);
|
||||||
|
|
||||||
|
const hyper_buf *headers = hyper_response_headers_raw(resp);
|
||||||
|
if (headers) {
|
||||||
|
write(1, hyper_buf_bytes(headers), hyper_buf_len(headers));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
typedef enum {
|
||||||
|
EXAMPLE_NOT_SET = 0, // tasks we don't know about won't have a userdata set
|
||||||
|
EXAMPLE_HANDSHAKE,
|
||||||
|
EXAMPLE_SEND,
|
||||||
|
EXAMPLE_RESP_BODY
|
||||||
|
} example_id;
|
||||||
|
|
||||||
|
#define STR_ARG(XX) (uint8_t *)XX, strlen(XX)
|
||||||
|
|
||||||
|
int main(int argc, char *argv[]) {
|
||||||
|
const char *file = argc > 1 ? argv[1] : NULL;
|
||||||
|
const char *host = argc > 2 ? argv[2] : "httpbin.org";
|
||||||
|
const char *port = argc > 3 ? argv[3] : "80";
|
||||||
|
const char *path = argc > 4 ? argv[4] : "/post";
|
||||||
|
|
||||||
|
if (!file) {
|
||||||
|
printf("Pass a file path as the first argument.\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
struct upload_body upload;
|
||||||
|
upload.fd = open(file, O_RDONLY);
|
||||||
|
|
||||||
|
if (upload.fd < 0) {
|
||||||
|
printf("error opening file to upload: %s\n", strerror(errno));
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("connecting to port %s on %s...\n", port, host);
|
||||||
|
|
||||||
|
int fd = connect_to(host, port);
|
||||||
|
if (fd < 0) {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
printf("connected to %s, now upload to %s\n", host, path);
|
||||||
|
|
||||||
|
if (fcntl(fd, F_SETFL, O_NONBLOCK) != 0) {
|
||||||
|
printf("failed to set socket to non-blocking\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
upload.len = 8192;
|
||||||
|
upload.buf = malloc(upload.len);
|
||||||
|
|
||||||
|
fd_set fds_read;
|
||||||
|
fd_set fds_write;
|
||||||
|
fd_set fds_excep;
|
||||||
|
|
||||||
|
struct conn_data *conn = malloc(sizeof(struct conn_data));
|
||||||
|
|
||||||
|
conn->fd = fd;
|
||||||
|
conn->read_waker = NULL;
|
||||||
|
conn->write_waker = NULL;
|
||||||
|
|
||||||
|
|
||||||
|
// Hookup the IO
|
||||||
|
hyper_io *io = hyper_io_new();
|
||||||
|
hyper_io_set_userdata(io, (void *)conn);
|
||||||
|
hyper_io_set_read(io, read_cb);
|
||||||
|
hyper_io_set_write(io, write_cb);
|
||||||
|
|
||||||
|
printf("http handshake (hyper v%s) ...\n", hyper_version());
|
||||||
|
|
||||||
|
// We need an executor generally to poll futures
|
||||||
|
const hyper_executor *exec = hyper_executor_new();
|
||||||
|
|
||||||
|
// Prepare client options
|
||||||
|
hyper_clientconn_options *opts = hyper_clientconn_options_new();
|
||||||
|
hyper_clientconn_options_exec(opts, exec);
|
||||||
|
hyper_clientconn_options_headers_raw(opts, 1);
|
||||||
|
|
||||||
|
hyper_task *handshake = hyper_clientconn_handshake(io, opts);
|
||||||
|
hyper_task_set_userdata(handshake, (void *)EXAMPLE_HANDSHAKE);
|
||||||
|
|
||||||
|
// Let's wait for the handshake to finish...
|
||||||
|
hyper_executor_push(exec, handshake);
|
||||||
|
|
||||||
|
// This body will get filled in eventually...
|
||||||
|
hyper_body *resp_body = NULL;
|
||||||
|
|
||||||
|
// The polling state machine!
|
||||||
|
while (1) {
|
||||||
|
// Poll all ready tasks and act on them...
|
||||||
|
while (1) {
|
||||||
|
hyper_task *task = hyper_executor_poll(exec);
|
||||||
|
if (!task) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
hyper_task_return_type task_type = hyper_task_type(task);
|
||||||
|
|
||||||
|
switch ((example_id) hyper_task_userdata(task)) {
|
||||||
|
case EXAMPLE_HANDSHAKE:
|
||||||
|
;
|
||||||
|
if (task_type == HYPER_TASK_ERROR) {
|
||||||
|
printf("handshake error!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
assert(task_type == HYPER_TASK_CLIENTCONN);
|
||||||
|
|
||||||
|
printf("preparing http request ...\n");
|
||||||
|
|
||||||
|
hyper_clientconn *client = hyper_task_value(task);
|
||||||
|
hyper_task_free(task);
|
||||||
|
|
||||||
|
// Prepare the request
|
||||||
|
hyper_request *req = hyper_request_new();
|
||||||
|
if (hyper_request_set_method(req, STR_ARG("POST"))) {
|
||||||
|
printf("error setting method\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
if (hyper_request_set_uri(req, STR_ARG(path))) {
|
||||||
|
printf("error setting uri\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
hyper_headers *req_headers = hyper_request_headers(req);
|
||||||
|
hyper_headers_set(req_headers, STR_ARG("host"), STR_ARG(host));
|
||||||
|
hyper_headers_set(req_headers, STR_ARG("expect"), STR_ARG("100-continue"));
|
||||||
|
|
||||||
|
// NOTE: We aren't handling *waiting* for the 100 Continue,
|
||||||
|
// the body is sent immediately. This will just print if any
|
||||||
|
// informational headers are received.
|
||||||
|
printf(" with expect-continue ...\n");
|
||||||
|
hyper_request_on_informational(req, print_informational, NULL);
|
||||||
|
|
||||||
|
// Prepare the req body
|
||||||
|
hyper_body *body = hyper_body_new();
|
||||||
|
hyper_body_set_userdata(body, &upload);
|
||||||
|
hyper_body_set_data_func(body, poll_req_upload);
|
||||||
|
hyper_request_set_body(req, body);
|
||||||
|
|
||||||
|
// Send it!
|
||||||
|
hyper_task *send = hyper_clientconn_send(client, req);
|
||||||
|
hyper_task_set_userdata(send, (void *)EXAMPLE_SEND);
|
||||||
|
printf("sending ...\n");
|
||||||
|
hyper_executor_push(exec, send);
|
||||||
|
|
||||||
|
// For this example, no longer need the client
|
||||||
|
hyper_clientconn_free(client);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EXAMPLE_SEND:
|
||||||
|
;
|
||||||
|
if (task_type == HYPER_TASK_ERROR) {
|
||||||
|
printf("send error!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
assert(task_type == HYPER_TASK_RESPONSE);
|
||||||
|
|
||||||
|
// Take the results
|
||||||
|
hyper_response *resp = hyper_task_value(task);
|
||||||
|
hyper_task_free(task);
|
||||||
|
|
||||||
|
uint16_t http_status = hyper_response_status(resp);
|
||||||
|
|
||||||
|
printf("\nResponse Status: %d\n", http_status);
|
||||||
|
|
||||||
|
hyper_headers *headers = hyper_response_headers(resp);
|
||||||
|
hyper_headers_foreach(headers, print_each_header, NULL);
|
||||||
|
printf("\n");
|
||||||
|
|
||||||
|
resp_body = hyper_response_body(resp);
|
||||||
|
|
||||||
|
// Set us up to peel data from the body a chunk at a time
|
||||||
|
hyper_task *body_data = hyper_body_data(resp_body);
|
||||||
|
hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY);
|
||||||
|
hyper_executor_push(exec, body_data);
|
||||||
|
|
||||||
|
// No longer need the response
|
||||||
|
hyper_response_free(resp);
|
||||||
|
|
||||||
|
break;
|
||||||
|
case EXAMPLE_RESP_BODY:
|
||||||
|
;
|
||||||
|
if (task_type == HYPER_TASK_ERROR) {
|
||||||
|
printf("body error!\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (task_type == HYPER_TASK_BUF) {
|
||||||
|
hyper_buf *chunk = hyper_task_value(task);
|
||||||
|
write(1, hyper_buf_bytes(chunk), hyper_buf_len(chunk));
|
||||||
|
hyper_buf_free(chunk);
|
||||||
|
hyper_task_free(task);
|
||||||
|
|
||||||
|
hyper_task *body_data = hyper_body_data(resp_body);
|
||||||
|
hyper_task_set_userdata(body_data, (void *)EXAMPLE_RESP_BODY);
|
||||||
|
hyper_executor_push(exec, body_data);
|
||||||
|
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(task_type == HYPER_TASK_EMPTY);
|
||||||
|
hyper_task_free(task);
|
||||||
|
hyper_body_free(resp_body);
|
||||||
|
|
||||||
|
printf("\n -- Done! -- \n");
|
||||||
|
|
||||||
|
// Cleaning up before exiting
|
||||||
|
hyper_executor_free(exec);
|
||||||
|
free_conn_data(conn);
|
||||||
|
free(upload.buf);
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
case EXAMPLE_NOT_SET:
|
||||||
|
// A background task for hyper completed...
|
||||||
|
hyper_task_free(task);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// All futures are pending on IO work, so select on the fds.
|
||||||
|
|
||||||
|
FD_ZERO(&fds_read);
|
||||||
|
FD_ZERO(&fds_write);
|
||||||
|
FD_ZERO(&fds_excep);
|
||||||
|
|
||||||
|
if (conn->read_waker) {
|
||||||
|
FD_SET(conn->fd, &fds_read);
|
||||||
|
}
|
||||||
|
if (conn->write_waker) {
|
||||||
|
FD_SET(conn->fd, &fds_write);
|
||||||
|
}
|
||||||
|
|
||||||
|
int sel_ret = select(conn->fd + 1, &fds_read, &fds_write, &fds_excep, NULL);
|
||||||
|
|
||||||
|
if (sel_ret < 0) {
|
||||||
|
printf("select() error\n");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(conn->fd, &fds_read)) {
|
||||||
|
hyper_waker_wake(conn->read_waker);
|
||||||
|
conn->read_waker = NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (FD_ISSET(conn->fd, &fds_write)) {
|
||||||
|
hyper_waker_wake(conn->write_waker);
|
||||||
|
conn->write_waker = NULL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
108
hyper/capi/gen_header.sh
Executable file
108
hyper/capi/gen_header.sh
Executable file
|
|
@ -0,0 +1,108 @@
|
||||||
|
#!/usr/bin/env bash
|
||||||
|
|
||||||
|
# This script regenerates hyper.h. As of April 2021, it only works with the
|
||||||
|
# nightly build of Rust.
|
||||||
|
|
||||||
|
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 "$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",
|
||||||
|
]
|
||||||
|
|
||||||
|
http1 = []
|
||||||
|
client = []
|
||||||
|
ffi = ["libc", "tokio/rt"]
|
||||||
|
EOF
|
||||||
|
|
||||||
|
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
|
||||||
|
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
|
||||||
|
bindgen_exit_code=$?
|
||||||
|
if [[ "--verify" == "$1" ]]; then
|
||||||
|
echo "diff generated (<) vs backup (>)"
|
||||||
|
diff "$CAPI_DIR/include/hyper.h" "$header_file_backup"
|
||||||
|
fi
|
||||||
|
exit $bindgen_exit_code
|
||||||
|
fi
|
||||||
|
|
||||||
|
exit 0
|
||||||
779
hyper/capi/include/hyper.h
Normal file
779
hyper/capi/include/hyper.h
Normal file
|
|
@ -0,0 +1,779 @@
|
||||||
|
/*
|
||||||
|
* Copyright 2021 Sean McArthur. MIT License.
|
||||||
|
* Generated by gen_header.sh. Do not edit directly.
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef _HYPER_H
|
||||||
|
#define _HYPER_H
|
||||||
|
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return in iter functions to continue iterating.
|
||||||
|
*/
|
||||||
|
#define HYPER_ITER_CONTINUE 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return in iter functions to stop iterating.
|
||||||
|
*/
|
||||||
|
#define HYPER_ITER_BREAK 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
An HTTP Version that is unspecified.
|
||||||
|
*/
|
||||||
|
#define HYPER_HTTP_VERSION_NONE 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
The HTTP/1.0 version.
|
||||||
|
*/
|
||||||
|
#define HYPER_HTTP_VERSION_1_0 10
|
||||||
|
|
||||||
|
/*
|
||||||
|
The HTTP/1.1 version.
|
||||||
|
*/
|
||||||
|
#define HYPER_HTTP_VERSION_1_1 11
|
||||||
|
|
||||||
|
/*
|
||||||
|
The HTTP/2 version.
|
||||||
|
*/
|
||||||
|
#define HYPER_HTTP_VERSION_2 20
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sentinel value to return from a read or write callback that the operation
|
||||||
|
is pending.
|
||||||
|
*/
|
||||||
|
#define HYPER_IO_PENDING 4294967295
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sentinel value to return from a read or write callback that the operation
|
||||||
|
has errored.
|
||||||
|
*/
|
||||||
|
#define HYPER_IO_ERROR 4294967294
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return in a poll function to indicate it was ready.
|
||||||
|
*/
|
||||||
|
#define HYPER_POLL_READY 0
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return in a poll function to indicate it is still pending.
|
||||||
|
|
||||||
|
The passed in `hyper_waker` should be registered to wake up the task at
|
||||||
|
some later point.
|
||||||
|
*/
|
||||||
|
#define HYPER_POLL_PENDING 1
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return in a poll function indicate an error.
|
||||||
|
*/
|
||||||
|
#define HYPER_POLL_ERROR 3
|
||||||
|
|
||||||
|
/*
|
||||||
|
A return code for many of hyper's methods.
|
||||||
|
*/
|
||||||
|
typedef enum hyper_code {
|
||||||
|
/*
|
||||||
|
All is well.
|
||||||
|
*/
|
||||||
|
HYPERE_OK,
|
||||||
|
/*
|
||||||
|
General error, details in the `hyper_error *`.
|
||||||
|
*/
|
||||||
|
HYPERE_ERROR,
|
||||||
|
/*
|
||||||
|
A function argument was invalid.
|
||||||
|
*/
|
||||||
|
HYPERE_INVALID_ARG,
|
||||||
|
/*
|
||||||
|
The IO transport returned an EOF when one wasn't expected.
|
||||||
|
|
||||||
|
This typically means an HTTP request or response was expected, but the
|
||||||
|
connection closed cleanly without sending (all of) it.
|
||||||
|
*/
|
||||||
|
HYPERE_UNEXPECTED_EOF,
|
||||||
|
/*
|
||||||
|
Aborted by a user supplied callback.
|
||||||
|
*/
|
||||||
|
HYPERE_ABORTED_BY_CALLBACK,
|
||||||
|
/*
|
||||||
|
An optional hyper feature was not enabled.
|
||||||
|
*/
|
||||||
|
HYPERE_FEATURE_NOT_ENABLED,
|
||||||
|
/*
|
||||||
|
The peer sent an HTTP message that could not be parsed.
|
||||||
|
*/
|
||||||
|
HYPERE_INVALID_PEER_MESSAGE,
|
||||||
|
} hyper_code;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A descriptor for what type a `hyper_task` value is.
|
||||||
|
*/
|
||||||
|
typedef enum hyper_task_return_type {
|
||||||
|
/*
|
||||||
|
The value of this task is null (does not imply an error).
|
||||||
|
*/
|
||||||
|
HYPER_TASK_EMPTY,
|
||||||
|
/*
|
||||||
|
The value of this task is `hyper_error *`.
|
||||||
|
*/
|
||||||
|
HYPER_TASK_ERROR,
|
||||||
|
/*
|
||||||
|
The value of this task is `hyper_clientconn *`.
|
||||||
|
*/
|
||||||
|
HYPER_TASK_CLIENTCONN,
|
||||||
|
/*
|
||||||
|
The value of this task is `hyper_response *`.
|
||||||
|
*/
|
||||||
|
HYPER_TASK_RESPONSE,
|
||||||
|
/*
|
||||||
|
The value of this task is `hyper_buf *`.
|
||||||
|
*/
|
||||||
|
HYPER_TASK_BUF,
|
||||||
|
} hyper_task_return_type;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A streaming HTTP body.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_body hyper_body;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A buffer of bytes that is sent or received on a `hyper_body`.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_buf hyper_buf;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An HTTP client connection handle.
|
||||||
|
|
||||||
|
These are used to send a request on a single connection. It's possible to
|
||||||
|
send multiple requests on a single connection, such as when HTTP/1
|
||||||
|
keep-alive or HTTP/2 is used.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_clientconn hyper_clientconn;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An options builder to configure an HTTP client connection.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_clientconn_options hyper_clientconn_options;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An async context for a task that contains the related waker.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_context hyper_context;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A more detailed error object returned by some hyper functions.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_error hyper_error;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A task executor for `hyper_task`s.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_executor hyper_executor;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An HTTP header map.
|
||||||
|
|
||||||
|
These can be part of a request or response.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_headers hyper_headers;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An IO object used to represent a socket or similar concept.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_io hyper_io;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An HTTP request.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_request hyper_request;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An HTTP response.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_response hyper_response;
|
||||||
|
|
||||||
|
/*
|
||||||
|
An async task.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_task hyper_task;
|
||||||
|
|
||||||
|
/*
|
||||||
|
A waker that is saved and used to waken a pending task.
|
||||||
|
*/
|
||||||
|
typedef struct hyper_waker hyper_waker;
|
||||||
|
|
||||||
|
typedef int (*hyper_body_foreach_callback)(void*, const struct hyper_buf*);
|
||||||
|
|
||||||
|
typedef int (*hyper_body_data_callback)(void*, struct hyper_context*, struct hyper_buf**);
|
||||||
|
|
||||||
|
typedef void (*hyper_request_on_informational_callback)(void*, struct hyper_response*);
|
||||||
|
|
||||||
|
typedef int (*hyper_headers_foreach_callback)(void*, const uint8_t*, size_t, const uint8_t*, size_t);
|
||||||
|
|
||||||
|
typedef size_t (*hyper_io_read_callback)(void*, struct hyper_context*, uint8_t*, size_t);
|
||||||
|
|
||||||
|
typedef size_t (*hyper_io_write_callback)(void*, struct hyper_context*, const uint8_t*, size_t);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
extern "C" {
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
/*
|
||||||
|
Returns a static ASCII (null terminated) string of the hyper version.
|
||||||
|
*/
|
||||||
|
const char *hyper_version(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create a new "empty" body.
|
||||||
|
|
||||||
|
If not configured, this body acts as an empty payload.
|
||||||
|
*/
|
||||||
|
struct hyper_body *hyper_body_new(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free a `hyper_body *`.
|
||||||
|
*/
|
||||||
|
void hyper_body_free(struct hyper_body *body);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return a task that will poll the body for the next buffer of data.
|
||||||
|
|
||||||
|
The task value may have different types depending on the outcome:
|
||||||
|
|
||||||
|
- `HYPER_TASK_BUF`: Success, and more data was received.
|
||||||
|
- `HYPER_TASK_ERROR`: An error retrieving the data.
|
||||||
|
- `HYPER_TASK_EMPTY`: The body has finished streaming data.
|
||||||
|
|
||||||
|
This does not consume the `hyper_body *`, so it may be used to again.
|
||||||
|
However, it MUST NOT be used or freed until the related task completes.
|
||||||
|
*/
|
||||||
|
struct hyper_task *hyper_body_data(struct hyper_body *body);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Return a task that will poll the body and execute the callback with each
|
||||||
|
body chunk that is received.
|
||||||
|
|
||||||
|
The `hyper_buf` pointer is only a borrowed reference, it cannot live outside
|
||||||
|
the execution of the callback. You must make a copy to retain it.
|
||||||
|
|
||||||
|
The callback should return `HYPER_ITER_CONTINUE` to continue iterating
|
||||||
|
chunks as they are received, or `HYPER_ITER_BREAK` to cancel.
|
||||||
|
|
||||||
|
This will consume the `hyper_body *`, you shouldn't use it anymore or free it.
|
||||||
|
*/
|
||||||
|
struct hyper_task *hyper_body_foreach(struct hyper_body *body,
|
||||||
|
hyper_body_foreach_callback func,
|
||||||
|
void *userdata);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set userdata on this body, which will be passed to callback functions.
|
||||||
|
*/
|
||||||
|
void hyper_body_set_userdata(struct hyper_body *body, void *userdata);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the data callback for this body.
|
||||||
|
|
||||||
|
The callback is called each time hyper needs to send more data for the
|
||||||
|
body. It is passed the value from `hyper_body_set_userdata`.
|
||||||
|
|
||||||
|
If there is data available, the `hyper_buf **` argument should be set
|
||||||
|
to a `hyper_buf *` containing the data, and `HYPER_POLL_READY` should
|
||||||
|
be returned.
|
||||||
|
|
||||||
|
Returning `HYPER_POLL_READY` while the `hyper_buf **` argument points
|
||||||
|
to `NULL` will indicate the body has completed all data.
|
||||||
|
|
||||||
|
If there is more data to send, but it isn't yet available, a
|
||||||
|
`hyper_waker` should be saved from the `hyper_context *` argument, and
|
||||||
|
`HYPER_POLL_PENDING` should be returned. You must wake the saved waker
|
||||||
|
to signal the task when data is available.
|
||||||
|
|
||||||
|
If some error has occurred, you can return `HYPER_POLL_ERROR` to abort
|
||||||
|
the body.
|
||||||
|
*/
|
||||||
|
void hyper_body_set_data_func(struct hyper_body *body, hyper_body_data_callback func);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create a new `hyper_buf *` by copying the provided bytes.
|
||||||
|
|
||||||
|
This makes an owned copy of the bytes, so the `buf` argument can be
|
||||||
|
freed or changed afterwards.
|
||||||
|
|
||||||
|
This returns `NULL` if allocating a new buffer fails.
|
||||||
|
*/
|
||||||
|
struct hyper_buf *hyper_buf_copy(const uint8_t *buf, size_t len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get a pointer to the bytes in this buffer.
|
||||||
|
|
||||||
|
This should be used in conjunction with `hyper_buf_len` to get the length
|
||||||
|
of the bytes data.
|
||||||
|
|
||||||
|
This pointer is borrowed data, and not valid once the `hyper_buf` is
|
||||||
|
consumed/freed.
|
||||||
|
*/
|
||||||
|
const uint8_t *hyper_buf_bytes(const struct hyper_buf *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the length of the bytes this buffer contains.
|
||||||
|
*/
|
||||||
|
size_t hyper_buf_len(const struct hyper_buf *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free this buffer.
|
||||||
|
*/
|
||||||
|
void hyper_buf_free(struct hyper_buf *buf);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Starts an HTTP client connection handshake using the provided IO transport
|
||||||
|
and options.
|
||||||
|
|
||||||
|
Both the `io` and the `options` are consumed in this function call.
|
||||||
|
|
||||||
|
The returned `hyper_task *` must be polled with an executor until the
|
||||||
|
handshake completes, at which point the value can be taken.
|
||||||
|
*/
|
||||||
|
struct hyper_task *hyper_clientconn_handshake(struct hyper_io *io,
|
||||||
|
struct hyper_clientconn_options *options);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Send a request on the client connection.
|
||||||
|
|
||||||
|
Returns a task that needs to be polled until it is ready. When ready, the
|
||||||
|
task yields a `hyper_response *`.
|
||||||
|
*/
|
||||||
|
struct hyper_task *hyper_clientconn_send(struct hyper_clientconn *conn, struct hyper_request *req);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free a `hyper_clientconn *`.
|
||||||
|
*/
|
||||||
|
void hyper_clientconn_free(struct hyper_clientconn *conn);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates a new set of HTTP clientconn options to be used in a handshake.
|
||||||
|
*/
|
||||||
|
struct hyper_clientconn_options *hyper_clientconn_options_new(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the whether or not header case is preserved.
|
||||||
|
|
||||||
|
Pass `0` to allow lowercase normalization (default), `1` to retain original case.
|
||||||
|
*/
|
||||||
|
void hyper_clientconn_options_set_preserve_header_case(struct hyper_clientconn_options *opts,
|
||||||
|
int enabled);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the whether or not header order is preserved.
|
||||||
|
|
||||||
|
Pass `0` to allow reordering (default), `1` to retain original ordering.
|
||||||
|
*/
|
||||||
|
void hyper_clientconn_options_set_preserve_header_order(struct hyper_clientconn_options *opts,
|
||||||
|
int enabled);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free a `hyper_clientconn_options *`.
|
||||||
|
*/
|
||||||
|
void hyper_clientconn_options_free(struct hyper_clientconn_options *opts);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the client background task executor.
|
||||||
|
|
||||||
|
This does not consume the `options` or the `exec`.
|
||||||
|
*/
|
||||||
|
void hyper_clientconn_options_exec(struct hyper_clientconn_options *opts,
|
||||||
|
const struct hyper_executor *exec);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the whether to use HTTP2.
|
||||||
|
|
||||||
|
Pass `0` to disable, `1` to enable.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_clientconn_options_http2(struct hyper_clientconn_options *opts, int enabled);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the whether to include a copy of the raw headers in responses
|
||||||
|
received on this connection.
|
||||||
|
|
||||||
|
Pass `0` to disable, `1` to enable.
|
||||||
|
|
||||||
|
If enabled, see `hyper_response_headers_raw()` for usage.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_clientconn_options_headers_raw(struct hyper_clientconn_options *opts,
|
||||||
|
int enabled);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Frees a `hyper_error`.
|
||||||
|
*/
|
||||||
|
void hyper_error_free(struct hyper_error *err);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get an equivalent `hyper_code` from this error.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_error_code(const struct hyper_error *err);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Print the details of this error to a buffer.
|
||||||
|
|
||||||
|
The `dst_len` value must be the maximum length that the buffer can
|
||||||
|
store.
|
||||||
|
|
||||||
|
The return value is number of bytes that were written to `dst`.
|
||||||
|
*/
|
||||||
|
size_t hyper_error_print(const struct hyper_error *err, uint8_t *dst, size_t dst_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Construct a new HTTP request.
|
||||||
|
*/
|
||||||
|
struct hyper_request *hyper_request_new(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free an HTTP request if not going to send it on a client.
|
||||||
|
*/
|
||||||
|
void hyper_request_free(struct hyper_request *req);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the HTTP Method of the request.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_request_set_method(struct hyper_request *req,
|
||||||
|
const uint8_t *method,
|
||||||
|
size_t method_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the URI of the request.
|
||||||
|
|
||||||
|
The request's URI is best described as the `request-target` from the RFCs. So in HTTP/1,
|
||||||
|
whatever is set will get sent as-is in the first line (GET $uri HTTP/1.1). It
|
||||||
|
supports the 4 defined variants, origin-form, absolute-form, authority-form, and
|
||||||
|
asterisk-form.
|
||||||
|
|
||||||
|
The underlying type was built to efficiently support HTTP/2 where the request-target is
|
||||||
|
split over :scheme, :authority, and :path. As such, each part can be set explicitly, or the
|
||||||
|
type can parse a single contiguous string and if a scheme is found, that slot is "set". If
|
||||||
|
the string just starts with a path, only the path portion is set. All pseudo headers that
|
||||||
|
have been parsed/set are sent when the connection type is HTTP/2.
|
||||||
|
|
||||||
|
To set each slot explicitly, use `hyper_request_set_uri_parts`.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_request_set_uri(struct hyper_request *req,
|
||||||
|
const uint8_t *uri,
|
||||||
|
size_t uri_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the URI of the request with separate scheme, authority, and
|
||||||
|
path/query strings.
|
||||||
|
|
||||||
|
Each of `scheme`, `authority`, and `path_and_query` should either be
|
||||||
|
null, to skip providing a component, or point to a UTF-8 encoded
|
||||||
|
string. If any string pointer argument is non-null, its corresponding
|
||||||
|
`len` parameter must be set to the string's length.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_request_set_uri_parts(struct hyper_request *req,
|
||||||
|
const uint8_t *scheme,
|
||||||
|
size_t scheme_len,
|
||||||
|
const uint8_t *authority,
|
||||||
|
size_t authority_len,
|
||||||
|
const uint8_t *path_and_query,
|
||||||
|
size_t path_and_query_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the preferred HTTP version of the request.
|
||||||
|
|
||||||
|
The version value should be one of the `HYPER_HTTP_VERSION_` constants.
|
||||||
|
|
||||||
|
Note that this won't change the major HTTP version of the connection,
|
||||||
|
since that is determined at the handshake step.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_request_set_version(struct hyper_request *req, int version);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets a reference to the HTTP headers of this request
|
||||||
|
|
||||||
|
This is not an owned reference, so it should not be accessed after the
|
||||||
|
`hyper_request` has been consumed.
|
||||||
|
*/
|
||||||
|
struct hyper_headers *hyper_request_headers(struct hyper_request *req);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the body of the request.
|
||||||
|
|
||||||
|
The default is an empty body.
|
||||||
|
|
||||||
|
This takes ownership of the `hyper_body *`, you must not use it or
|
||||||
|
free it after setting it on the request.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_request_set_body(struct hyper_request *req, struct hyper_body *body);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set an informational (1xx) response callback.
|
||||||
|
|
||||||
|
The callback is called each time hyper receives an informational (1xx)
|
||||||
|
response for this request.
|
||||||
|
|
||||||
|
The third argument is an opaque user data pointer, which is passed to
|
||||||
|
the callback each time.
|
||||||
|
|
||||||
|
The callback is passed the `void *` data pointer, and a
|
||||||
|
`hyper_response *` which can be inspected as any other response. The
|
||||||
|
body of the response will always be empty.
|
||||||
|
|
||||||
|
NOTE: The `hyper_response *` is just borrowed data, and will not
|
||||||
|
be valid after the callback finishes. You must copy any data you wish
|
||||||
|
to persist.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_request_on_informational(struct hyper_request *req,
|
||||||
|
hyper_request_on_informational_callback callback,
|
||||||
|
void *data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free an HTTP response after using it.
|
||||||
|
*/
|
||||||
|
void hyper_response_free(struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the HTTP-Status code of this response.
|
||||||
|
|
||||||
|
It will always be within the range of 100-599.
|
||||||
|
*/
|
||||||
|
uint16_t hyper_response_status(const struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get a pointer to the reason-phrase of this response.
|
||||||
|
|
||||||
|
This buffer is not null-terminated.
|
||||||
|
|
||||||
|
This buffer is owned by the response, and should not be used after
|
||||||
|
the response has been freed.
|
||||||
|
|
||||||
|
Use `hyper_response_reason_phrase_len()` to get the length of this
|
||||||
|
buffer.
|
||||||
|
*/
|
||||||
|
const uint8_t *hyper_response_reason_phrase(const struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the length of the reason-phrase of this response.
|
||||||
|
|
||||||
|
Use `hyper_response_reason_phrase()` to get the buffer pointer.
|
||||||
|
*/
|
||||||
|
size_t hyper_response_reason_phrase_len(const struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get a reference to the full raw headers of this response.
|
||||||
|
|
||||||
|
You must have enabled `hyper_clientconn_options_headers_raw()`, or this
|
||||||
|
will return NULL.
|
||||||
|
|
||||||
|
The returned `hyper_buf *` is just a reference, owned by the response.
|
||||||
|
You need to make a copy if you wish to use it after freeing the
|
||||||
|
response.
|
||||||
|
|
||||||
|
The buffer is not null-terminated, see the `hyper_buf` functions for
|
||||||
|
getting the bytes and length.
|
||||||
|
*/
|
||||||
|
const struct hyper_buf *hyper_response_headers_raw(const struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Get the HTTP version used by this response.
|
||||||
|
|
||||||
|
The returned value could be:
|
||||||
|
|
||||||
|
- `HYPER_HTTP_VERSION_1_0`
|
||||||
|
- `HYPER_HTTP_VERSION_1_1`
|
||||||
|
- `HYPER_HTTP_VERSION_2`
|
||||||
|
- `HYPER_HTTP_VERSION_NONE` if newer (or older).
|
||||||
|
*/
|
||||||
|
int hyper_response_version(const struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Gets a reference to the HTTP headers of this response.
|
||||||
|
|
||||||
|
This is not an owned reference, so it should not be accessed after the
|
||||||
|
`hyper_response` has been freed.
|
||||||
|
*/
|
||||||
|
struct hyper_headers *hyper_response_headers(struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Take ownership of the body of this response.
|
||||||
|
|
||||||
|
It is safe to free the response even after taking ownership of its body.
|
||||||
|
*/
|
||||||
|
struct hyper_body *hyper_response_body(struct hyper_response *resp);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Iterates the headers passing each name and value pair to the callback.
|
||||||
|
|
||||||
|
The `userdata` pointer is also passed to the callback.
|
||||||
|
|
||||||
|
The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or
|
||||||
|
`HYPER_ITER_BREAK` to stop.
|
||||||
|
*/
|
||||||
|
void hyper_headers_foreach(const struct hyper_headers *headers,
|
||||||
|
hyper_headers_foreach_callback func,
|
||||||
|
void *userdata);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Sets the header with the provided name to the provided value.
|
||||||
|
|
||||||
|
This overwrites any previous value set for the header.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_headers_set(struct hyper_headers *headers,
|
||||||
|
const uint8_t *name,
|
||||||
|
size_t name_len,
|
||||||
|
const uint8_t *value,
|
||||||
|
size_t value_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Adds the provided value to the list of the provided name.
|
||||||
|
|
||||||
|
If there were already existing values for the name, this will append the
|
||||||
|
new value to the internal list.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_headers_add(struct hyper_headers *headers,
|
||||||
|
const uint8_t *name,
|
||||||
|
size_t name_len,
|
||||||
|
const uint8_t *value,
|
||||||
|
size_t value_len);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Create a new IO type used to represent a transport.
|
||||||
|
|
||||||
|
The read and write functions of this transport should be set with
|
||||||
|
`hyper_io_set_read` and `hyper_io_set_write`.
|
||||||
|
*/
|
||||||
|
struct hyper_io *hyper_io_new(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free an unused `hyper_io *`.
|
||||||
|
|
||||||
|
This is typically only useful if you aren't going to pass ownership
|
||||||
|
of the IO handle to hyper, such as with `hyper_clientconn_handshake()`.
|
||||||
|
*/
|
||||||
|
void hyper_io_free(struct hyper_io *io);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the user data pointer for this IO to some value.
|
||||||
|
|
||||||
|
This value is passed as an argument to the read and write callbacks.
|
||||||
|
*/
|
||||||
|
void hyper_io_set_userdata(struct hyper_io *io, void *data);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the read function for this IO transport.
|
||||||
|
|
||||||
|
Data that is read from the transport should be put in the `buf` pointer,
|
||||||
|
up to `buf_len` bytes. The number of bytes read should be the return value.
|
||||||
|
|
||||||
|
It is undefined behavior to try to access the bytes in the `buf` pointer,
|
||||||
|
unless you have already written them yourself. It is also undefined behavior
|
||||||
|
to return that more bytes have been written than actually set on the `buf`.
|
||||||
|
|
||||||
|
If there is no data currently available, a waker should be claimed from
|
||||||
|
the `ctx` and registered with whatever polling mechanism is used to signal
|
||||||
|
when data is available later on. The return value should be
|
||||||
|
`HYPER_IO_PENDING`.
|
||||||
|
|
||||||
|
If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`
|
||||||
|
should be the return value.
|
||||||
|
*/
|
||||||
|
void hyper_io_set_read(struct hyper_io *io, hyper_io_read_callback func);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set the write function for this IO transport.
|
||||||
|
|
||||||
|
Data from the `buf` pointer should be written to the transport, up to
|
||||||
|
`buf_len` bytes. The number of bytes written should be the return value.
|
||||||
|
|
||||||
|
If no data can currently be written, the `waker` should be cloned and
|
||||||
|
registered with whatever polling mechanism is used to signal when data
|
||||||
|
is available later on. The return value should be `HYPER_IO_PENDING`.
|
||||||
|
|
||||||
|
Yeet.
|
||||||
|
|
||||||
|
If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`
|
||||||
|
should be the return value.
|
||||||
|
*/
|
||||||
|
void hyper_io_set_write(struct hyper_io *io, hyper_io_write_callback func);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Creates a new task executor.
|
||||||
|
*/
|
||||||
|
const struct hyper_executor *hyper_executor_new(void);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Frees an executor and any incomplete tasks still part of it.
|
||||||
|
*/
|
||||||
|
void hyper_executor_free(const struct hyper_executor *exec);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Push a task onto the executor.
|
||||||
|
|
||||||
|
The executor takes ownership of the task, it should not be accessed
|
||||||
|
again unless returned back to the user with `hyper_executor_poll`.
|
||||||
|
*/
|
||||||
|
enum hyper_code hyper_executor_push(const struct hyper_executor *exec, struct hyper_task *task);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Polls the executor, trying to make progress on any tasks that have notified
|
||||||
|
that they are ready again.
|
||||||
|
|
||||||
|
If ready, returns a task from the executor that has completed.
|
||||||
|
|
||||||
|
If there are no ready tasks, this returns `NULL`.
|
||||||
|
*/
|
||||||
|
struct hyper_task *hyper_executor_poll(const struct hyper_executor *exec);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free a task.
|
||||||
|
*/
|
||||||
|
void hyper_task_free(struct hyper_task *task);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Takes the output value of this task.
|
||||||
|
|
||||||
|
This must only be called once polling the task on an executor has finished
|
||||||
|
this task.
|
||||||
|
|
||||||
|
Use `hyper_task_type` to determine the type of the `void *` return value.
|
||||||
|
*/
|
||||||
|
void *hyper_task_value(struct hyper_task *task);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Query the return type of this task.
|
||||||
|
*/
|
||||||
|
enum hyper_task_return_type hyper_task_type(struct hyper_task *task);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Set a user data pointer to be associated with this task.
|
||||||
|
|
||||||
|
This value will be passed to task callbacks, and can be checked later
|
||||||
|
with `hyper_task_userdata`.
|
||||||
|
*/
|
||||||
|
void hyper_task_set_userdata(struct hyper_task *task, void *userdata);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Retrieve the userdata that has been set via `hyper_task_set_userdata`.
|
||||||
|
*/
|
||||||
|
void *hyper_task_userdata(struct hyper_task *task);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Copies a waker out of the task context.
|
||||||
|
*/
|
||||||
|
struct hyper_waker *hyper_context_waker(struct hyper_context *cx);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Free a waker that hasn't been woken.
|
||||||
|
*/
|
||||||
|
void hyper_waker_free(struct hyper_waker *waker);
|
||||||
|
|
||||||
|
/*
|
||||||
|
Wake up the task associated with a waker.
|
||||||
|
|
||||||
|
NOTE: This consumes the waker. You should not use or free the waker afterwards.
|
||||||
|
*/
|
||||||
|
void hyper_waker_wake(struct hyper_waker *waker);
|
||||||
|
|
||||||
|
#ifdef __cplusplus
|
||||||
|
} // extern "C"
|
||||||
|
#endif // __cplusplus
|
||||||
|
|
||||||
|
#endif /* _HYPER_H */
|
||||||
21
hyper/docs/CODE_OF_CONDUCT.md
Normal file
21
hyper/docs/CODE_OF_CONDUCT.md
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
# Code of Conduct
|
||||||
|
|
||||||
|
## Be Kind
|
||||||
|
|
||||||
|
- Don't be mean.
|
||||||
|
- Insulting anyone is prohibited.
|
||||||
|
- Harassment of any kind is prohibited.
|
||||||
|
- If another person feels uncomfortable with your remarks, stop it.
|
||||||
|
- If a moderator deems your comment or conduct as inappropriate, stop it.
|
||||||
|
- Disagreeing is fine, but keep it to technical arguments. Never attack the person.
|
||||||
|
- Give the benefit of the doubt. Assume good intentions.
|
||||||
|
- Show empathy. There are 3 interpretations to any message: what we thought, what we said, and what they understand.
|
||||||
|
- This does mean we exclude people who are not kind. We are happy to make that sacrifice.
|
||||||
|
|
||||||
|
## Or Else
|
||||||
|
|
||||||
|
- Violations of the Code of Conduct will result in 1 warning.
|
||||||
|
- If the violation is major, a moderator may just ban immediately.
|
||||||
|
- If a warning has already been given, a moderator will ban the offender.
|
||||||
|
- There is no process for appealing a ban.
|
||||||
|
- Any violations can be reported to sean@seanmonstar.com.
|
||||||
65
hyper/docs/COMMITS.md
Normal file
65
hyper/docs/COMMITS.md
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
# Git Commit Guidelines
|
||||||
|
|
||||||
|
We have very precise rules over how our git commit messages can be formatted. This leads to **more
|
||||||
|
readable messages** that are easy to follow when looking through the **project history**. But also,
|
||||||
|
we use the git commit messages to **generate the change log**.
|
||||||
|
|
||||||
|
## Commit Message Format
|
||||||
|
Each commit message consists of a **header**, a **body** and a **footer**. The header has a special
|
||||||
|
format that includes a **type**, a **scope** and a **subject**:
|
||||||
|
|
||||||
|
```
|
||||||
|
<type>(<scope>): <subject>
|
||||||
|
<BLANK LINE>
|
||||||
|
<body>
|
||||||
|
<BLANK LINE>
|
||||||
|
<footer>
|
||||||
|
```
|
||||||
|
|
||||||
|
Any line of the commit message cannot be longer 100 characters! This allows the message to be easier
|
||||||
|
to read on github as well as in various git tools.
|
||||||
|
|
||||||
|
## Type
|
||||||
|
Must be one of the following:
|
||||||
|
|
||||||
|
* **feat**: A new feature
|
||||||
|
* **fix**: A bug fix
|
||||||
|
* **docs**: Documentation only changes
|
||||||
|
* **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing
|
||||||
|
semi-colons, etc)
|
||||||
|
* **refactor**: A code change that neither fixes a bug or adds a feature
|
||||||
|
* **perf**: A code change that improves performance
|
||||||
|
* **test**: Adding missing tests
|
||||||
|
* **chore**: Changes to the build process or auxiliary tools and libraries such as documentation
|
||||||
|
generation
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
The scope should refer to a module in hyper that is being touched. Examples:
|
||||||
|
|
||||||
|
* client
|
||||||
|
* server
|
||||||
|
* http1
|
||||||
|
* http2
|
||||||
|
* ffi
|
||||||
|
* upgrade
|
||||||
|
* examples
|
||||||
|
|
||||||
|
## Subject
|
||||||
|
|
||||||
|
The subject contains succinct description of the change:
|
||||||
|
|
||||||
|
* use the imperative, present tense: "change" not "changed" nor "changes"
|
||||||
|
* don't capitalize first letter
|
||||||
|
* no dot (.) at the end
|
||||||
|
|
||||||
|
## Body
|
||||||
|
|
||||||
|
Just as in the **subject**, use the imperative, present tense: "change" not "changed" nor "changes"
|
||||||
|
The body should include the motivation for the change and contrast this with previous behavior.
|
||||||
|
|
||||||
|
## Footer
|
||||||
|
|
||||||
|
The footer should contain any information about **Breaking Changes** and is also the place to
|
||||||
|
reference GitHub issues that this commit **Closes**.
|
||||||
|
|
||||||
|
The last line of commits introducing breaking changes should be in the form `BREAKING CHANGE: <desc>`
|
||||||
113
hyper/docs/ISSUES.md
Normal file
113
hyper/docs/ISSUES.md
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
# Issues
|
||||||
|
|
||||||
|
The [issue tracker][issues] for hyper is where we track all features, bugs, and discuss proposals.
|
||||||
|
|
||||||
|
## Triaging
|
||||||
|
|
||||||
|
Once an issue has been opened, it is normal for there to be discussion
|
||||||
|
around it. Some contributors may have differing opinions about the issue,
|
||||||
|
including whether the behavior being seen is a bug or a feature. This
|
||||||
|
discussion is part of the process and should be kept focused, helpful, and
|
||||||
|
professional.
|
||||||
|
|
||||||
|
The objective of helping with triaging issues is to help reduce the issue
|
||||||
|
backlog and keep the issue tracker healthy, while enabling newcomers another
|
||||||
|
meaningful way to get engaged and contribute.
|
||||||
|
|
||||||
|
### Acknowledge
|
||||||
|
|
||||||
|
Acknowledge the human. This is meant actively, such as giving a welcome, or
|
||||||
|
thanks for a detailed report, or any other greeting that makes the person feel
|
||||||
|
that their contribution (issues are contributions!) is valued. It also is meant
|
||||||
|
to be internalized, and be sure to always [treat the person kindly][COC]
|
||||||
|
throughout the rest of the steps of triaging.
|
||||||
|
|
||||||
|
### Ask for more info
|
||||||
|
|
||||||
|
Frequently, we need more information than was originally provided to fully
|
||||||
|
evaluate an issue.
|
||||||
|
|
||||||
|
If it is a bug report, ask follow up questions that help us get a [minimum
|
||||||
|
reproducible example][MRE]. This may take several round-trip questions. Once
|
||||||
|
all the details are gathered, it may be helpful to edit the original issue text
|
||||||
|
to include them all.
|
||||||
|
|
||||||
|
### Categorize
|
||||||
|
|
||||||
|
Once enough information has been gathered, the issue should be categorized
|
||||||
|
with [labels][#labels]. Ideally, most issues should be labelled with an area,
|
||||||
|
effort, and severity. An issue _can_ have multiple areas, pick what fits. There
|
||||||
|
should be only one severity, and the descriptions of each should help to pick
|
||||||
|
the right one. The hardest label to select is "effort". If after reading the
|
||||||
|
descriptions of each effort level, you're still unsure, you can ping a
|
||||||
|
maintainer to pick one.
|
||||||
|
|
||||||
|
### Adjust the title
|
||||||
|
|
||||||
|
An optional step when triaging is to adjust the title once more information is
|
||||||
|
known. Sometimes an issue starts as a question, and through discussion, it
|
||||||
|
turns out to be a feature request, or a bug report. In those cases, the title
|
||||||
|
should be changed from a question, and the title should be a succinct action to
|
||||||
|
be taken. For example, a question about an non-existent configuration option
|
||||||
|
may be reworded to "Add option to Client to do Zed".
|
||||||
|
|
||||||
|
### Mentoring
|
||||||
|
|
||||||
|
The last part of triaging is to try to make the issue a learning experience.
|
||||||
|
After a discussion with the reporter, it would be good to ask if they are now
|
||||||
|
interested in submitting the change described in the issue.
|
||||||
|
|
||||||
|
Otherwise, it would be best to leave the issue with a series of steps for
|
||||||
|
anyone else to try to write the change. That could be pointing out that a
|
||||||
|
design proposal is needed, addressing certain points. Or, if the required
|
||||||
|
changes are mostly know, a list of links to modules and functions where code
|
||||||
|
needs to be changed, and to what. That way we mentor newcomers to become
|
||||||
|
successful contributors of new [pull requests][PRs].
|
||||||
|
|
||||||
|
## Labels
|
||||||
|
|
||||||
|
Issues are organized with a set of labels. Most labels follow a system of being prefixed by a "type".
|
||||||
|
|
||||||
|
### Area
|
||||||
|
|
||||||
|
The area labels describe what part of hyper is relevant.
|
||||||
|
|
||||||
|
- **A-body**: streaming request and response bodies.
|
||||||
|
- **A-client**: the HTTP client.
|
||||||
|
- **A-dependencies**: library dependencies.
|
||||||
|
- **A-docs**: documentation.
|
||||||
|
- **A-error**: error reporting and types.
|
||||||
|
- **A-ffi**: the C API.
|
||||||
|
- **A-http1**: the HTTP/1 specifics.
|
||||||
|
- **A-http2**: the HTTP/2 specifics.
|
||||||
|
- **A-server**: the HTTP server.
|
||||||
|
- **A-tests**: the unit and integration tests.
|
||||||
|
|
||||||
|
### Blocked
|
||||||
|
|
||||||
|
These labels indicate an issue is "blocked" for some reason.
|
||||||
|
|
||||||
|
- **B-breaking-change**: a breaking change that is waiting for the next semver-compatible release.
|
||||||
|
- **B-rfc**: request for comments. More discussion is needed to explore the design.
|
||||||
|
- **B-upstream**: waiting on something in a dependency or the compiler.
|
||||||
|
|
||||||
|
### Effort
|
||||||
|
|
||||||
|
The effort labels are a best-guess at roughly how much effort and knowledge of hyper is needed to accomplish the task.
|
||||||
|
|
||||||
|
- **E-easy**: a great starting point for a new contributor.
|
||||||
|
- **E-medium**: some knowledge of how hyper internals work would be useful.
|
||||||
|
- **E-hard**: likely requires a deeper understanding of how hyper internals work.
|
||||||
|
|
||||||
|
### Severity
|
||||||
|
|
||||||
|
The severity marks how _severe_ the issue is. Note this isn't "importance" or "priority".
|
||||||
|
|
||||||
|
- **S-bug**: something is wrong, this is bad!
|
||||||
|
- **S-feature**: this is a new feature request, adding something new.
|
||||||
|
- **S-performance**: make existing working code go faster.
|
||||||
|
- **S-refactor**: improve internal code to help readability and maintenance.
|
||||||
|
|
||||||
|
[issues]: https://github.com/hyperium/hyper/issues
|
||||||
|
[COC]: ./CODE_OF_CONDUCT.md
|
||||||
|
[PRs]: ./PULL_REQUESTS.md
|
||||||
9
hyper/docs/MSRV.md
Normal file
9
hyper/docs/MSRV.md
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
# Minimum Support Rust Version (MSRV)
|
||||||
|
|
||||||
|
hyper's current policy is to always support a Rust version at least 6 months
|
||||||
|
old. That is, a compiler version released within the last 6 months can compile
|
||||||
|
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**.
|
||||||
49
hyper/docs/PULL_REQUESTS.md
Normal file
49
hyper/docs/PULL_REQUESTS.md
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
# Pull Requests
|
||||||
|
|
||||||
|
Pull requests are the way to submit changes to the hyper repository.
|
||||||
|
|
||||||
|
## Submitting a Pull Request
|
||||||
|
|
||||||
|
In most cases, it a good idea to discuss a potential change in an
|
||||||
|
[issue](ISSUES.md). This will allow other contributors to provide guidance and
|
||||||
|
feedback _before_ significant code work is done, and can increase the
|
||||||
|
likelihood of getting the pull request merged.
|
||||||
|
|
||||||
|
### Tests
|
||||||
|
|
||||||
|
If the change being proposed alters code (as opposed to only documentation for
|
||||||
|
example), it is either adding new functionality to hyper or it is fixing
|
||||||
|
existing, broken functionality. In both of these cases, the pull request should
|
||||||
|
include one or more tests to ensure that hyper does not regress in the future.
|
||||||
|
|
||||||
|
### Commits
|
||||||
|
|
||||||
|
Once code, tests, and documentation have been written, a commit needs to be
|
||||||
|
made. Following the [commit guidelines](COMMITS.md) will help with the review
|
||||||
|
process by making your change easier to understand, and makes it easier for
|
||||||
|
hyper to produce a valuable changelog with each release.
|
||||||
|
|
||||||
|
However, if your message doesn't perfectly match the guidelines, **do not
|
||||||
|
worry!** The person that eventually merges can easily fixup the message at that
|
||||||
|
time.
|
||||||
|
|
||||||
|
### Opening the Pull Request
|
||||||
|
|
||||||
|
From within GitHub, open a new pull request from your personal branch.
|
||||||
|
|
||||||
|
Once opened, pull requests are usually reviewed within a few days.
|
||||||
|
|
||||||
|
### Discuss and Update
|
||||||
|
|
||||||
|
You will probably get feedback or requests for changes to your Pull Request.
|
||||||
|
This is a big part of the submission process so don't be discouraged! Some
|
||||||
|
contributors may sign off on the Pull Request right away, others may have more
|
||||||
|
detailed comments or feedback. This is a necessary part of the process in order
|
||||||
|
to evaluate whether the changes are correct and necessary.
|
||||||
|
|
||||||
|
Any community member can review a PR and you might get conflicting feedback.
|
||||||
|
Keep an eye out for comments from code owners to provide guidance on
|
||||||
|
conflicting feedback.
|
||||||
|
|
||||||
|
You don't need to close the PR and create a new one to address feedback. You
|
||||||
|
may simply push new commits to the existing branch.
|
||||||
3
hyper/docs/README.md
Normal file
3
hyper/docs/README.md
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
# Developing hyper
|
||||||
|
|
||||||
|
This is a set of documents outline how hyper is developed, how to contribute or get involved, various processes we use, and internal design explanations.
|
||||||
404
hyper/docs/ROADMAP.md
Normal file
404
hyper/docs/ROADMAP.md
Normal file
|
|
@ -0,0 +1,404 @@
|
||||||
|
# hyper 1.0 Roadmap
|
||||||
|
|
||||||
|
## Goal
|
||||||
|
|
||||||
|
Align current hyper to the [hyper VISION][VISION].
|
||||||
|
|
||||||
|
The VISION outlines a decision-making framework, use-cases, and general shape
|
||||||
|
of hyper. This roadmap describes the currently known problems with hyper, and
|
||||||
|
then shows what changes are needed to make hyper 1.0 look more like what is in
|
||||||
|
the VISION.
|
||||||
|
|
||||||
|
## Known Issues
|
||||||
|
|
||||||
|
|
||||||
|
> **Note**: These known issues are as of hyper v0.14.x. After v1.0 is released,
|
||||||
|
ideally these issues will have been solved. Keeping this history may be helpful
|
||||||
|
to Future Us, though.
|
||||||
|
|
||||||
|
### Higher-level Client and Server problems
|
||||||
|
|
||||||
|
Both the higher-level `Client` and `Server` types have stability concerns.
|
||||||
|
|
||||||
|
For the `hyper::Server`:
|
||||||
|
|
||||||
|
- The `Accept` trait is complex, and too easy to get wrong. If used with TLS, a slow TLS handshake
|
||||||
|
can affect all other new connections waiting for it to finish.
|
||||||
|
- The `MakeService<&IO>` is confusing. The bounds are an assault on the eyes.
|
||||||
|
- The `MakeService` API doesn't allow to easily annotate the HTTP connection with `tracing`.
|
||||||
|
- Graceful shutdown doesn't give enough control.
|
||||||
|
|
||||||
|
|
||||||
|
It's more common for people to simply use `hyper::server::conn` at this point,
|
||||||
|
than to bother with the `hyper::Server`.
|
||||||
|
|
||||||
|
While the `hyper::Client` is much easier to use, problems still exist:
|
||||||
|
|
||||||
|
- The whole `Connect` design isn't stable.
|
||||||
|
- ALPN and proxies can provide surprising extra configuration of connections.
|
||||||
|
- Some `Connect` implementations may wish to view the path, in addition to the scheme, host, and port.
|
||||||
|
- Wants `runtime` feature
|
||||||
|
- The Pool could be made more general or composable. At the same time, more customization is
|
||||||
|
desired, and it's not clear
|
||||||
|
how to expose it yet.
|
||||||
|
|
||||||
|
|
||||||
|
### Runtime woes
|
||||||
|
|
||||||
|
hyper has been able to support different runtimes, but it has sometimes awkward
|
||||||
|
default support for Tokio.
|
||||||
|
|
||||||
|
- The `runtime` cargo-feature isn't additive
|
||||||
|
- Built-in Tokio support can be confusing
|
||||||
|
- Executors and Timers
|
||||||
|
- The `runtime` feature currently enables a few options that require a timer, such as timeouts and
|
||||||
|
keepalive intervals. It implicitly relies on Tokio's timer context. This can be quite confusing.
|
||||||
|
- IO traits
|
||||||
|
- Should we publicly depend on Tokio's traits?
|
||||||
|
- `futures-io`?
|
||||||
|
- Definitely nope.
|
||||||
|
- Not stable. (0.3?)
|
||||||
|
- No uninitialized memory.
|
||||||
|
- Eventual `std` traits?
|
||||||
|
- They've been in design for years.
|
||||||
|
- We cannot base our schedule on them.
|
||||||
|
- When they are stable, we can:
|
||||||
|
- Provide a bridge in `hyper-util`.
|
||||||
|
- Consider a 2.0 of hyper.
|
||||||
|
- Define our own traits, provide util wrappers?
|
||||||
|
|
||||||
|
### Forwards-compatibility
|
||||||
|
|
||||||
|
There's a concern about forwards-compatibility. We want to be able to add
|
||||||
|
support for new HTTP features without needing a new major version. While most
|
||||||
|
of `http` and `hyper` are prepared for that, there's two potential problems.
|
||||||
|
|
||||||
|
- New frames on an HTTP stream (body)
|
||||||
|
- Receiving a new frame type would require a new trait method
|
||||||
|
- There's no way to implement a "receive unknown frame" that hyper doesn't know about.
|
||||||
|
- Sending an unknown frame type would be even harder.
|
||||||
|
- Besides being able to pass an "unknown" type through the trait, the user would need to be
|
||||||
|
able to describe how that frame is encoded in HTTP/2/3.
|
||||||
|
- New HTTP versions
|
||||||
|
- HTTP/3 will require a new transport abstraction. It's not as simple as just using some
|
||||||
|
`impl AsyncRead + AsyncWrite`. While HTTP/2 bundled the concept of stream creation internally,
|
||||||
|
and thus could be managed wholly on top of a read-write transport, HTTP/3 is different. Stream
|
||||||
|
creation is shifted to the QUIC protocol, and HTTP/3 needs to be able to use that directly.
|
||||||
|
- This means the existing `Connection` types for both client and server will not be able to
|
||||||
|
accept a QUIC transport so we can add HTTP/3 support.
|
||||||
|
|
||||||
|
### Errors
|
||||||
|
|
||||||
|
It's not easy to match for specific errors.
|
||||||
|
|
||||||
|
The `Error::source()` can leak an internal dependency. For example, a
|
||||||
|
`hyper::Error` may wrap an `h2::Error`. Users can downcast the source at
|
||||||
|
runtime, and hyper internally changing the version of its `h2` dependency can
|
||||||
|
cause runtime breakage for users.
|
||||||
|
|
||||||
|
Formatting errors is in conflict with the current expected norm. The
|
||||||
|
`fmt::Display` implementation for `hyper::Error` currently prints its own
|
||||||
|
message, and then prints the message of any wrapped source error. The Errors
|
||||||
|
Working Group currently recommends that errors only print their own message
|
||||||
|
(link?). This conflict means that error "reporters", which crawl a source chain
|
||||||
|
and print each error, has a lot of duplicated information.
|
||||||
|
|
||||||
|
```
|
||||||
|
error fetching website: error trying to connect: tcp connect error: Connection refused (os error 61)
|
||||||
|
tcp connect error: Connection refused (os error 61)
|
||||||
|
Connection refused (os error 61)
|
||||||
|
```
|
||||||
|
|
||||||
|
While there is a good reason for why hyper's `Error` types do this, at the very
|
||||||
|
least, it _is_ unfortunate.
|
||||||
|
|
||||||
|
### You call hyper, or hyper calls you?
|
||||||
|
|
||||||
|
> Note: this problem space, of who calls whom, will be explored more deeply in
|
||||||
|
> a future article.
|
||||||
|
|
||||||
|
At times, it's been wondered whether hyper should call user code, or if user
|
||||||
|
code should call hyper. For instance, should a `Service` be called with a
|
||||||
|
request when the connection receives one, or should the user always poll for
|
||||||
|
the next request.
|
||||||
|
|
||||||
|
There's a similar question around sending a message body. Should hyper ask the
|
||||||
|
body for more data to write, or should the user call a `write` method directly?
|
||||||
|
|
||||||
|
These both get at a root topic about [write
|
||||||
|
observability](https://github.com/hyperium/hyper/issues/2181). How do you know
|
||||||
|
when a response, or when body data, has been written successfully? This is
|
||||||
|
desirable for metrics, or for triggering other side-effects.
|
||||||
|
|
||||||
|
The `Service` trait also has some other frequently mentioned issues. Does
|
||||||
|
`poll_ready` pull its complexity weight for servers? What about returning
|
||||||
|
errors, what does that mean? Ideally users would turn all errors into
|
||||||
|
appropriate `http::Response`s. But in HTTP/2 and beyond, stream errors are
|
||||||
|
different from HTTP Server Error responses. Could the `Service::Error` type do
|
||||||
|
more to encourage best practices?
|
||||||
|
|
||||||
|
## Design
|
||||||
|
|
||||||
|
The goal is to get hyper closer to the [VISION][], using that to determine the
|
||||||
|
best way to solve the known issues above. The main thrust of the proposed
|
||||||
|
changes are to make hyper more **Flexible** and stable.
|
||||||
|
|
||||||
|
In order to keep hyper **Understandable**, however, the proposed changes *must*
|
||||||
|
be accompanied by providing utilities that solve the common usage patterns,
|
||||||
|
documentation explaining how to use the more flexible pieces, and guides on how
|
||||||
|
to reach for the `hyper-util`ity belt.
|
||||||
|
|
||||||
|
The majority of the changes are smaller and can be contained to the *Public
|
||||||
|
API* section, since they usually only apply to a single module or type. But the
|
||||||
|
biggest changes are explained in detail here.
|
||||||
|
|
||||||
|
### Split per HTTP version
|
||||||
|
|
||||||
|
The existing `Connection` types, both for the client and server, abstract over
|
||||||
|
HTTP version by requiring a generic `AsyncRead + AsyncWrite` transport type.
|
||||||
|
But as we figure out HTTP/3, that needs to change. So to prepare now, the
|
||||||
|
`Connection` types will be split up.
|
||||||
|
|
||||||
|
For example, there will now be `hyper::server::conn::http1::Connection` and
|
||||||
|
`hyper::server::conn::http2::Connection` types.
|
||||||
|
|
||||||
|
These specific types will still have a very similar looking API that, as the
|
||||||
|
VISION describes, provides **Correct** connection management as it pertains to
|
||||||
|
HTTP.
|
||||||
|
|
||||||
|
There will be still be a type to wrap the different versions. It will no longer
|
||||||
|
be generic over the transport type, to prepare for being able to wrap HTTP/3
|
||||||
|
connections. Exactly how it will wrap, either by using internal trait objects,
|
||||||
|
or an `enum Either` style, or using a `trait Connection` that each type
|
||||||
|
implements, is something to be determined. It's likely that this "auto" type
|
||||||
|
will start in `hyper-util`.
|
||||||
|
|
||||||
|
### Focus on the `Connection` level
|
||||||
|
|
||||||
|
As mentioned in the *Known Issues*, the higher-level `Client` and `Server` have
|
||||||
|
stability and complexity problems. Therefore, for hyper 1.0, the main API will
|
||||||
|
focus on the "lower-level" connection types. The `Client` and `Server` helpers
|
||||||
|
will be moved to `hyper-util`.
|
||||||
|
|
||||||
|
## Public API
|
||||||
|
|
||||||
|
### body
|
||||||
|
|
||||||
|
The `Body` struct is removed. Its internal "variants" are [separated into
|
||||||
|
distinct types](https://github.com/hyperium/hyper/issues/2345), and can start
|
||||||
|
in either `hyper-util` or `http-body-util`.
|
||||||
|
|
||||||
|
The exported trait `HttpBody` is renamed to `Body`.
|
||||||
|
|
||||||
|
A single `Body` implementation in `hyper` is the one provided by receiving
|
||||||
|
client responses and server requests. It has the name `Streaming`.
|
||||||
|
|
||||||
|
> **Unresolved**: Other names can be considered during implementation. Another
|
||||||
|
> option is to not publicly name the implementation, but return `Response<impl
|
||||||
|
Body>`s.
|
||||||
|
|
||||||
|
The `Body` trait will be experimented on to see about making it possible to
|
||||||
|
return more frame types beyonds just data and trailers.
|
||||||
|
|
||||||
|
> **Unresolved**: What exactly this looks like will only be known after
|
||||||
|
> experimentation.
|
||||||
|
|
||||||
|
### client
|
||||||
|
|
||||||
|
The high-level `hyper::Client` will be removed, along with the
|
||||||
|
`hyper::client::connect` module. They will be explored more in `hyper-util`.
|
||||||
|
|
||||||
|
As described in *Design*, the `client::conn` module will gain `http1` and
|
||||||
|
`http2` sub-modules, providing per-version `SendRequest`, `Connection`, and
|
||||||
|
`Builder` structs. An `auto` version can be explored in `hyper-util`.
|
||||||
|
|
||||||
|
### error
|
||||||
|
|
||||||
|
The `hyper::Error` struct remains in place.
|
||||||
|
|
||||||
|
All errors returned from `Error::source()` are made opaque. They are wrapped an
|
||||||
|
internal `Opaque` newtype that still allows printing, but prevents downcasting
|
||||||
|
to the internal dependency.
|
||||||
|
|
||||||
|
A new `hyper::error::Code` struct is defined. It is an opaque struct, with
|
||||||
|
associated constants defining various code variants.
|
||||||
|
|
||||||
|
> Alternative: define a non-exhaustive enum. It's not clear that this is
|
||||||
|
> definitely better, though. Keeping it an opaque struct means we can add
|
||||||
|
> secondary parts to the code in the future, or add bit flags, or similar
|
||||||
|
> extensions.
|
||||||
|
|
||||||
|
The purpose of `Code` is to provide an abstraction over the kind of error that
|
||||||
|
is encountered. The `Code` could be some behavior noticed inside hyper, such as
|
||||||
|
an incomplete HTTP message. Or it can be "translated" from the underlying
|
||||||
|
protocol, if it defines protocol level errors. For example, an
|
||||||
|
`h2::Reason::CANCEL`.
|
||||||
|
|
||||||
|
### rt
|
||||||
|
|
||||||
|
The `Executor` trait stays in here.
|
||||||
|
|
||||||
|
Define a new trait `Timer`, which describes a way for users to provide a source
|
||||||
|
of sleeping/timeout futures. Similar to `Executor`, a new generic is added to
|
||||||
|
connection builders to provide a `Timer`.
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
The higher-level `hyper::Server` struct, its related `Builder`, and the
|
||||||
|
`Accept` trait are all removed.
|
||||||
|
|
||||||
|
The `AddrStream` struct will be completely removed, as it provides no value but
|
||||||
|
causes binary bloat.
|
||||||
|
|
||||||
|
Similar to `client`, and as describe in the *Design*, the `conn` modules will
|
||||||
|
be expanded to support `http1` and `http2` submodules. An `auto` version can be
|
||||||
|
explored in `hyper-util`.
|
||||||
|
|
||||||
|
### service
|
||||||
|
|
||||||
|
A vendored and simplified `Service` trait will be explored.
|
||||||
|
|
||||||
|
The error type for `Service`s used for a server will explore having the return
|
||||||
|
type changed from any error to one that can become a `hyper::error::Code`.
|
||||||
|
|
||||||
|
> **Unresolved**: Both of the above points are not set in stone. We will
|
||||||
|
> explore and decide if they are the best outcome during development.
|
||||||
|
|
||||||
|
The `MakeService` pieces will be removed.
|
||||||
|
|
||||||
|
### Cargo Features
|
||||||
|
|
||||||
|
Remove the `stream` feature. The `Stream` trait is not stable, and we cannot
|
||||||
|
depend on an unstable API.
|
||||||
|
|
||||||
|
Remove the `tcp` and `runtime` features. The automatic executor and timer parts
|
||||||
|
are handled by providing implementations of `Executor` and `Timer`. The
|
||||||
|
`connect` and `Accept` parts are also moving to `hyper-util`.
|
||||||
|
|
||||||
|
### Public Dependencies
|
||||||
|
|
||||||
|
- `http`
|
||||||
|
- `http-body`
|
||||||
|
- `bytes`
|
||||||
|
|
||||||
|
Cannot be public while "unstable":
|
||||||
|
|
||||||
|
- `tracing`
|
||||||
|
|
||||||
|
## `hyper-util`
|
||||||
|
|
||||||
|
|
||||||
|
### body
|
||||||
|
|
||||||
|
A channel implementation of `Body` that has an API to know when the data has
|
||||||
|
been successfully written is provided in `hyper_util::body::channel`.
|
||||||
|
|
||||||
|
### client
|
||||||
|
|
||||||
|
A `Pool` struct that implements `Service` is provided. It fills a similar role
|
||||||
|
as the previous `hyper::Client`.
|
||||||
|
|
||||||
|
> **Note**: The `Pool` might be something that goes into the `tower` crate
|
||||||
|
> instead. Or it might stay here as a slightly more specialized racing-connect
|
||||||
|
> pool. We'll find out as we go.
|
||||||
|
|
||||||
|
A `connect` submodule that mostly mirrors the existing `hyper::client::connect`
|
||||||
|
module is moved here. Connectors can be used as a source to provide `Service`s
|
||||||
|
used by the `Pool`.
|
||||||
|
|
||||||
|
### rt
|
||||||
|
|
||||||
|
We can provide Tokio-backed implementations of `Executor` and `Timer`.
|
||||||
|
|
||||||
|
### server
|
||||||
|
|
||||||
|
A `GracefulShutdown` helper is provided, to allow for similar style of graceful
|
||||||
|
shutdown as the previous `hyper::Server` did, but with better control.
|
||||||
|
|
||||||
|
# Appendix
|
||||||
|
|
||||||
|
## Unresolved Questions
|
||||||
|
|
||||||
|
There are some parts of the proposal which are not fully resolved. They are
|
||||||
|
mentioned in Design and API sections above, but also collected here for easy
|
||||||
|
finding. While they all have _plans_, they are more exploratory parts of the
|
||||||
|
API, and thus they have a higher possibility of changing as we implement them.
|
||||||
|
|
||||||
|
The goal is to have these questions resolved and removed from the document by
|
||||||
|
the time there is a [Release Candidate][timeline].
|
||||||
|
|
||||||
|
### Should there be `hyper::io` traits?
|
||||||
|
|
||||||
|
Depending on `tokio` just for `AsyncRead` and `AsyncWrite` is convenient, but
|
||||||
|
can be confusing for users integrating hyper with other runtimes. It also ties
|
||||||
|
our version directly to Tokio. We can consider having vendored traits, and
|
||||||
|
providing Tokio wrappers in `hyper-util`.
|
||||||
|
|
||||||
|
### Should returned body types be `impl Body`?
|
||||||
|
|
||||||
|
### How could the `Body` trait prepare for unknown frames?
|
||||||
|
|
||||||
|
We will experiment with this, and keep track of those experiments in a
|
||||||
|
dedicated issue. It might be possible to use something like this:
|
||||||
|
|
||||||
|
```rust
|
||||||
|
pub trait Body {
|
||||||
|
type Data;
|
||||||
|
fn poll_frame(..) -> Result<Option<Frame<Self::Data>>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Frame<T>(Kind<T>);
|
||||||
|
|
||||||
|
enum Kind<T> {
|
||||||
|
Data(T),
|
||||||
|
Trailers(HeaderMap),
|
||||||
|
Unknown(Box<dyn FrameThingy>),
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Should there be a simplified `hyper::Service` trait, or should hyper depend on `tower-service`?
|
||||||
|
|
||||||
|
- There's still a few uncertain decisions around tower, such as if it should be
|
||||||
|
changed to `async fn call`, and if `poll_ready` is the best way to handle
|
||||||
|
backpressure.
|
||||||
|
- It's not clear that the backpressure is something needed at the `Server`
|
||||||
|
boundary, thus meaning we should remove `poll_ready` from hyper.
|
||||||
|
- It's not 100% clear if we should keep the service pattern, or use a
|
||||||
|
pull-based API. This will be explored in a future blog post.
|
||||||
|
|
||||||
|
## FAQ
|
||||||
|
|
||||||
|
### Why did you pick _that_ name? Why not this other better name?
|
||||||
|
|
||||||
|
Naming is hard. We certainly should solve it, but discussion for particular
|
||||||
|
names for structs and traits should be scoped to the specific issues. This
|
||||||
|
document is to define the shape of the library API.
|
||||||
|
|
||||||
|
### Should I publicly depend on `hyper-util`?
|
||||||
|
|
||||||
|
The `hyper-util` crate will not reach 1.0 when `hyper` does. Some types and
|
||||||
|
traits are being moved to `hyper-util`. As with any pre-1.0 crate, you _can_
|
||||||
|
publicly depend on it, but it is explicitly less stable.
|
||||||
|
|
||||||
|
In most cases, it's recommended to not publicly expose your dependency on
|
||||||
|
`hyper-util`. If you depend on a trait, such as used by the moved higher-level
|
||||||
|
`Client` or `Server`, it may be better for your users to define your own
|
||||||
|
abstraction, and then make an internal adapter.
|
||||||
|
|
||||||
|
### Isn't this making hyper harder?
|
||||||
|
|
||||||
|
We are making hyper more **flexible**. As noted in the [VISION][], most use
|
||||||
|
cases of hyper require it to be flexible. That _can_ mean that the exposed API
|
||||||
|
is lower level, and that it feels more complicated. It should still be
|
||||||
|
**understandable**.
|
||||||
|
|
||||||
|
But the hyper 1.0 effort is more than just the single `hyper` crate. Many
|
||||||
|
useful helpers will be migrated to a `hyper-util` crate, and likely improved in
|
||||||
|
the process. The [timeline][] also points out that we will have a significant
|
||||||
|
documentation push. While the flexible pieces will be in hyper to compose how
|
||||||
|
they need, we will also write guides for the [hyper.rs][] showing people how to
|
||||||
|
accomplish the most common tasks.
|
||||||
|
|
||||||
|
[timeline]: https://seanmonstar.com/post/676912131372875776/hyper-10-timeline
|
||||||
|
[VISION]: https://github.com/hyperium/hyper/pull/2772
|
||||||
|
[hyper.rs]: https://hyper.rs
|
||||||
100
hyper/docs/TENETS.md
Normal file
100
hyper/docs/TENETS.md
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
# Charter
|
||||||
|
|
||||||
|
> hyper is a protective and efficient HTTP library for all.
|
||||||
|
|
||||||
|
# Tenets
|
||||||
|
|
||||||
|
Tenets are guiding principles. They guide how decisions are made for the whole
|
||||||
|
project. Ideally, we do all of them all the time. In some cases, though, we may
|
||||||
|
be forced to decide between slightly penalizing one goal or another. In that
|
||||||
|
case, we tend to support those goals that come earlier in the list over those
|
||||||
|
that come later (but every case is different).
|
||||||
|
|
||||||
|
## 0. Open
|
||||||
|
|
||||||
|
hyper is open source, always. The success of hyper depends on the health of the
|
||||||
|
community building and using it. All contributions are in the open. We don't
|
||||||
|
maintain private versions, and don't include features that aren't useful to
|
||||||
|
others.
|
||||||
|
|
||||||
|
[We prioritize kindness][CONDUCT], compassion and empathy towards all
|
||||||
|
contributors. Technical skill is not a substitute for human decency.
|
||||||
|
|
||||||
|
[CONDUCT]: https://github.com/hyperium/hyper/blob/master/docs/CODE_OF_CONDUCT.md
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
It's not usually hard for an open source library to stay open and also meet its
|
||||||
|
other priorities. Here's some instances where being **Open** would be more
|
||||||
|
important than **Correct** or **Fast**:
|
||||||
|
|
||||||
|
- Say an individual were to bring forward a contribution that makes hyper more
|
||||||
|
correct, or faster, perhaps fixing some serious bug. But in doing so, they
|
||||||
|
also insulted people, harassed other contributors or users, or shamed
|
||||||
|
everyone for the previous code. They felt their contribution was "invaluable".
|
||||||
|
We would not accept such a contribution, instead banning the user and
|
||||||
|
rewriting the code amongst the kind collaborators of the project.
|
||||||
|
|
||||||
|
- Say someone brings a contribution that adds a new feature useful for
|
||||||
|
performance or correctness, but their work accomplishes this by integrating
|
||||||
|
hyper with a proprietary library. We would not accept such a contribution,
|
||||||
|
because we don't want such a feature limited only to those users willing to
|
||||||
|
compromise openness, and we don't want to bifurcate the ecosystem between those
|
||||||
|
who make that compromise and those who don't.
|
||||||
|
|
||||||
|
## 1. Correct
|
||||||
|
|
||||||
|
hyper is a memory safe and precise implementation of the HTTP specification.
|
||||||
|
Memory safety is vital in a core Internet technology. Following the HTTP
|
||||||
|
specifications correctly protects users. It makes the software durable to the
|
||||||
|
“real world”. Where feasible, hyper enforces correct usage.
|
||||||
|
|
||||||
|
This is more than just "don't write bugs". hyper actively protects the user.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
- Even though we follow the **HTTP/\*** specs, hyper doesn't blindly implement
|
||||||
|
everything without considering if it is safe to do so.
|
||||||
|
|
||||||
|
## 2. Fast
|
||||||
|
|
||||||
|
A fast experience delights users. A faster network library means a faster
|
||||||
|
application, resulting in delighting our users’ users. Whether with one request,
|
||||||
|
or millions.
|
||||||
|
|
||||||
|
Being _fast_ means we improve throughput, drive down CPU usage, and improve
|
||||||
|
sustainability.
|
||||||
|
|
||||||
|
Fast _enough_. We don't sacrifice sanity for speed.
|
||||||
|
|
||||||
|
## 3. HTTP/*
|
||||||
|
|
||||||
|
hyper is specifically focused on HTTP. Supporting new HTTP versions is in scope,
|
||||||
|
but supporting separate protocols is not.
|
||||||
|
|
||||||
|
This also defines what the abstraction layer is: the API is designed around
|
||||||
|
sending and receiving HTTP messages.
|
||||||
|
|
||||||
|
## 4. Flexible
|
||||||
|
|
||||||
|
hyper enables as many usecases as possible. It has no opinion on application
|
||||||
|
structure, and makes few assumptions about its environment. This includes being
|
||||||
|
portable to different operating systems.
|
||||||
|
|
||||||
|
### Examples
|
||||||
|
|
||||||
|
- While we choose safer defaults to be **Correct**, hyper includes options to
|
||||||
|
_allow_ different behavior, when the user requires them.
|
||||||
|
- Providing choice usually makes things more complex, so being **Flexible** does
|
||||||
|
mean it's less _easy_. That can sometimes conflict with simplest way of making
|
||||||
|
hyper **Understandable**.
|
||||||
|
|
||||||
|
## 5. Understandable
|
||||||
|
|
||||||
|
hyper is [no more complicated than it has to
|
||||||
|
be](https://en.wikipedia.org/wiki/Occam%27s_razor). HTTP is not simple. It may
|
||||||
|
not be as "easy" as 1-line to do everything, but it shouldn't be "hard" to find
|
||||||
|
the answers.
|
||||||
|
|
||||||
|
From logical and misuse-resistant APIs, to stellar documentation, to transparent
|
||||||
|
metrics.
|
||||||
230
hyper/docs/VISION.md
Normal file
230
hyper/docs/VISION.md
Normal file
|
|
@ -0,0 +1,230 @@
|
||||||
|
# hyper Vision
|
||||||
|
|
||||||
|
## Purpose
|
||||||
|
|
||||||
|
This is an overview of what the shape of hyper looks like, but also somewhat
|
||||||
|
zoomed out, so that the _vision_ can survive while the exact minute details
|
||||||
|
might shift and change over time.
|
||||||
|
|
||||||
|
### Charter
|
||||||
|
|
||||||
|
> hyper is a protective and efficient HTTP library for all.
|
||||||
|
|
||||||
|
### Tenets
|
||||||
|
|
||||||
|
Tenets are guiding principles. They guide how decisions are made for the whole
|
||||||
|
project. Ideally, we do all of them all the time. In some cases, though, we may
|
||||||
|
be forced to decide between slightly penalizing one goal or another. In that
|
||||||
|
case, we tend to support those goals that come earlier in the list over those
|
||||||
|
that come later (but every case is different).
|
||||||
|
|
||||||
|
0. Open
|
||||||
|
1. Correct
|
||||||
|
2. Fast
|
||||||
|
3. HTTP/\*
|
||||||
|
4. Flexible
|
||||||
|
5. Understandable
|
||||||
|
|
||||||
|
There's a lot more detail about each in [TENETS](./TENETS.md).
|
||||||
|
|
||||||
|
## Use Cases
|
||||||
|
|
||||||
|
Who are the *users* of hyper? How would they use hyper?
|
||||||
|
|
||||||
|
### Low-Level Client Library (curl, reqwest, aws-sdk)
|
||||||
|
|
||||||
|
These client libraries care that hyper is **Flexible**, since they are
|
||||||
|
expressing their own opinion on how a more-featured HTTP client should act.
|
||||||
|
This includes opinions on connection establishment, management, pooling, HTTP
|
||||||
|
version options, and even runtimes.
|
||||||
|
|
||||||
|
curl's main reason for using hyper is that it is **Safe**.
|
||||||
|
|
||||||
|
### Web Server Frameworks (deno, axum)
|
||||||
|
|
||||||
|
These are using hyper's server feature to expose a different, higher-level API
|
||||||
|
to users. Besides the obvious requirements, these require that hyper is
|
||||||
|
**Fast**. Servers are costly, handling more requests faster is important to
|
||||||
|
them.
|
||||||
|
|
||||||
|
That hyper is **Flexible** is also important, in that it needs to be flexible
|
||||||
|
enough for them to build a server framework, and allow them to express their
|
||||||
|
own opinions about API to their users.
|
||||||
|
|
||||||
|
### Services and Proxies (linkerd, cloudflare, fastly)
|
||||||
|
|
||||||
|
These are using hyper directly, likely both the client and server, in order to
|
||||||
|
build efficient and powerful services, applications, and tools for their end
|
||||||
|
users. They care greatly that hyper is **Correct**, since web traffic can
|
||||||
|
stretch the limits of what is valid HTTP, and exercise less-common parts of the
|
||||||
|
specifications.
|
||||||
|
|
||||||
|
They also require hyper to be **Fast**, for similar reasons that the web server
|
||||||
|
frameworks do.
|
||||||
|
|
||||||
|
### New Rust Web Developers
|
||||||
|
|
||||||
|
These are developers who are either new to Rust, or new to web servers, and
|
||||||
|
have reached for hyper to start with.
|
||||||
|
|
||||||
|
It's likely that these users don't have strong opinions about how an HTTP
|
||||||
|
server or client should work, just that it _should_ handle all the things they
|
||||||
|
normally assume it would. For these users, it would be best to quickly help
|
||||||
|
them compare their own expectations with hyper's capabilities, and may
|
||||||
|
suggest reaching for higher-level, _easier_ libraries instead.
|
||||||
|
|
||||||
|
Those that stick around after that recommendation are users that wish both to
|
||||||
|
learn at a lower level, and to pick and choose what batteries they plug in to
|
||||||
|
hyper as they move along. While they do care about the other tenets, that hyper
|
||||||
|
is **Understandable** is of extra importance to them.
|
||||||
|
|
||||||
|
## The Library
|
||||||
|
|
||||||
|
So with all that context in mind, what does hyper, the library, actually look
|
||||||
|
like? This doesn't highlight what _is_ and _isn't_ present. What currently
|
||||||
|
needs to change to reach this vision is left to individual version roadmaps.
|
||||||
|
|
||||||
|
### Layers
|
||||||
|
|
||||||
|
In all cases, a user brings their own runtime and IO to work with hyper. The IO
|
||||||
|
is provided to hyper, and hyper acts on top of it. hyper returns `Future`s that
|
||||||
|
the user then decides how to poll, likely involving their runtime options.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
#### Protocol Codecs
|
||||||
|
|
||||||
|
hyper has dedicated codecs for the major HTTP versions. Each is internally
|
||||||
|
designed to be **Correct** and **Fast** when it comes to encoding and decoding.
|
||||||
|
|
||||||
|
The individual codecs may be implemented as sub-crates, with a less-stable
|
||||||
|
promise, to support the **Flexible** needs of some users who wish to build
|
||||||
|
their own connection management, or customize encoding and decoding beyond what
|
||||||
|
is officially supported.
|
||||||
|
|
||||||
|
#### Connection State Management
|
||||||
|
|
||||||
|
A **Correct** implementation includes more than just enforcing certain
|
||||||
|
characters when encoding and decoding. Order of frames, and flags in certain
|
||||||
|
frames can affect the state of the connection. Some examples of things enforced
|
||||||
|
at this layer:
|
||||||
|
|
||||||
|
- If a message has a `content-length`, enforce only that many bytes are read or
|
||||||
|
written.
|
||||||
|
- Reading a `Response` before a `Request` is even written implies a mismatched
|
||||||
|
reply that should be interpreted as an error.
|
||||||
|
- The presence of some headers, such as `Connection: close`, or the absence of
|
||||||
|
others, such as `content-length` and `transfer-encoding`, can mean that the
|
||||||
|
connection should terminate after the current message.
|
||||||
|
- HTTP/2 and HTTP/3 may send connection-level frames that don't pertain to any
|
||||||
|
specific transaction, and must be read and handled regardless of if a user is
|
||||||
|
currently checking for a message.
|
||||||
|
|
||||||
|
#### HTTP Role and Version Abstraction
|
||||||
|
|
||||||
|
This is the public API layer. Methods exposed are around sending and receiving
|
||||||
|
`http::Request`s and `http::Response`s, not around framing specifics of the
|
||||||
|
different versions. These are built around a client or server `Connection`
|
||||||
|
interface.
|
||||||
|
|
||||||
|
By exposing this layer publicly, we take care of the **Correct** tenet, by not
|
||||||
|
forcing the user to send the specific frames themselves. The API should be
|
||||||
|
designed in a way that a user cannot easily (if at all) create an _incorrect_
|
||||||
|
HTTP connection.
|
||||||
|
|
||||||
|
Motivated by the **Flexible** tenet, there _are_ version-specific options that
|
||||||
|
can be configured at this level, and version-specific functionality can usually
|
||||||
|
be handled via `http::Extensions`.
|
||||||
|
|
||||||
|
### Not quite stable, but utile (useful)
|
||||||
|
|
||||||
|
Beyond what is directly in the hyper crate, there are useful (utile) parts that
|
||||||
|
may not meet hyper's stability promise. Developing, experimenting, and exposing
|
||||||
|
those parts is the purpose of the `hyper-util` crate. That crate does not have
|
||||||
|
the same stability level as hyper. However, the goal is that things that other
|
||||||
|
libraries might want to expose as a public dependency do not live in
|
||||||
|
`hyper-util` forever, but rather stabilize and get promoted into `hyper`.
|
||||||
|
|
||||||
|
Exactly what gets put into `hyper-util` presently is kept in the roadmap
|
||||||
|
documents.
|
||||||
|
|
||||||
|
### Stability Promise
|
||||||
|
|
||||||
|
What even is hyper's stability promise? Does it mean we are "done"? No. Will we
|
||||||
|
ever make breaking changes again? Probably. We'll still follow the [semantic
|
||||||
|
versioning](https://semver.org).
|
||||||
|
|
||||||
|
Prior to 1.0, hyper has already only done breaking changes once a year. So 1
|
||||||
|
year isn't much of a promise. We'll have significant more use and understanding
|
||||||
|
after a few years, and that could prompt some redesign.
|
||||||
|
|
||||||
|
As of this writing, we'll promise that _major_ versions of hyper are stable for
|
||||||
|
3 years. New features will come out in _minor_ versions frequently. If it is
|
||||||
|
determined necessary to make breaking changes to the API, we'll save them for
|
||||||
|
after the 3 years.
|
||||||
|
|
||||||
|
hyper also establishes a Minimum Supported Rust Version (MSRV). hyper will
|
||||||
|
support Rust versions at least 6 months old. If a new Rust version is released
|
||||||
|
with a feature hyper wishes to use, we won't do so until at least 6 months
|
||||||
|
afterwards. hyper will only ever require a new Rust version as a _minor_
|
||||||
|
release (1.x), not as a patch (1.x.y).
|
||||||
|
|
||||||
|
## Security
|
||||||
|
|
||||||
|
The security of hyper is a large part of what makes hyper _protective_. We make
|
||||||
|
hyper secure via the combined efforts of being **Correct**, focusing on
|
||||||
|
**HTTP/\***, and making it all **Understandable**.
|
||||||
|
|
||||||
|
### Memory Safety
|
||||||
|
|
||||||
|
Being **Correct** requires that hyper be memory-safe. Using the Rust language
|
||||||
|
gets us most of the way there. But there is the ability to write `unsafe`
|
||||||
|
Rust. Does being **Correct** mean that we can _never_ write `unsafe` code
|
||||||
|
anywhere? Even if it helps make hyper **Fast**? We can, carefully.
|
||||||
|
|
||||||
|
How do we balance the two, so that hyper is secure?
|
||||||
|
|
||||||
|
hyper prefers not to have large modules of intertwined `unsafe` code. hyper
|
||||||
|
does allow small `unsafe` blocks, no more than a few lines, where it's easier
|
||||||
|
to verify that the `unsafe` code was written **Correctly**.
|
||||||
|
|
||||||
|
### Meticulous Testing
|
||||||
|
|
||||||
|
hyper's test suite grows and grows. There's a lot that needs to be right.
|
||||||
|
Parsers, encoders, state machines. When easily isolated, those pieces have
|
||||||
|
internal unit tests. But hyper also keeps a large list of growing integration
|
||||||
|
tests that make sure all the parts are **Correct**.
|
||||||
|
|
||||||
|
Making writing new tests easy is a high priority. Investing in the testing
|
||||||
|
infrastructure is a proven way to make sure hyper stays **Correct** and secure.
|
||||||
|
|
||||||
|
### Constant Fuzzing
|
||||||
|
|
||||||
|
One thing is to know specific cases to test for. But we can't know all the
|
||||||
|
inputs or states that *might* cause a bug. That's why hyper has rounds of
|
||||||
|
fuzzing built into its CI. It's also why hyper signs up for and uses resources
|
||||||
|
to provide *constant*, around-the-clock fuzzing, always looking for something
|
||||||
|
that hyper should be hardened against.
|
||||||
|
|
||||||
|
### Security Process
|
||||||
|
|
||||||
|
hyper has an outlined
|
||||||
|
[SECURITY](https://github.com/hyperium/hyper/blob/master/SECURITY.md) process,
|
||||||
|
so we can safely report and fix issues.
|
||||||
|
|
||||||
|
## Non-goals
|
||||||
|
|
||||||
|
After writing this up, it is easier to articulate what sorts of things many
|
||||||
|
might associate with an HTTP library, but which are explicitly *not* for hyper.
|
||||||
|
These are all things that definitely **out of scope**.
|
||||||
|
|
||||||
|
- TLS: We learned early that bundling TLS directly in hyper [has
|
||||||
|
problems](https://github.com/hyperium/hyper/issues/985). People also have
|
||||||
|
very strong opinions about which TLS implementation to use. The design of
|
||||||
|
hyper allows users to bring their own TLS.
|
||||||
|
- Routing
|
||||||
|
- Cookies
|
||||||
|
- Not-HTTP: WebSockets, or other protocols that are built next to HTTP. It
|
||||||
|
should be possible to _use_ hyper to upgrade, but the actual next-protocol
|
||||||
|
should be handled by a different library.
|
||||||
4
hyper/docs/vision-arch.svg
Normal file
4
hyper/docs/vision-arch.svg
Normal file
File diff suppressed because one or more lines are too long
|
After Width: | Height: | Size: 9.7 KiB |
31
hyper/src/body/aggregate.rs
Normal file
31
hyper/src/body/aggregate.rs
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
use bytes::Buf;
|
||||||
|
|
||||||
|
use super::HttpBody;
|
||||||
|
use crate::common::buf::BufList;
|
||||||
|
|
||||||
|
/// Aggregate the data buffers from a body asynchronously.
|
||||||
|
///
|
||||||
|
/// The returned `impl Buf` groups the `Buf`s from the `HttpBody` without
|
||||||
|
/// copying them. This is ideal if you don't require a contiguous buffer.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
pub async fn aggregate<T>(body: T) -> Result<impl Buf, T::Error>
|
||||||
|
where
|
||||||
|
T: HttpBody,
|
||||||
|
{
|
||||||
|
let mut bufs = BufList::new();
|
||||||
|
|
||||||
|
futures_util::pin_mut!(body);
|
||||||
|
while let Some(buf) = body.data().await {
|
||||||
|
let buf = buf?;
|
||||||
|
if buf.has_remaining() {
|
||||||
|
bufs.push(buf);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(bufs)
|
||||||
|
}
|
||||||
675
hyper/src/body/body.rs
Normal file
675
hyper/src/body/body.rs
Normal file
|
|
@ -0,0 +1,675 @@
|
||||||
|
use std::borrow::Cow;
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures_channel::mpsc;
|
||||||
|
use futures_channel::oneshot;
|
||||||
|
use futures_core::Stream;
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
use futures_util::TryStreamExt;
|
||||||
|
use http::HeaderMap;
|
||||||
|
use http_body::{Body as HttpBody, SizeHint};
|
||||||
|
use super::DecodedLength;
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
use crate::common::sync_wrapper::SyncWrapper;
|
||||||
|
use crate::common::Future;
|
||||||
|
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||||
|
use crate::common::Never;
|
||||||
|
use crate::common::{task, watch, Pin, Poll};
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
use crate::proto::h2::ping;
|
||||||
|
type BodySender = mpsc::Sender<Result<Bytes, crate::Error>>;
|
||||||
|
type TrailersSender = oneshot::Sender<HeaderMap>;
|
||||||
|
/// A stream of `Bytes`, used when receiving bodies.
|
||||||
|
///
|
||||||
|
/// A good default [`HttpBody`](crate::body::HttpBody) to use in many
|
||||||
|
/// applications.
|
||||||
|
///
|
||||||
|
/// Note: To read the full body, use [`body::to_bytes`](crate::body::to_bytes)
|
||||||
|
/// or [`body::aggregate`](crate::body::aggregate).
|
||||||
|
#[must_use = "streams do nothing unless polled"]
|
||||||
|
pub struct Body {
|
||||||
|
kind: Kind,
|
||||||
|
/// Keep the extra bits in an `Option<Box<Extra>>`, so that
|
||||||
|
/// Body stays small in the common case (no extras needed).
|
||||||
|
extra: Option<Box<Extra>>,
|
||||||
|
}
|
||||||
|
enum Kind {
|
||||||
|
Once(Option<Bytes>),
|
||||||
|
Chan {
|
||||||
|
content_length: DecodedLength,
|
||||||
|
want_tx: watch::Sender,
|
||||||
|
data_rx: mpsc::Receiver<Result<Bytes, crate::Error>>,
|
||||||
|
trailers_rx: oneshot::Receiver<HeaderMap>,
|
||||||
|
},
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
H2 { ping: ping::Recorder, content_length: DecodedLength, recv: h2::RecvStream },
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
Ffi(crate::ffi::UserBody),
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
Wrapped(
|
||||||
|
SyncWrapper<
|
||||||
|
Pin<
|
||||||
|
Box<
|
||||||
|
dyn Stream<
|
||||||
|
Item = Result<Bytes, Box<dyn StdError + Send + Sync>>,
|
||||||
|
> + Send,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
>,
|
||||||
|
),
|
||||||
|
}
|
||||||
|
struct Extra {
|
||||||
|
/// Allow the client to pass a future to delay the `Body` from returning
|
||||||
|
/// EOF. This allows the `Client` to try to put the idle connection
|
||||||
|
/// back into the pool before the body is "finished".
|
||||||
|
///
|
||||||
|
/// The reason for this is so that creating a new request after finishing
|
||||||
|
/// streaming the body of a response could sometimes result in creating
|
||||||
|
/// a brand new connection, since the pool didn't know about the idle
|
||||||
|
/// connection yet.
|
||||||
|
delayed_eof: Option<DelayEof>,
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||||
|
type DelayEofUntil = oneshot::Receiver<Never>;
|
||||||
|
enum DelayEof {
|
||||||
|
/// Initial state, stream hasn't seen EOF yet.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
NotEof(DelayEofUntil),
|
||||||
|
/// Transitions to this state once we've seen `poll` try to
|
||||||
|
/// return EOF (`None`). This future is then polled, and
|
||||||
|
/// when it completes, the Body finally returns EOF (`None`).
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Eof(DelayEofUntil),
|
||||||
|
}
|
||||||
|
/// A sender half created through [`Body::channel()`].
|
||||||
|
///
|
||||||
|
/// Useful when wanting to stream chunks from another thread.
|
||||||
|
///
|
||||||
|
/// ## Body Closing
|
||||||
|
///
|
||||||
|
/// Note that the request body will always be closed normally when the sender is dropped (meaning
|
||||||
|
/// that the empty terminating chunk will be sent to the remote). If you desire to close the
|
||||||
|
/// connection with an incomplete response (e.g. in the case of an error during asynchronous
|
||||||
|
/// processing), call the [`Sender::abort()`] method to abort the body in an abnormal fashion.
|
||||||
|
///
|
||||||
|
/// [`Body::channel()`]: struct.Body.html#method.channel
|
||||||
|
/// [`Sender::abort()`]: struct.Sender.html#method.abort
|
||||||
|
#[must_use = "Sender does nothing unless sent on"]
|
||||||
|
pub struct Sender {
|
||||||
|
want_rx: watch::Receiver,
|
||||||
|
data_tx: BodySender,
|
||||||
|
trailers_tx: Option<TrailersSender>,
|
||||||
|
}
|
||||||
|
const WANT_PENDING: usize = 1;
|
||||||
|
const WANT_READY: usize = 2;
|
||||||
|
impl Body {
|
||||||
|
/// Create an empty `Body` stream.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use hyper::{Body, Request};
|
||||||
|
///
|
||||||
|
/// // create a `GET /` request
|
||||||
|
/// let get = Request::new(Body::empty());
|
||||||
|
/// ```
|
||||||
|
#[inline]
|
||||||
|
pub fn empty() -> Body {
|
||||||
|
Body::new(Kind::Once(None))
|
||||||
|
}
|
||||||
|
/// Create a `Body` stream with an associated sender half.
|
||||||
|
///
|
||||||
|
/// Useful when wanting to stream chunks from another thread.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn channel() -> (Sender, Body) {
|
||||||
|
Self::new_channel(DecodedLength::CHUNKED, false)
|
||||||
|
}
|
||||||
|
pub(crate) fn new_channel(
|
||||||
|
content_length: DecodedLength,
|
||||||
|
wanter: bool,
|
||||||
|
) -> (Sender, Body) {
|
||||||
|
let (data_tx, data_rx) = mpsc::channel(0);
|
||||||
|
let (trailers_tx, trailers_rx) = oneshot::channel();
|
||||||
|
let want = if wanter { WANT_PENDING } else { WANT_READY };
|
||||||
|
let (want_tx, want_rx) = watch::channel(want);
|
||||||
|
let tx = Sender {
|
||||||
|
want_rx,
|
||||||
|
data_tx,
|
||||||
|
trailers_tx: Some(trailers_tx),
|
||||||
|
};
|
||||||
|
let rx = Body::new(Kind::Chan {
|
||||||
|
content_length,
|
||||||
|
want_tx,
|
||||||
|
data_rx,
|
||||||
|
trailers_rx,
|
||||||
|
});
|
||||||
|
(tx, rx)
|
||||||
|
}
|
||||||
|
/// Wrap a futures `Stream` in a box inside `Body`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use hyper::Body;
|
||||||
|
/// let chunks: Vec<Result<_, std::io::Error>> = vec![
|
||||||
|
/// Ok("hello"),
|
||||||
|
/// Ok(" "),
|
||||||
|
/// Ok("world"),
|
||||||
|
/// ];
|
||||||
|
///
|
||||||
|
/// let stream = futures_util::stream::iter(chunks);
|
||||||
|
///
|
||||||
|
/// let body = Body::wrap_stream(stream);
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Optional
|
||||||
|
///
|
||||||
|
/// This function requires enabling the `stream` feature in your
|
||||||
|
/// `Cargo.toml`.
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "stream")))]
|
||||||
|
pub fn wrap_stream<S, O, E>(stream: S) -> Body
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<O, E>> + Send + 'static,
|
||||||
|
O: Into<Bytes> + 'static,
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>> + 'static,
|
||||||
|
{
|
||||||
|
let mapped = stream.map_ok(Into::into).map_err(Into::into);
|
||||||
|
Body::new(Kind::Wrapped(SyncWrapper::new(Box::pin(mapped))))
|
||||||
|
}
|
||||||
|
fn new(kind: Kind) -> Body {
|
||||||
|
Body { kind, extra: None }
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
pub(crate) fn h2(
|
||||||
|
recv: h2::RecvStream,
|
||||||
|
mut content_length: DecodedLength,
|
||||||
|
ping: ping::Recorder,
|
||||||
|
) -> Self {
|
||||||
|
if !content_length.is_exact() && recv.is_end_stream() {
|
||||||
|
content_length = DecodedLength::ZERO;
|
||||||
|
}
|
||||||
|
let body = Body::new(Kind::H2 {
|
||||||
|
ping,
|
||||||
|
content_length,
|
||||||
|
recv,
|
||||||
|
});
|
||||||
|
body
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(crate) fn delayed_eof(&mut self, fut: DelayEofUntil) {
|
||||||
|
self.extra_mut().delayed_eof = Some(DelayEof::NotEof(fut));
|
||||||
|
}
|
||||||
|
fn take_delayed_eof(&mut self) -> Option<DelayEof> {
|
||||||
|
self.extra.as_mut().and_then(|extra| extra.delayed_eof.take())
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
fn extra_mut(&mut self) -> &mut Extra {
|
||||||
|
self.extra.get_or_insert_with(|| Box::new(Extra { delayed_eof: None }))
|
||||||
|
}
|
||||||
|
fn poll_eof(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<crate::Result<Bytes>>> {
|
||||||
|
match self.take_delayed_eof() {
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Some(DelayEof::NotEof(mut delay)) => {
|
||||||
|
match self.poll_inner(cx) {
|
||||||
|
ok @ Poll::Ready(Some(Ok(..))) | ok @ Poll::Pending => {
|
||||||
|
self.extra_mut().delayed_eof = Some(DelayEof::NotEof(delay));
|
||||||
|
ok
|
||||||
|
}
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
match Pin::new(&mut delay).poll(cx) {
|
||||||
|
Poll::Ready(Ok(never)) => match never {}
|
||||||
|
Poll::Pending => {
|
||||||
|
self.extra_mut().delayed_eof = Some(DelayEof::Eof(delay));
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(_done)) => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => Poll::Ready(Some(Err(e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Some(DelayEof::Eof(mut delay)) => {
|
||||||
|
match Pin::new(&mut delay).poll(cx) {
|
||||||
|
Poll::Ready(Ok(never)) => match never {}
|
||||||
|
Poll::Pending => {
|
||||||
|
self.extra_mut().delayed_eof = Some(DelayEof::Eof(delay));
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(_done)) => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(
|
||||||
|
any(
|
||||||
|
not(any(feature = "http1", feature = "http2")),
|
||||||
|
not(feature = "client")
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
Some(delay_eof) => match delay_eof {}
|
||||||
|
None => self.poll_inner(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
pub(crate) fn as_ffi_mut(&mut self) -> &mut crate::ffi::UserBody {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Ffi(ref mut body) => return body,
|
||||||
|
_ => {
|
||||||
|
self.kind = Kind::Ffi(crate::ffi::UserBody::new());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.kind {
|
||||||
|
Kind::Ffi(ref mut body) => body,
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn poll_inner(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<crate::Result<Bytes>>> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Once(ref mut val) => Poll::Ready(val.take().map(Ok)),
|
||||||
|
Kind::Chan {
|
||||||
|
content_length: ref mut len,
|
||||||
|
ref mut data_rx,
|
||||||
|
ref mut want_tx,
|
||||||
|
..
|
||||||
|
} => {
|
||||||
|
want_tx.send(WANT_READY);
|
||||||
|
match ready!(Pin::new(data_rx).poll_next(cx) ?) {
|
||||||
|
Some(chunk) => {
|
||||||
|
len.sub_if(chunk.len() as u64);
|
||||||
|
Poll::Ready(Some(Ok(chunk)))
|
||||||
|
}
|
||||||
|
None => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
Kind::H2 { ref ping, recv: ref mut h2, content_length: ref mut len } => {
|
||||||
|
match ready!(h2.poll_data(cx)) {
|
||||||
|
Some(Ok(bytes)) => {
|
||||||
|
let _ = h2.flow_control().release_capacity(bytes.len());
|
||||||
|
len.sub_if(bytes.len() as u64);
|
||||||
|
ping.record_data(bytes.len());
|
||||||
|
Poll::Ready(Some(Ok(bytes)))
|
||||||
|
}
|
||||||
|
Some(Err(e)) => Poll::Ready(Some(Err(crate::Error::new_body(e)))),
|
||||||
|
None => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
Kind::Ffi(ref mut body) => body.poll_data(cx),
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
Kind::Wrapped(ref mut s) => {
|
||||||
|
match ready!(s.get_mut().as_mut().poll_next(cx)) {
|
||||||
|
Some(res) => Poll::Ready(Some(res.map_err(crate::Error::new_body))),
|
||||||
|
None => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn take_full_data(&mut self) -> Option<Bytes> {
|
||||||
|
if let Kind::Once(ref mut chunk) = self.kind { chunk.take() } else { None }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Default for Body {
|
||||||
|
/// Returns `Body::empty()`.
|
||||||
|
#[inline]
|
||||||
|
fn default() -> Body {
|
||||||
|
Body::empty()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl HttpBody for Body {
|
||||||
|
type Data = Bytes;
|
||||||
|
type Error = crate::Error;
|
||||||
|
fn poll_data(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Data, Self::Error>>> {
|
||||||
|
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<'_>,
|
||||||
|
) -> Poll<Result<Option<HeaderMap>, Self::Error>> {
|
||||||
|
match self.kind {
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
Kind::H2 { recv: ref mut h2, ref ping, .. } => {
|
||||||
|
match ready!(h2.poll_trailers(cx)) {
|
||||||
|
Ok(t) => {
|
||||||
|
ping.record_non_data();
|
||||||
|
Poll::Ready(Ok(t))
|
||||||
|
}
|
||||||
|
Err(e) => Poll::Ready(Err(crate::Error::new_h2(e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Kind::Chan { ref mut trailers_rx, .. } => {
|
||||||
|
match ready!(Pin::new(trailers_rx).poll(cx)) {
|
||||||
|
Ok(t) => Poll::Ready(Ok(Some(t))),
|
||||||
|
Err(_) => Poll::Ready(Ok(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
Kind::Ffi(ref mut body) => body.poll_trailers(cx),
|
||||||
|
_ => Poll::Ready(Ok(None)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn is_end_stream(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Once(ref val) => val.is_none(),
|
||||||
|
Kind::Chan { content_length, .. } => content_length == DecodedLength::ZERO,
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
Kind::H2 { recv: ref h2, .. } => h2.is_end_stream(),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
Kind::Ffi(..) => false,
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
Kind::Wrapped(..) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn size_hint(&self) -> SizeHint {
|
||||||
|
macro_rules! opt_len {
|
||||||
|
($content_length:expr) => {
|
||||||
|
{ let mut hint = SizeHint::default(); if let Some(content_length) =
|
||||||
|
$content_length .into_opt() { hint.set_exact(content_length); } hint }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
match self.kind {
|
||||||
|
Kind::Once(Some(ref val)) => SizeHint::with_exact(val.len() as u64),
|
||||||
|
Kind::Once(None) => SizeHint::with_exact(0),
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
Kind::Wrapped(..) => SizeHint::default(),
|
||||||
|
Kind::Chan { content_length, .. } => opt_len!(content_length),
|
||||||
|
#[cfg(all(feature = "http2", any(feature = "client", feature = "server")))]
|
||||||
|
Kind::H2 { content_length, .. } => opt_len!(content_length),
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
Kind::Ffi(..) => SizeHint::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for Body {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Streaming;
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Empty;
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Full<'a>(&'a Bytes);
|
||||||
|
let mut builder = f.debug_tuple("Body");
|
||||||
|
match self.kind {
|
||||||
|
Kind::Once(None) => builder.field(&Empty),
|
||||||
|
Kind::Once(Some(ref chunk)) => builder.field(&Full(chunk)),
|
||||||
|
_ => builder.field(&Streaming),
|
||||||
|
};
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// # Optional
|
||||||
|
///
|
||||||
|
/// This function requires enabling the `stream` feature in your
|
||||||
|
/// `Cargo.toml`.
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
impl Stream for Body {
|
||||||
|
type Item = crate::Result<Bytes>;
|
||||||
|
fn poll_next(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Self::Item>> {
|
||||||
|
HttpBody::poll_data(self, cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// # Optional
|
||||||
|
///
|
||||||
|
/// This function requires enabling the `stream` feature in your
|
||||||
|
/// `Cargo.toml`.
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
impl From<Box<dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send>>
|
||||||
|
for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(
|
||||||
|
stream: Box<
|
||||||
|
dyn Stream<Item = Result<Bytes, Box<dyn StdError + Send + Sync>>> + Send,
|
||||||
|
>,
|
||||||
|
) -> Body {
|
||||||
|
Body::new(Kind::Wrapped(SyncWrapper::new(stream.into())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Bytes> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(chunk: Bytes) -> Body {
|
||||||
|
if chunk.is_empty() { Body::empty() } else { Body::new(Kind::Once(Some(chunk))) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Vec<u8>> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(vec: Vec<u8>) -> Body {
|
||||||
|
Body::from(Bytes::from(vec))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&'static [u8]> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(slice: &'static [u8]) -> Body {
|
||||||
|
Body::from(Bytes::from(slice))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Cow<'static, [u8]>> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(cow: Cow<'static, [u8]>) -> Body {
|
||||||
|
match cow {
|
||||||
|
Cow::Borrowed(b) => Body::from(b),
|
||||||
|
Cow::Owned(o) => Body::from(o),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<String> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(s: String) -> Body {
|
||||||
|
Body::from(Bytes::from(s.into_bytes()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<&'static str> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(slice: &'static str) -> Body {
|
||||||
|
Body::from(Bytes::from(slice.as_bytes()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<Cow<'static, str>> for Body {
|
||||||
|
#[inline]
|
||||||
|
fn from(cow: Cow<'static, str>) -> Body {
|
||||||
|
match cow {
|
||||||
|
Cow::Borrowed(b) => Body::from(b),
|
||||||
|
Cow::Owned(o) => Body::from(o),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Sender {
|
||||||
|
/// Check to see if this `Sender` can send more data.
|
||||||
|
pub(crate) fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<crate::Result<()>> {
|
||||||
|
ready!(self.poll_want(cx) ?);
|
||||||
|
self.data_tx.poll_ready(cx).map_err(|_| crate::Error::new_closed())
|
||||||
|
}
|
||||||
|
fn poll_want(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
match self.want_rx.load(cx) {
|
||||||
|
WANT_READY => Poll::Ready(Ok(())),
|
||||||
|
WANT_PENDING => Poll::Pending,
|
||||||
|
watch::CLOSED => Poll::Ready(Err(crate::Error::new_closed())),
|
||||||
|
unexpected => unreachable!("want_rx value: {}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
async fn ready(&mut self) -> crate::Result<()> {
|
||||||
|
futures_util::future::poll_fn(|cx| self.poll_ready(cx)).await
|
||||||
|
}
|
||||||
|
/// Send data on data channel when it is ready.
|
||||||
|
pub(crate) async fn send_data(&mut self, chunk: Bytes) -> crate::Result<()> {
|
||||||
|
self.ready().await?;
|
||||||
|
self.data_tx.try_send(Ok(chunk)).map_err(|_| crate::Error::new_closed())
|
||||||
|
}
|
||||||
|
/// Send trailers on trailers channel.
|
||||||
|
pub(crate) async fn send_trailers(
|
||||||
|
&mut self,
|
||||||
|
trailers: HeaderMap,
|
||||||
|
) -> crate::Result<()> {
|
||||||
|
let tx = match self.trailers_tx.take() {
|
||||||
|
Some(tx) => tx,
|
||||||
|
None => return Err(crate::Error::new_closed()),
|
||||||
|
};
|
||||||
|
tx.send(trailers).map_err(|_| crate::Error::new_closed())
|
||||||
|
}
|
||||||
|
/// Try to send data on this channel.
|
||||||
|
///
|
||||||
|
/// # Errors
|
||||||
|
///
|
||||||
|
/// Returns `Err(Bytes)` if the channel could not (currently) accept
|
||||||
|
/// another `Bytes`.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This is mostly useful for when trying to send from some other thread
|
||||||
|
/// that doesn't have an async context. If in an async context, prefer
|
||||||
|
/// `send_data()` instead.
|
||||||
|
pub(crate) fn try_send_data(&mut self, chunk: Bytes) -> Result<(), Bytes> {
|
||||||
|
self.data_tx
|
||||||
|
.try_send(Ok(chunk))
|
||||||
|
.map_err(|err| err.into_inner().expect("just sent Ok"))
|
||||||
|
}
|
||||||
|
/// Aborts the body in an abnormal fashion.
|
||||||
|
pub(crate) fn abort(self) {
|
||||||
|
let _ = self
|
||||||
|
.data_tx
|
||||||
|
.clone()
|
||||||
|
.try_send(Err(crate::Error::new_body_write_aborted()));
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn send_error(&mut self, err: crate::Error) {
|
||||||
|
let _ = self.data_tx.try_send(Err(err));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for Sender {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Open;
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Closed;
|
||||||
|
let mut builder = f.debug_tuple("Sender");
|
||||||
|
match self.want_rx.peek() {
|
||||||
|
watch::CLOSED => builder.field(&Closed),
|
||||||
|
_ => builder.field(&Open),
|
||||||
|
};
|
||||||
|
builder.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::mem;
|
||||||
|
use std::task::Poll;
|
||||||
|
use super::{Body, DecodedLength, HttpBody, Sender, SizeHint};
|
||||||
|
#[test]
|
||||||
|
fn test_size_of() {
|
||||||
|
let body_size = mem::size_of::<Body>();
|
||||||
|
let body_expected_size = mem::size_of::<u64>() * 6;
|
||||||
|
assert!(
|
||||||
|
body_size <= body_expected_size, "Body size = {} <= {}", body_size,
|
||||||
|
body_expected_size,
|
||||||
|
);
|
||||||
|
assert_eq!(body_size, mem::size_of::< Option < Body >> (), "Option<Body>");
|
||||||
|
assert_eq!(
|
||||||
|
mem::size_of::< Sender > (), mem::size_of::< usize > () * 5, "Sender"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
mem::size_of::< Sender > (), mem::size_of::< Option < Sender >> (),
|
||||||
|
"Option<Sender>"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn size_hint() {
|
||||||
|
fn eq(body: Body, b: SizeHint, note: &str) {
|
||||||
|
let a = body.size_hint();
|
||||||
|
assert_eq!(a.lower(), b.lower(), "lower for {:?}", note);
|
||||||
|
assert_eq!(a.upper(), b.upper(), "upper for {:?}", note);
|
||||||
|
}
|
||||||
|
eq(Body::from("Hello"), SizeHint::with_exact(5), "from str");
|
||||||
|
eq(Body::empty(), SizeHint::with_exact(0), "empty");
|
||||||
|
eq(Body::channel().1, SizeHint::new(), "channel");
|
||||||
|
eq(
|
||||||
|
Body::new_channel(DecodedLength::new(4), false).1,
|
||||||
|
SizeHint::with_exact(4),
|
||||||
|
"channel with length",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn channel_abort() {
|
||||||
|
let (tx, mut rx) = Body::channel();
|
||||||
|
tx.abort();
|
||||||
|
let err = rx.data().await.unwrap().unwrap_err();
|
||||||
|
assert!(err.is_body_write_aborted(), "{:?}", err);
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn channel_abort_when_buffer_is_full() {
|
||||||
|
let (mut tx, mut rx) = Body::channel();
|
||||||
|
tx.try_send_data("chunk 1".into()).expect("send 1");
|
||||||
|
tx.abort();
|
||||||
|
let chunk1 = rx.data().await.expect("item 1").expect("chunk 1");
|
||||||
|
assert_eq!(chunk1, "chunk 1");
|
||||||
|
let err = rx.data().await.unwrap().unwrap_err();
|
||||||
|
assert!(err.is_body_write_aborted(), "{:?}", err);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn channel_buffers_one() {
|
||||||
|
let (mut tx, _rx) = Body::channel();
|
||||||
|
tx.try_send_data("chunk 1".into()).expect("send 1");
|
||||||
|
let chunk2 = tx.try_send_data("chunk 2".into()).expect_err("send 2");
|
||||||
|
assert_eq!(chunk2, "chunk 2");
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn channel_empty() {
|
||||||
|
let (_, mut rx) = Body::channel();
|
||||||
|
assert!(rx.data().await.is_none());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn channel_ready() {
|
||||||
|
let (mut tx, _rx) = Body::new_channel(DecodedLength::CHUNKED, false);
|
||||||
|
let mut tx_ready = tokio_test::task::spawn(tx.ready());
|
||||||
|
assert!(tx_ready.poll().is_ready(), "tx is ready immediately");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn channel_wanter() {
|
||||||
|
let (mut tx, mut rx) = Body::new_channel(DecodedLength::CHUNKED, true);
|
||||||
|
let mut tx_ready = tokio_test::task::spawn(tx.ready());
|
||||||
|
let mut rx_data = tokio_test::task::spawn(rx.data());
|
||||||
|
assert!(
|
||||||
|
tx_ready.poll().is_pending(), "tx isn't ready before rx has been polled"
|
||||||
|
);
|
||||||
|
assert!(rx_data.poll().is_pending(), "poll rx.data");
|
||||||
|
assert!(tx_ready.is_woken(), "rx poll wakes tx");
|
||||||
|
assert!(tx_ready.poll().is_ready(), "tx is ready after rx has been polled");
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn channel_notices_closure() {
|
||||||
|
let (mut tx, rx) = Body::new_channel(DecodedLength::CHUNKED, true);
|
||||||
|
let mut tx_ready = tokio_test::task::spawn(tx.ready());
|
||||||
|
assert!(
|
||||||
|
tx_ready.poll().is_pending(), "tx isn't ready before rx has been polled"
|
||||||
|
);
|
||||||
|
drop(rx);
|
||||||
|
assert!(tx_ready.is_woken(), "dropping rx wakes tx");
|
||||||
|
match tx_ready.poll() {
|
||||||
|
Poll::Ready(Err(ref e)) if e.is_closed() => {}
|
||||||
|
unexpected => panic!("tx poll ready unexpected: {:?}", unexpected),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
123
hyper/src/body/length.rs
Normal file
123
hyper/src/body/length.rs
Normal file
|
|
@ -0,0 +1,123 @@
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
|
pub(crate) struct DecodedLength(u64);
|
||||||
|
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl From<Option<u64>> for DecodedLength {
|
||||||
|
fn from(len: Option<u64>) -> Self {
|
||||||
|
len.and_then(|len| {
|
||||||
|
// If the length is u64::MAX, oh well, just reported chunked.
|
||||||
|
Self::checked_new(len).ok()
|
||||||
|
})
|
||||||
|
.unwrap_or(DecodedLength::CHUNKED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2", test))]
|
||||||
|
const MAX_LEN: u64 = std::u64::MAX - 2;
|
||||||
|
|
||||||
|
impl DecodedLength {
|
||||||
|
pub(crate) const CLOSE_DELIMITED: DecodedLength = DecodedLength(::std::u64::MAX);
|
||||||
|
pub(crate) const CHUNKED: DecodedLength = DecodedLength(::std::u64::MAX - 1);
|
||||||
|
pub(crate) const ZERO: DecodedLength = DecodedLength(0);
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn new(len: u64) -> Self {
|
||||||
|
debug_assert!(len <= MAX_LEN);
|
||||||
|
DecodedLength(len)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Takes the length as a content-length without other checks.
|
||||||
|
///
|
||||||
|
/// Should only be called if previously confirmed this isn't
|
||||||
|
/// CLOSE_DELIMITED or CHUNKED.
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn danger_len(self) -> u64 {
|
||||||
|
debug_assert!(self.0 < Self::CHUNKED.0);
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Converts to an Option<u64> representing a Known or Unknown length.
|
||||||
|
pub(crate) fn into_opt(self) -> Option<u64> {
|
||||||
|
match self {
|
||||||
|
DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => None,
|
||||||
|
DecodedLength(known) => Some(known),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Checks the `u64` is within the maximum allowed for content-length.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pub(crate) fn checked_new(len: u64) -> Result<Self, crate::error::Parse> {
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
|
if len <= MAX_LEN {
|
||||||
|
Ok(DecodedLength(len))
|
||||||
|
} else {
|
||||||
|
warn!("content-length bigger than maximum: {} > {}", len, MAX_LEN);
|
||||||
|
Err(crate::error::Parse::TooLarge)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn sub_if(&mut self, amt: u64) {
|
||||||
|
match *self {
|
||||||
|
DecodedLength::CHUNKED | DecodedLength::CLOSE_DELIMITED => (),
|
||||||
|
DecodedLength(ref mut known) => {
|
||||||
|
*known -= amt;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns whether this represents an exact length.
|
||||||
|
///
|
||||||
|
/// This includes 0, which of course is an exact known length.
|
||||||
|
///
|
||||||
|
/// It would return false if "chunked" or otherwise size-unknown.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) fn is_exact(&self) -> bool {
|
||||||
|
self.0 <= MAX_LEN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for DecodedLength {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
DecodedLength::CLOSE_DELIMITED => f.write_str("CLOSE_DELIMITED"),
|
||||||
|
DecodedLength::CHUNKED => f.write_str("CHUNKED"),
|
||||||
|
DecodedLength(n) => f.debug_tuple("DecodedLength").field(&n).finish(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for DecodedLength {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
match *self {
|
||||||
|
DecodedLength::CLOSE_DELIMITED => f.write_str("close-delimited"),
|
||||||
|
DecodedLength::CHUNKED => f.write_str("chunked encoding"),
|
||||||
|
DecodedLength::ZERO => f.write_str("empty"),
|
||||||
|
DecodedLength(n) => write!(f, "content-length ({} bytes)", n),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sub_if_known() {
|
||||||
|
let mut len = DecodedLength::new(30);
|
||||||
|
len.sub_if(20);
|
||||||
|
|
||||||
|
assert_eq!(len.0, 10);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn sub_if_chunked() {
|
||||||
|
let mut len = DecodedLength::CHUNKED;
|
||||||
|
len.sub_if(20);
|
||||||
|
|
||||||
|
assert_eq!(len, DecodedLength::CHUNKED);
|
||||||
|
}
|
||||||
|
}
|
||||||
65
hyper/src/body/mod.rs
Normal file
65
hyper/src/body/mod.rs
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
//! Streaming bodies for Requests and Responses
|
||||||
|
//!
|
||||||
|
//! For both [Clients](crate::client) and [Servers](crate::server), requests and
|
||||||
|
//! responses use streaming bodies, instead of complete buffering. This
|
||||||
|
//! allows applications to not use memory they don't need, and allows exerting
|
||||||
|
//! back-pressure on connections by only reading when asked.
|
||||||
|
//!
|
||||||
|
//! There are two pieces to this in hyper:
|
||||||
|
//!
|
||||||
|
//! - **The [`HttpBody`](HttpBody) trait** describes all possible bodies.
|
||||||
|
//! hyper allows any body type that implements `HttpBody`, allowing
|
||||||
|
//! applications to have fine-grained control over their streaming.
|
||||||
|
//! - **The [`Body`](Body) concrete type**, which is an implementation of
|
||||||
|
//! `HttpBody`, and returned by hyper as a "receive stream" (so, for server
|
||||||
|
//! requests and client responses). It is also a decent default implementation
|
||||||
|
//! if you don't have very custom needs of your send streams.
|
||||||
|
|
||||||
|
pub use bytes::{Buf, Bytes};
|
||||||
|
pub use http_body::Body as HttpBody;
|
||||||
|
pub use http_body::SizeHint;
|
||||||
|
|
||||||
|
pub use self::aggregate::aggregate;
|
||||||
|
pub use self::body::{Body, Sender};
|
||||||
|
pub(crate) use self::length::DecodedLength;
|
||||||
|
pub use self::to_bytes::to_bytes;
|
||||||
|
|
||||||
|
mod aggregate;
|
||||||
|
mod body;
|
||||||
|
mod length;
|
||||||
|
mod to_bytes;
|
||||||
|
|
||||||
|
/// An optimization to try to take a full body if immediately available.
|
||||||
|
///
|
||||||
|
/// This is currently limited to *only* `hyper::Body`s.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn take_full_data<T: HttpBody + 'static>(body: &mut T) -> Option<T::Data> {
|
||||||
|
use std::any::{Any, TypeId};
|
||||||
|
|
||||||
|
// This static type check can be optimized at compile-time.
|
||||||
|
if TypeId::of::<T>() == TypeId::of::<Body>() {
|
||||||
|
let mut full = (body as &mut dyn Any)
|
||||||
|
.downcast_mut::<Body>()
|
||||||
|
.expect("must be Body")
|
||||||
|
.take_full_data();
|
||||||
|
// This second cast is required to make the type system happy.
|
||||||
|
// Without it, the compiler cannot reason that the type is actually
|
||||||
|
// `T::Data`. Oh wells.
|
||||||
|
//
|
||||||
|
// It's still a measurable win!
|
||||||
|
(&mut full as &mut dyn Any)
|
||||||
|
.downcast_mut::<Option<T::Data>>()
|
||||||
|
.expect("must be T::Data")
|
||||||
|
.take()
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn _assert_send_sync() {
|
||||||
|
fn _assert_send<T: Send>() {}
|
||||||
|
fn _assert_sync<T: Sync>() {}
|
||||||
|
|
||||||
|
_assert_send::<Body>();
|
||||||
|
_assert_sync::<Body>();
|
||||||
|
}
|
||||||
82
hyper/src/body/to_bytes.rs
Normal file
82
hyper/src/body/to_bytes.rs
Normal file
|
|
@ -0,0 +1,82 @@
|
||||||
|
use bytes::{Buf, BufMut, Bytes};
|
||||||
|
|
||||||
|
use super::HttpBody;
|
||||||
|
|
||||||
|
/// Concatenate the buffers from a body into a single `Bytes` asynchronously.
|
||||||
|
///
|
||||||
|
/// This may require copying the data into a single buffer. If you don't need
|
||||||
|
/// a contiguous buffer, prefer the [`aggregate`](crate::body::aggregate())
|
||||||
|
/// function.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// 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.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[cfg(all(feature = "client", feature = "tcp", any(feature = "http1", feature = "http2")))]
|
||||||
|
/// # async fn doc() -> hyper::Result<()> {
|
||||||
|
/// use hyper::{body::HttpBody};
|
||||||
|
///
|
||||||
|
/// # let request = hyper::Request::builder()
|
||||||
|
/// # .method(hyper::Method::POST)
|
||||||
|
/// # .uri("http://httpbin.org/post")
|
||||||
|
/// # .header("content-type", "application/json")
|
||||||
|
/// # .body(hyper::Body::from(r#"{"library":"hyper"}"#)).unwrap();
|
||||||
|
/// # let client = hyper::Client::new();
|
||||||
|
/// let response = client.request(request).await?;
|
||||||
|
///
|
||||||
|
/// const MAX_ALLOWED_RESPONSE_SIZE: u64 = 1024;
|
||||||
|
///
|
||||||
|
/// let response_content_length = match response.body().size_hint().upper() {
|
||||||
|
/// Some(v) => v,
|
||||||
|
/// None => MAX_ALLOWED_RESPONSE_SIZE + 1 // Just to protect ourselves from a malicious response
|
||||||
|
/// };
|
||||||
|
///
|
||||||
|
/// if response_content_length < MAX_ALLOWED_RESPONSE_SIZE {
|
||||||
|
/// let body_bytes = hyper::body::to_bytes(response.into_body()).await?;
|
||||||
|
/// println!("body: {:?}", body_bytes);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub async fn to_bytes<T>(body: T) -> Result<Bytes, T::Error>
|
||||||
|
where
|
||||||
|
T: HttpBody,
|
||||||
|
{
|
||||||
|
futures_util::pin_mut!(body);
|
||||||
|
|
||||||
|
// If there's only 1 chunk, we can just return Buf::to_bytes()
|
||||||
|
let mut first = if let Some(buf) = body.data().await {
|
||||||
|
buf?
|
||||||
|
} else {
|
||||||
|
return Ok(Bytes::new());
|
||||||
|
};
|
||||||
|
|
||||||
|
let second = if let Some(buf) = body.data().await {
|
||||||
|
buf?
|
||||||
|
} else {
|
||||||
|
return Ok(first.copy_to_bytes(first.remaining()));
|
||||||
|
};
|
||||||
|
|
||||||
|
// Don't pre-emptively reserve *too* much.
|
||||||
|
let rest = (body.size_hint().lower() as usize).min(1024 * 16);
|
||||||
|
let cap = first
|
||||||
|
.remaining()
|
||||||
|
.saturating_add(second.remaining())
|
||||||
|
.saturating_add(rest);
|
||||||
|
// With more than 1 buf, we gotta flatten into a Vec first.
|
||||||
|
let mut vec = Vec::with_capacity(cap);
|
||||||
|
vec.put(first);
|
||||||
|
vec.put(second);
|
||||||
|
|
||||||
|
while let Some(buf) = body.data().await {
|
||||||
|
vec.put(buf?);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(vec.into())
|
||||||
|
}
|
||||||
44
hyper/src/cfg.rs
Normal file
44
hyper/src/cfg.rs
Normal file
|
|
@ -0,0 +1,44 @@
|
||||||
|
macro_rules! cfg_feature {
|
||||||
|
(
|
||||||
|
#![$meta:meta]
|
||||||
|
$($item:item)*
|
||||||
|
) => {
|
||||||
|
$(
|
||||||
|
#[cfg($meta)]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg($meta)))]
|
||||||
|
$item
|
||||||
|
)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! cfg_proto {
|
||||||
|
($($item:item)*) => {
|
||||||
|
cfg_feature! {
|
||||||
|
#![all(
|
||||||
|
any(feature = "http1", feature = "http2"),
|
||||||
|
any(feature = "client", feature = "server"),
|
||||||
|
)]
|
||||||
|
$($item)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_proto! {
|
||||||
|
macro_rules! cfg_client {
|
||||||
|
($($item:item)*) => {
|
||||||
|
cfg_feature! {
|
||||||
|
#![feature = "client"]
|
||||||
|
$($item)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! cfg_server {
|
||||||
|
($($item:item)*) => {
|
||||||
|
cfg_feature! {
|
||||||
|
#![feature = "server"]
|
||||||
|
$($item)*
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
1305
hyper/src/client/client.rs
Normal file
1305
hyper/src/client/client.rs
Normal file
File diff suppressed because it is too large
Load diff
1025
hyper/src/client/conn.rs
Normal file
1025
hyper/src/client/conn.rs
Normal file
File diff suppressed because it is too large
Load diff
323
hyper/src/client/connect/dns.rs
Normal file
323
hyper/src/client/connect/dns.rs
Normal file
|
|
@ -0,0 +1,323 @@
|
||||||
|
//! DNS Resolution used by the `HttpConnector`.
|
||||||
|
//!
|
||||||
|
//! This module contains:
|
||||||
|
//!
|
||||||
|
//! - A [`GaiResolver`](GaiResolver) that is the default resolver for the
|
||||||
|
//! `HttpConnector`.
|
||||||
|
//! - The `Name` type used as an argument to custom resolvers.
|
||||||
|
//!
|
||||||
|
//! # Resolvers are `Service`s
|
||||||
|
//!
|
||||||
|
//! A resolver is just a
|
||||||
|
//! `Service<Name, Response = impl Iterator<Item = SocketAddr>>`.
|
||||||
|
//!
|
||||||
|
//! A simple resolver that ignores the name and always returns a specific
|
||||||
|
//! address:
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! use std::{convert::Infallible, iter, net::SocketAddr};
|
||||||
|
//!
|
||||||
|
//! let resolver = tower::service_fn(|_name| async {
|
||||||
|
//! Ok::<_, Infallible>(iter::once(SocketAddr::from(([127, 0, 0, 1], 8080))))
|
||||||
|
//! });
|
||||||
|
//! ```
|
||||||
|
use std::error::Error;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::net::{
|
||||||
|
Ipv4Addr, Ipv6Addr, SocketAddr, SocketAddrV4, SocketAddrV6, ToSocketAddrs,
|
||||||
|
};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::str::FromStr;
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
use std::{fmt, io, vec};
|
||||||
|
use tokio::task::JoinHandle;
|
||||||
|
use tower_service::Service;
|
||||||
|
use tracing::debug;
|
||||||
|
pub(super) use self::sealed::Resolve;
|
||||||
|
/// A domain name to resolve into IP addresses.
|
||||||
|
#[derive(Clone, Hash, Eq, PartialEq)]
|
||||||
|
pub struct Name {
|
||||||
|
host: Box<str>,
|
||||||
|
}
|
||||||
|
/// A resolver using blocking `getaddrinfo` calls in a threadpool.
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GaiResolver {
|
||||||
|
_priv: (),
|
||||||
|
}
|
||||||
|
/// An iterator of IP addresses returned from `getaddrinfo`.
|
||||||
|
pub struct GaiAddrs {
|
||||||
|
inner: SocketAddrs,
|
||||||
|
}
|
||||||
|
/// A future to resolve a name returned by `GaiResolver`.
|
||||||
|
pub struct GaiFuture {
|
||||||
|
inner: JoinHandle<Result<SocketAddrs, io::Error>>,
|
||||||
|
}
|
||||||
|
impl Name {
|
||||||
|
pub(super) fn new(host: Box<str>) -> Name {
|
||||||
|
Name { host }
|
||||||
|
}
|
||||||
|
/// View the hostname as a string slice.
|
||||||
|
pub(crate) fn as_str(&self) -> &str {
|
||||||
|
&self.host
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for Name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.host, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for Name {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Display::fmt(&self.host, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl FromStr for Name {
|
||||||
|
type Err = InvalidNameError;
|
||||||
|
fn from_str(host: &str) -> Result<Self, Self::Err> {
|
||||||
|
Ok(Name::new(host.into()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Error indicating a given string was not a valid domain name.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct InvalidNameError(());
|
||||||
|
impl fmt::Display for InvalidNameError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("Not a valid domain name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Error for InvalidNameError {}
|
||||||
|
impl GaiResolver {
|
||||||
|
/// Construct a new `GaiResolver`.
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
GaiResolver { _priv: () }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Service<Name> for GaiResolver {
|
||||||
|
type Response = GaiAddrs;
|
||||||
|
type Error = io::Error;
|
||||||
|
type Future = GaiFuture;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
_cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), io::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
fn call(&mut self, name: Name) -> Self::Future {
|
||||||
|
let blocking = tokio::task::spawn_blocking(move || {
|
||||||
|
debug!("resolving host={:?}", name.host);
|
||||||
|
(&*name.host, 0).to_socket_addrs().map(|i| SocketAddrs { iter: i })
|
||||||
|
});
|
||||||
|
GaiFuture { inner: blocking }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for GaiResolver {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.pad("GaiResolver")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Future for GaiFuture {
|
||||||
|
type Output = Result<GaiAddrs, io::Error>;
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
Pin::new(&mut self.inner)
|
||||||
|
.poll(cx)
|
||||||
|
.map(|res| match res {
|
||||||
|
Ok(Ok(addrs)) => Ok(GaiAddrs { inner: addrs }),
|
||||||
|
Ok(Err(err)) => Err(err),
|
||||||
|
Err(join_err) => {
|
||||||
|
if join_err.is_cancelled() {
|
||||||
|
Err(io::Error::new(io::ErrorKind::Interrupted, join_err))
|
||||||
|
} else {
|
||||||
|
panic!("gai background task failed: {:?}", join_err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for GaiFuture {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.pad("GaiFuture")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Drop for GaiFuture {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.inner.abort();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Iterator for GaiAddrs {
|
||||||
|
type Item = SocketAddr;
|
||||||
|
fn next(&mut self) -> Option<Self::Item> {
|
||||||
|
self.inner.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for GaiAddrs {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.pad("GaiAddrs")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) struct SocketAddrs {
|
||||||
|
iter: vec::IntoIter<SocketAddr>,
|
||||||
|
}
|
||||||
|
impl SocketAddrs {
|
||||||
|
pub(super) fn new(addrs: Vec<SocketAddr>) -> Self {
|
||||||
|
SocketAddrs {
|
||||||
|
iter: addrs.into_iter(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) fn try_parse(host: &str, port: u16) -> Option<SocketAddrs> {
|
||||||
|
if let Ok(addr) = host.parse::<Ipv4Addr>() {
|
||||||
|
let addr = SocketAddrV4::new(addr, port);
|
||||||
|
return Some(SocketAddrs {
|
||||||
|
iter: vec![SocketAddr::V4(addr)].into_iter(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if let Ok(addr) = host.parse::<Ipv6Addr>() {
|
||||||
|
let addr = SocketAddrV6::new(addr, port, 0, 0);
|
||||||
|
return Some(SocketAddrs {
|
||||||
|
iter: vec![SocketAddr::V6(addr)].into_iter(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[inline]
|
||||||
|
fn filter(self, predicate: impl FnMut(&SocketAddr) -> bool) -> SocketAddrs {
|
||||||
|
SocketAddrs::new(self.iter.filter(predicate).collect())
|
||||||
|
}
|
||||||
|
pub(super) fn split_by_preference(
|
||||||
|
self,
|
||||||
|
local_addr_ipv4: Option<Ipv4Addr>,
|
||||||
|
local_addr_ipv6: Option<Ipv6Addr>,
|
||||||
|
) -> (SocketAddrs, SocketAddrs) {
|
||||||
|
match (local_addr_ipv4, local_addr_ipv6) {
|
||||||
|
(Some(_), None) => {
|
||||||
|
(self.filter(SocketAddr::is_ipv4), SocketAddrs::new(vec![]))
|
||||||
|
}
|
||||||
|
(None, Some(_)) => {
|
||||||
|
(self.filter(SocketAddr::is_ipv6), SocketAddrs::new(vec![]))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
let preferring_v6 = self
|
||||||
|
.iter
|
||||||
|
.as_slice()
|
||||||
|
.first()
|
||||||
|
.map(SocketAddr::is_ipv6)
|
||||||
|
.unwrap_or(false);
|
||||||
|
let (preferred, fallback) = self
|
||||||
|
.iter
|
||||||
|
.partition::<Vec<_>, _>(|addr| addr.is_ipv6() == preferring_v6);
|
||||||
|
(SocketAddrs::new(preferred), SocketAddrs::new(fallback))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) fn is_empty(&self) -> bool {
|
||||||
|
self.iter.as_slice().is_empty()
|
||||||
|
}
|
||||||
|
pub(super) fn len(&self) -> usize {
|
||||||
|
self.iter.as_slice().len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Iterator for SocketAddrs {
|
||||||
|
type Item = SocketAddr;
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<SocketAddr> {
|
||||||
|
self.iter.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod sealed {
|
||||||
|
use super::{SocketAddr, Name};
|
||||||
|
use crate::common::{task, Future, Poll};
|
||||||
|
use tower_service::Service;
|
||||||
|
pub trait Resolve {
|
||||||
|
type Addrs: Iterator<Item = SocketAddr>;
|
||||||
|
type Error: Into<Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
type Future: Future<Output = Result<Self::Addrs, Self::Error>>;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>>;
|
||||||
|
fn resolve(&mut self, name: Name) -> Self::Future;
|
||||||
|
}
|
||||||
|
impl<S> Resolve for S
|
||||||
|
where
|
||||||
|
S: Service<Name>,
|
||||||
|
S::Response: Iterator<Item = SocketAddr>,
|
||||||
|
S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Addrs = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = S::Future;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Service::poll_ready(self, cx)
|
||||||
|
}
|
||||||
|
fn resolve(&mut self, name: Name) -> Self::Future {
|
||||||
|
Service::call(self, name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) async fn resolve<R>(
|
||||||
|
resolver: &mut R,
|
||||||
|
name: Name,
|
||||||
|
) -> Result<R::Addrs, R::Error>
|
||||||
|
where
|
||||||
|
R: Resolve,
|
||||||
|
{
|
||||||
|
futures_util::future::poll_fn(|cx| resolver.poll_ready(cx)).await?;
|
||||||
|
resolver.resolve(name).await
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::net::{Ipv4Addr, Ipv6Addr};
|
||||||
|
#[test]
|
||||||
|
fn test_ip_addrs_split_by_preference() {
|
||||||
|
let ip_v4 = Ipv4Addr::new(127, 0, 0, 1);
|
||||||
|
let ip_v6 = Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1);
|
||||||
|
let v4_addr = (ip_v4, 80).into();
|
||||||
|
let v6_addr = (ip_v6, 80).into();
|
||||||
|
let (mut preferred, mut fallback) = SocketAddrs {
|
||||||
|
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||||
|
}
|
||||||
|
.split_by_preference(None, None);
|
||||||
|
assert!(preferred.next().unwrap().is_ipv4());
|
||||||
|
assert!(fallback.next().unwrap().is_ipv6());
|
||||||
|
let (mut preferred, mut fallback) = SocketAddrs {
|
||||||
|
iter: vec![v6_addr, v4_addr].into_iter(),
|
||||||
|
}
|
||||||
|
.split_by_preference(None, None);
|
||||||
|
assert!(preferred.next().unwrap().is_ipv6());
|
||||||
|
assert!(fallback.next().unwrap().is_ipv4());
|
||||||
|
let (mut preferred, mut fallback) = SocketAddrs {
|
||||||
|
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||||
|
}
|
||||||
|
.split_by_preference(Some(ip_v4), Some(ip_v6));
|
||||||
|
assert!(preferred.next().unwrap().is_ipv4());
|
||||||
|
assert!(fallback.next().unwrap().is_ipv6());
|
||||||
|
let (mut preferred, mut fallback) = SocketAddrs {
|
||||||
|
iter: vec![v6_addr, v4_addr].into_iter(),
|
||||||
|
}
|
||||||
|
.split_by_preference(Some(ip_v4), Some(ip_v6));
|
||||||
|
assert!(preferred.next().unwrap().is_ipv6());
|
||||||
|
assert!(fallback.next().unwrap().is_ipv4());
|
||||||
|
let (mut preferred, fallback) = SocketAddrs {
|
||||||
|
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||||
|
}
|
||||||
|
.split_by_preference(Some(ip_v4), None);
|
||||||
|
assert!(preferred.next().unwrap().is_ipv4());
|
||||||
|
assert!(fallback.is_empty());
|
||||||
|
let (mut preferred, fallback) = SocketAddrs {
|
||||||
|
iter: vec![v4_addr, v6_addr].into_iter(),
|
||||||
|
}
|
||||||
|
.split_by_preference(None, Some(ip_v6));
|
||||||
|
assert!(preferred.next().unwrap().is_ipv6());
|
||||||
|
assert!(fallback.is_empty());
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_name_from_str() {
|
||||||
|
const DOMAIN: &str = "test.example.com";
|
||||||
|
let name = Name::from_str(DOMAIN).expect("Should be a valid domain");
|
||||||
|
assert_eq!(name.as_str(), DOMAIN);
|
||||||
|
assert_eq!(name.to_string(), DOMAIN);
|
||||||
|
}
|
||||||
|
}
|
||||||
842
hyper/src/client/connect/http.rs
Normal file
842
hyper/src/client/connect/http.rs
Normal file
|
|
@ -0,0 +1,842 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::io;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr};
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
use std::time::Duration;
|
||||||
|
use futures_util::future::Either;
|
||||||
|
use http::uri::{Scheme, Uri};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use tokio::net::{TcpSocket, TcpStream};
|
||||||
|
use tokio::time::Sleep;
|
||||||
|
use tracing::{debug, trace, warn};
|
||||||
|
use super::dns::{self, resolve, GaiResolver, Resolve};
|
||||||
|
use super::{Connected, Connection};
|
||||||
|
/// A connector for the `http` scheme.
|
||||||
|
///
|
||||||
|
/// Performs DNS resolution in a thread pool, and then connects over TCP.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// Sets the [`HttpInfo`](HttpInfo) value on responses, which includes
|
||||||
|
/// transport information such as the remote socket address used.
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "tcp")))]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct HttpConnector<R = GaiResolver> {
|
||||||
|
config: Arc<Config>,
|
||||||
|
resolver: R,
|
||||||
|
}
|
||||||
|
/// Extra information about the transport when an HttpConnector is used.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # async fn doc() -> hyper::Result<()> {
|
||||||
|
/// use hyper::Uri;
|
||||||
|
/// use hyper::client::{Client, connect::HttpInfo};
|
||||||
|
///
|
||||||
|
/// let client = Client::new();
|
||||||
|
/// let uri = Uri::from_static("http://example.com");
|
||||||
|
///
|
||||||
|
/// let res = client.get(uri).await?;
|
||||||
|
/// res
|
||||||
|
/// .extensions()
|
||||||
|
/// .get::<HttpInfo>()
|
||||||
|
/// .map(|info| {
|
||||||
|
/// println!("remote addr = {}", info.remote_addr());
|
||||||
|
/// });
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// If a different connector is used besides [`HttpConnector`](HttpConnector),
|
||||||
|
/// this value will not exist in the extensions. Consult that specific
|
||||||
|
/// connector to see what "extra" information it might provide to responses.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub struct HttpInfo {
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
local_addr: SocketAddr,
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct Config {
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
|
enforce_http: bool,
|
||||||
|
happy_eyeballs_timeout: Option<Duration>,
|
||||||
|
keep_alive_timeout: Option<Duration>,
|
||||||
|
local_address_ipv4: Option<Ipv4Addr>,
|
||||||
|
local_address_ipv6: Option<Ipv6Addr>,
|
||||||
|
nodelay: bool,
|
||||||
|
reuse_address: bool,
|
||||||
|
send_buffer_size: Option<usize>,
|
||||||
|
recv_buffer_size: Option<usize>,
|
||||||
|
}
|
||||||
|
impl HttpConnector {
|
||||||
|
/// Construct a new HttpConnector.
|
||||||
|
pub(crate) fn new() -> HttpConnector {
|
||||||
|
HttpConnector::new_with_resolver(GaiResolver::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<R> HttpConnector<R> {
|
||||||
|
/// Construct a new HttpConnector.
|
||||||
|
///
|
||||||
|
/// Takes a [`Resolver`](crate::client::connect::dns#resolvers-are-services) to handle DNS lookups.
|
||||||
|
pub(crate) fn new_with_resolver(resolver: R) -> HttpConnector<R> {
|
||||||
|
HttpConnector {
|
||||||
|
config: Arc::new(Config {
|
||||||
|
connect_timeout: None,
|
||||||
|
enforce_http: true,
|
||||||
|
happy_eyeballs_timeout: Some(Duration::from_millis(300)),
|
||||||
|
keep_alive_timeout: None,
|
||||||
|
local_address_ipv4: None,
|
||||||
|
local_address_ipv6: None,
|
||||||
|
nodelay: false,
|
||||||
|
reuse_address: false,
|
||||||
|
send_buffer_size: None,
|
||||||
|
recv_buffer_size: None,
|
||||||
|
}),
|
||||||
|
resolver,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Option to enforce all `Uri`s have the `http` scheme.
|
||||||
|
///
|
||||||
|
/// Enabled by default.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn enforce_http(&mut self, is_enforced: bool) {
|
||||||
|
self.config_mut().enforce_http = is_enforced;
|
||||||
|
}
|
||||||
|
/// Set that all sockets have `SO_KEEPALIVE` set with the supplied duration.
|
||||||
|
///
|
||||||
|
/// If `None`, the option will not be set.
|
||||||
|
///
|
||||||
|
/// Default is `None`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_keepalive(&mut self, dur: Option<Duration>) {
|
||||||
|
self.config_mut().keep_alive_timeout = dur;
|
||||||
|
}
|
||||||
|
/// Set that all sockets have `SO_NODELAY` set to the supplied value `nodelay`.
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_nodelay(&mut self, nodelay: bool) {
|
||||||
|
self.config_mut().nodelay = nodelay;
|
||||||
|
}
|
||||||
|
/// Sets the value of the SO_SNDBUF option on the socket.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_send_buffer_size(&mut self, size: Option<usize>) {
|
||||||
|
self.config_mut().send_buffer_size = size;
|
||||||
|
}
|
||||||
|
/// Sets the value of the SO_RCVBUF option on the socket.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_recv_buffer_size(&mut self, size: Option<usize>) {
|
||||||
|
self.config_mut().recv_buffer_size = size;
|
||||||
|
}
|
||||||
|
/// Set that all sockets are bound to the configured address before connection.
|
||||||
|
///
|
||||||
|
/// If `None`, the sockets will not be bound.
|
||||||
|
///
|
||||||
|
/// Default is `None`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_local_address(&mut self, addr: Option<IpAddr>) {
|
||||||
|
let (v4, v6) = match addr {
|
||||||
|
Some(IpAddr::V4(a)) => (Some(a), None),
|
||||||
|
Some(IpAddr::V6(a)) => (None, Some(a)),
|
||||||
|
_ => (None, None),
|
||||||
|
};
|
||||||
|
let cfg = self.config_mut();
|
||||||
|
cfg.local_address_ipv4 = v4;
|
||||||
|
cfg.local_address_ipv6 = v6;
|
||||||
|
}
|
||||||
|
/// Set that all sockets are bound to the configured IPv4 or IPv6 address (depending on host's
|
||||||
|
/// preferences) before connection.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_local_addresses(
|
||||||
|
&mut self,
|
||||||
|
addr_ipv4: Ipv4Addr,
|
||||||
|
addr_ipv6: Ipv6Addr,
|
||||||
|
) {
|
||||||
|
let cfg = self.config_mut();
|
||||||
|
cfg.local_address_ipv4 = Some(addr_ipv4);
|
||||||
|
cfg.local_address_ipv6 = Some(addr_ipv6);
|
||||||
|
}
|
||||||
|
/// Set the connect timeout.
|
||||||
|
///
|
||||||
|
/// If a domain resolves to multiple IP addresses, the timeout will be
|
||||||
|
/// evenly divided across them.
|
||||||
|
///
|
||||||
|
/// Default is `None`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_connect_timeout(&mut self, dur: Option<Duration>) {
|
||||||
|
self.config_mut().connect_timeout = dur;
|
||||||
|
}
|
||||||
|
/// Set timeout for [RFC 6555 (Happy Eyeballs)][RFC 6555] algorithm.
|
||||||
|
///
|
||||||
|
/// If hostname resolves to both IPv4 and IPv6 addresses and connection
|
||||||
|
/// cannot be established using preferred address family before timeout
|
||||||
|
/// elapses, then connector will in parallel attempt connection using other
|
||||||
|
/// address family.
|
||||||
|
///
|
||||||
|
/// If `None`, parallel connection attempts are disabled.
|
||||||
|
///
|
||||||
|
/// Default is 300 milliseconds.
|
||||||
|
///
|
||||||
|
/// [RFC 6555]: https://tools.ietf.org/html/rfc6555
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_happy_eyeballs_timeout(&mut self, dur: Option<Duration>) {
|
||||||
|
self.config_mut().happy_eyeballs_timeout = dur;
|
||||||
|
}
|
||||||
|
/// Set that all socket have `SO_REUSEADDR` set to the supplied value `reuse_address`.
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn set_reuse_address(&mut self, reuse_address: bool) -> &mut Self {
|
||||||
|
self.config_mut().reuse_address = reuse_address;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn config_mut(&mut self) -> &mut Config {
|
||||||
|
Arc::make_mut(&mut self.config)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
static INVALID_NOT_HTTP: &str = "invalid URL, scheme is not http";
|
||||||
|
static INVALID_MISSING_SCHEME: &str = "invalid URL, scheme is missing";
|
||||||
|
static INVALID_MISSING_HOST: &str = "invalid URL, host is missing";
|
||||||
|
impl<R: fmt::Debug> fmt::Debug for HttpConnector<R> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("HttpConnector").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<R> tower_service::Service<Uri> for HttpConnector<R>
|
||||||
|
where
|
||||||
|
R: Resolve + Clone + Send + Sync + 'static,
|
||||||
|
R::Future: Send,
|
||||||
|
{
|
||||||
|
type Response = TcpStream;
|
||||||
|
type Error = ConnectError;
|
||||||
|
type Future = HttpConnecting<R>;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
ready!(self.resolver.poll_ready(cx)).map_err(ConnectError::dns)?;
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
fn call(&mut self, dst: Uri) -> Self::Future {
|
||||||
|
let mut self_ = self.clone();
|
||||||
|
HttpConnecting {
|
||||||
|
fut: Box::pin(async move { self_.call_async(dst).await }),
|
||||||
|
_marker: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn get_host_port<'u>(
|
||||||
|
config: &Config,
|
||||||
|
dst: &'u Uri,
|
||||||
|
) -> Result<(&'u str, u16), ConnectError> {
|
||||||
|
trace!(
|
||||||
|
"Http::connect; scheme={:?}, host={:?}, port={:?}", dst.scheme(), dst.host(), dst
|
||||||
|
.port(),
|
||||||
|
);
|
||||||
|
if config.enforce_http {
|
||||||
|
if dst.scheme() != Some(&Scheme::HTTP) {
|
||||||
|
return Err(ConnectError {
|
||||||
|
msg: INVALID_NOT_HTTP.into(),
|
||||||
|
cause: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} else if dst.scheme().is_none() {
|
||||||
|
return Err(ConnectError {
|
||||||
|
msg: INVALID_MISSING_SCHEME.into(),
|
||||||
|
cause: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
let host = match dst.host() {
|
||||||
|
Some(s) => s,
|
||||||
|
None => {
|
||||||
|
return Err(ConnectError {
|
||||||
|
msg: INVALID_MISSING_HOST.into(),
|
||||||
|
cause: None,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let port = match dst.port() {
|
||||||
|
Some(port) => port.as_u16(),
|
||||||
|
None => if dst.scheme() == Some(&Scheme::HTTPS) { 443 } else { 80 }
|
||||||
|
};
|
||||||
|
Ok((host, port))
|
||||||
|
}
|
||||||
|
impl<R> HttpConnector<R>
|
||||||
|
where
|
||||||
|
R: Resolve,
|
||||||
|
{
|
||||||
|
async fn call_async(&mut self, dst: Uri) -> Result<TcpStream, ConnectError> {
|
||||||
|
let config = &self.config;
|
||||||
|
let (host, port) = get_host_port(config, &dst)?;
|
||||||
|
let host = host.trim_start_matches('[').trim_end_matches(']');
|
||||||
|
let addrs = if let Some(addrs) = dns::SocketAddrs::try_parse(host, port) {
|
||||||
|
addrs
|
||||||
|
} else {
|
||||||
|
let addrs = resolve(&mut self.resolver, dns::Name::new(host.into()))
|
||||||
|
.await
|
||||||
|
.map_err(ConnectError::dns)?;
|
||||||
|
let addrs = addrs
|
||||||
|
.map(|mut addr| {
|
||||||
|
addr.set_port(port);
|
||||||
|
addr
|
||||||
|
})
|
||||||
|
.collect();
|
||||||
|
dns::SocketAddrs::new(addrs)
|
||||||
|
};
|
||||||
|
let c = ConnectingTcp::new(addrs, config);
|
||||||
|
let sock = c.connect().await?;
|
||||||
|
if let Err(e) = sock.set_nodelay(config.nodelay) {
|
||||||
|
warn!("tcp set_nodelay error: {}", e);
|
||||||
|
}
|
||||||
|
Ok(sock)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Connection for TcpStream {
|
||||||
|
fn connected(&self) -> Connected {
|
||||||
|
let connected = Connected::new();
|
||||||
|
if let (Ok(remote_addr), Ok(local_addr))
|
||||||
|
= (self.peer_addr(), self.local_addr()) {
|
||||||
|
connected
|
||||||
|
.extra(HttpInfo {
|
||||||
|
remote_addr,
|
||||||
|
local_addr,
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
connected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl HttpInfo {
|
||||||
|
/// Get the remote address of the transport used.
|
||||||
|
pub(crate) fn remote_addr(&self) -> SocketAddr {
|
||||||
|
self.remote_addr
|
||||||
|
}
|
||||||
|
/// Get the local address of the transport used.
|
||||||
|
pub(crate) fn local_addr(&self) -> SocketAddr {
|
||||||
|
self.local_addr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pin_project! {
|
||||||
|
#[must_use = "futures do nothing unless polled"]
|
||||||
|
#[allow(missing_debug_implementations)] pub struct HttpConnecting < R > { #[pin] fut
|
||||||
|
: BoxConnecting, _marker : PhantomData < R >, }
|
||||||
|
}
|
||||||
|
type ConnectResult = Result<TcpStream, ConnectError>;
|
||||||
|
type BoxConnecting = Pin<Box<dyn Future<Output = ConnectResult> + Send>>;
|
||||||
|
impl<R: Resolve> Future for HttpConnecting<R> {
|
||||||
|
type Output = ConnectResult;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.project().fut.poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub struct ConnectError {
|
||||||
|
msg: Box<str>,
|
||||||
|
cause: Option<Box<dyn StdError + Send + Sync>>,
|
||||||
|
}
|
||||||
|
impl ConnectError {
|
||||||
|
fn new<S, E>(msg: S, cause: E) -> ConnectError
|
||||||
|
where
|
||||||
|
S: Into<Box<str>>,
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
ConnectError {
|
||||||
|
msg: msg.into(),
|
||||||
|
cause: Some(cause.into()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn dns<E>(cause: E) -> ConnectError
|
||||||
|
where
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
ConnectError::new("dns error", cause)
|
||||||
|
}
|
||||||
|
fn m<S, E>(msg: S) -> impl FnOnce(E) -> ConnectError
|
||||||
|
where
|
||||||
|
S: Into<Box<str>>,
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
move |cause| ConnectError::new(msg, cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for ConnectError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(ref cause) = self.cause {
|
||||||
|
f.debug_tuple("ConnectError").field(&self.msg).field(cause).finish()
|
||||||
|
} else {
|
||||||
|
self.msg.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for ConnectError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str(&self.msg)?;
|
||||||
|
if let Some(ref cause) = self.cause {
|
||||||
|
write!(f, ": {}", cause)?;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl StdError for ConnectError {
|
||||||
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
self.cause.as_ref().map(|e| &**e as _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct ConnectingTcp<'a> {
|
||||||
|
preferred: ConnectingTcpRemote,
|
||||||
|
fallback: Option<ConnectingTcpFallback>,
|
||||||
|
config: &'a Config,
|
||||||
|
}
|
||||||
|
impl<'a> ConnectingTcp<'a> {
|
||||||
|
fn new(remote_addrs: dns::SocketAddrs, config: &'a Config) -> Self {
|
||||||
|
if let Some(fallback_timeout) = config.happy_eyeballs_timeout {
|
||||||
|
let (preferred_addrs, fallback_addrs) = remote_addrs
|
||||||
|
.split_by_preference(
|
||||||
|
config.local_address_ipv4,
|
||||||
|
config.local_address_ipv6,
|
||||||
|
);
|
||||||
|
if fallback_addrs.is_empty() {
|
||||||
|
return ConnectingTcp {
|
||||||
|
preferred: ConnectingTcpRemote::new(
|
||||||
|
preferred_addrs,
|
||||||
|
config.connect_timeout,
|
||||||
|
),
|
||||||
|
fallback: None,
|
||||||
|
config,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
ConnectingTcp {
|
||||||
|
preferred: ConnectingTcpRemote::new(
|
||||||
|
preferred_addrs,
|
||||||
|
config.connect_timeout,
|
||||||
|
),
|
||||||
|
fallback: Some(ConnectingTcpFallback {
|
||||||
|
delay: tokio::time::sleep(fallback_timeout),
|
||||||
|
remote: ConnectingTcpRemote::new(
|
||||||
|
fallback_addrs,
|
||||||
|
config.connect_timeout,
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ConnectingTcp {
|
||||||
|
preferred: ConnectingTcpRemote::new(
|
||||||
|
remote_addrs,
|
||||||
|
config.connect_timeout,
|
||||||
|
),
|
||||||
|
fallback: None,
|
||||||
|
config,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct ConnectingTcpFallback {
|
||||||
|
delay: Sleep,
|
||||||
|
remote: ConnectingTcpRemote,
|
||||||
|
}
|
||||||
|
struct ConnectingTcpRemote {
|
||||||
|
addrs: dns::SocketAddrs,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
|
}
|
||||||
|
impl ConnectingTcpRemote {
|
||||||
|
fn new(addrs: dns::SocketAddrs, connect_timeout: Option<Duration>) -> Self {
|
||||||
|
let connect_timeout = connect_timeout.map(|t| t / (addrs.len() as u32));
|
||||||
|
Self { addrs, connect_timeout }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl ConnectingTcpRemote {
|
||||||
|
async fn connect(&mut self, config: &Config) -> Result<TcpStream, ConnectError> {
|
||||||
|
let mut err = None;
|
||||||
|
for addr in &mut self.addrs {
|
||||||
|
debug!("connecting to {}", addr);
|
||||||
|
match connect(&addr, config, self.connect_timeout)?.await {
|
||||||
|
Ok(tcp) => {
|
||||||
|
debug!("connected to {}", addr);
|
||||||
|
return Ok(tcp);
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
trace!("connect error for {}: {:?}", addr, e);
|
||||||
|
err = Some(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match err {
|
||||||
|
Some(e) => Err(e),
|
||||||
|
None => {
|
||||||
|
Err(
|
||||||
|
ConnectError::new(
|
||||||
|
"tcp connect error",
|
||||||
|
std::io::Error::new(
|
||||||
|
std::io::ErrorKind::NotConnected,
|
||||||
|
"Network unreachable",
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn bind_local_address(
|
||||||
|
socket: &socket2::Socket,
|
||||||
|
dst_addr: &SocketAddr,
|
||||||
|
local_addr_ipv4: &Option<Ipv4Addr>,
|
||||||
|
local_addr_ipv6: &Option<Ipv6Addr>,
|
||||||
|
) -> io::Result<()> {
|
||||||
|
match (*dst_addr, local_addr_ipv4, local_addr_ipv6) {
|
||||||
|
(SocketAddr::V4(_), Some(addr), _) => {
|
||||||
|
socket.bind(&SocketAddr::new(addr.clone().into(), 0).into())?;
|
||||||
|
}
|
||||||
|
(SocketAddr::V6(_), _, Some(addr)) => {
|
||||||
|
socket.bind(&SocketAddr::new(addr.clone().into(), 0).into())?;
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
if cfg!(windows) {
|
||||||
|
let any: SocketAddr = match *dst_addr {
|
||||||
|
SocketAddr::V4(_) => ([0, 0, 0, 0], 0).into(),
|
||||||
|
SocketAddr::V6(_) => ([0, 0, 0, 0, 0, 0, 0, 0], 0).into(),
|
||||||
|
};
|
||||||
|
socket.bind(&any.into())?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
fn connect(
|
||||||
|
addr: &SocketAddr,
|
||||||
|
config: &Config,
|
||||||
|
connect_timeout: Option<Duration>,
|
||||||
|
) -> Result<impl Future<Output = Result<TcpStream, ConnectError>>, ConnectError> {
|
||||||
|
use socket2::{Domain, Protocol, Socket, TcpKeepalive, Type};
|
||||||
|
use std::convert::TryInto;
|
||||||
|
let domain = Domain::for_address(*addr);
|
||||||
|
let socket = Socket::new(domain, Type::STREAM, Some(Protocol::TCP))
|
||||||
|
.map_err(ConnectError::m("tcp open error"))?;
|
||||||
|
socket.set_nonblocking(true).map_err(ConnectError::m("tcp set_nonblocking error"))?;
|
||||||
|
if let Some(dur) = config.keep_alive_timeout {
|
||||||
|
let conf = TcpKeepalive::new().with_time(dur);
|
||||||
|
if let Err(e) = socket.set_tcp_keepalive(&conf) {
|
||||||
|
warn!("tcp set_keepalive error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
bind_local_address(
|
||||||
|
&socket,
|
||||||
|
addr,
|
||||||
|
&config.local_address_ipv4,
|
||||||
|
&config.local_address_ipv6,
|
||||||
|
)
|
||||||
|
.map_err(ConnectError::m("tcp bind local error"))?;
|
||||||
|
#[cfg(unix)]
|
||||||
|
let socket = unsafe {
|
||||||
|
use std::os::unix::io::{FromRawFd, IntoRawFd};
|
||||||
|
TcpSocket::from_raw_fd(socket.into_raw_fd())
|
||||||
|
};
|
||||||
|
#[cfg(windows)]
|
||||||
|
let socket = unsafe {
|
||||||
|
use std::os::windows::io::{FromRawSocket, IntoRawSocket};
|
||||||
|
TcpSocket::from_raw_socket(socket.into_raw_socket())
|
||||||
|
};
|
||||||
|
if config.reuse_address {
|
||||||
|
if let Err(e) = socket.set_reuseaddr(true) {
|
||||||
|
warn!("tcp set_reuse_address error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(size) = config.send_buffer_size {
|
||||||
|
if let Err(e)
|
||||||
|
= socket.set_send_buffer_size(size.try_into().unwrap_or(std::u32::MAX))
|
||||||
|
{
|
||||||
|
warn!("tcp set_buffer_size error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(size) = config.recv_buffer_size {
|
||||||
|
if let Err(e)
|
||||||
|
= socket.set_recv_buffer_size(size.try_into().unwrap_or(std::u32::MAX))
|
||||||
|
{
|
||||||
|
warn!("tcp set_recv_buffer_size error: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let connect = socket.connect(*addr);
|
||||||
|
Ok(async move {
|
||||||
|
match connect_timeout {
|
||||||
|
Some(dur) => {
|
||||||
|
match tokio::time::timeout(dur, connect).await {
|
||||||
|
Ok(Ok(s)) => Ok(s),
|
||||||
|
Ok(Err(e)) => Err(e),
|
||||||
|
Err(e) => Err(io::Error::new(io::ErrorKind::TimedOut, e)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => connect.await,
|
||||||
|
}
|
||||||
|
.map_err(ConnectError::m("tcp connect error"))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
impl ConnectingTcp<'_> {
|
||||||
|
async fn connect(mut self) -> Result<TcpStream, ConnectError> {
|
||||||
|
match self.fallback {
|
||||||
|
None => self.preferred.connect(self.config).await,
|
||||||
|
Some(mut fallback) => {
|
||||||
|
let preferred_fut = self.preferred.connect(self.config);
|
||||||
|
futures_util::pin_mut!(preferred_fut);
|
||||||
|
let fallback_fut = fallback.remote.connect(self.config);
|
||||||
|
futures_util::pin_mut!(fallback_fut);
|
||||||
|
let fallback_delay = fallback.delay;
|
||||||
|
futures_util::pin_mut!(fallback_delay);
|
||||||
|
let (result, future) = match futures_util::future::select(
|
||||||
|
preferred_fut,
|
||||||
|
fallback_delay,
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
{
|
||||||
|
Either::Left((result, _fallback_delay)) => {
|
||||||
|
(result, Either::Right(fallback_fut))
|
||||||
|
}
|
||||||
|
Either::Right(((), preferred_fut)) => {
|
||||||
|
futures_util::future::select(preferred_fut, fallback_fut)
|
||||||
|
.await
|
||||||
|
.factor_first()
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if result.is_err() { future.await } else { result }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::io;
|
||||||
|
use ::http::Uri;
|
||||||
|
use super::super::sealed::{Connect, ConnectSvc};
|
||||||
|
use super::{Config, ConnectError, HttpConnector};
|
||||||
|
async fn connect<C>(
|
||||||
|
connector: C,
|
||||||
|
dst: Uri,
|
||||||
|
) -> Result<<C::_Svc as ConnectSvc>::Connection, <C::_Svc as ConnectSvc>::Error>
|
||||||
|
where
|
||||||
|
C: Connect,
|
||||||
|
{
|
||||||
|
connector.connect(super::super::sealed::Internal, dst).await
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_errors_enforce_http() {
|
||||||
|
let dst = "https://example.domain/foo/bar?baz".parse().unwrap();
|
||||||
|
let connector = HttpConnector::new();
|
||||||
|
let err = connect(connector, dst).await.unwrap_err();
|
||||||
|
assert_eq!(&* err.msg, super::INVALID_NOT_HTTP);
|
||||||
|
}
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
fn get_local_ips() -> (Option<std::net::Ipv4Addr>, Option<std::net::Ipv6Addr>) {
|
||||||
|
use std::net::{IpAddr, TcpListener};
|
||||||
|
let mut ip_v4 = None;
|
||||||
|
let mut ip_v6 = None;
|
||||||
|
let ips = pnet_datalink::interfaces()
|
||||||
|
.into_iter()
|
||||||
|
.flat_map(|i| i.ips.into_iter().map(|n| n.ip()));
|
||||||
|
for ip in ips {
|
||||||
|
match ip {
|
||||||
|
IpAddr::V4(ip) if TcpListener::bind((ip, 0)).is_ok() => ip_v4 = Some(ip),
|
||||||
|
IpAddr::V6(ip) if TcpListener::bind((ip, 0)).is_ok() => ip_v6 = Some(ip),
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
if ip_v4.is_some() && ip_v6.is_some() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(ip_v4, ip_v6)
|
||||||
|
}
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_errors_missing_scheme() {
|
||||||
|
let dst = "example.domain".parse().unwrap();
|
||||||
|
let mut connector = HttpConnector::new();
|
||||||
|
connector.enforce_http(false);
|
||||||
|
let err = connect(connector, dst).await.unwrap_err();
|
||||||
|
assert_eq!(&* err.msg, super::INVALID_MISSING_SCHEME);
|
||||||
|
}
|
||||||
|
#[cfg(any(target_os = "linux", target_os = "macos"))]
|
||||||
|
#[tokio::test]
|
||||||
|
async fn local_address() {
|
||||||
|
use std::net::{IpAddr, TcpListener};
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let (bind_ip_v4, bind_ip_v6) = get_local_ips();
|
||||||
|
let server4 = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let port = server4.local_addr().unwrap().port();
|
||||||
|
let server6 = TcpListener::bind(&format!("[::1]:{}", port)).unwrap();
|
||||||
|
let assert_client_ip = |dst: String, server: TcpListener, expected_ip: IpAddr| async move {
|
||||||
|
let mut connector = HttpConnector::new();
|
||||||
|
match (bind_ip_v4, bind_ip_v6) {
|
||||||
|
(Some(v4), Some(v6)) => connector.set_local_addresses(v4, v6),
|
||||||
|
(Some(v4), None) => connector.set_local_address(Some(v4.into())),
|
||||||
|
(None, Some(v6)) => connector.set_local_address(Some(v6.into())),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
connect(connector, dst.parse().unwrap()).await.unwrap();
|
||||||
|
let (_, client_addr) = server.accept().unwrap();
|
||||||
|
assert_eq!(client_addr.ip(), expected_ip);
|
||||||
|
};
|
||||||
|
if let Some(ip) = bind_ip_v4 {
|
||||||
|
assert_client_ip(format!("http://127.0.0.1:{}", port), server4, ip.into())
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
if let Some(ip) = bind_ip_v6 {
|
||||||
|
assert_client_ip(format!("http://[::1]:{}", port), server6, ip.into()).await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
#[cfg_attr(not(feature = "__internal_happy_eyeballs_tests"), ignore)]
|
||||||
|
fn client_happy_eyeballs() {
|
||||||
|
use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, TcpListener};
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
use super::dns;
|
||||||
|
use super::ConnectingTcp;
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
let server4 = TcpListener::bind("127.0.0.1:0").unwrap();
|
||||||
|
let addr = server4.local_addr().unwrap();
|
||||||
|
let _server6 = TcpListener::bind(&format!("[::1]:{}", addr.port())).unwrap();
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let local_timeout = Duration::default();
|
||||||
|
let unreachable_v4_timeout = measure_connect(unreachable_ipv4_addr()).1;
|
||||||
|
let unreachable_v6_timeout = measure_connect(unreachable_ipv6_addr()).1;
|
||||||
|
let fallback_timeout = std::cmp::max(
|
||||||
|
unreachable_v4_timeout,
|
||||||
|
unreachable_v6_timeout,
|
||||||
|
) + Duration::from_millis(250);
|
||||||
|
let scenarios = &[
|
||||||
|
(&[local_ipv4_addr()][..], 4, local_timeout, false),
|
||||||
|
(&[local_ipv6_addr()][..], 6, local_timeout, false),
|
||||||
|
(&[local_ipv4_addr(), local_ipv6_addr()][..], 4, local_timeout, false),
|
||||||
|
(&[local_ipv6_addr(), local_ipv4_addr()][..], 6, local_timeout, false),
|
||||||
|
(
|
||||||
|
&[unreachable_ipv4_addr(), local_ipv4_addr()][..],
|
||||||
|
4,
|
||||||
|
unreachable_v4_timeout,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[unreachable_ipv6_addr(), local_ipv6_addr()][..],
|
||||||
|
6,
|
||||||
|
unreachable_v6_timeout,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[unreachable_ipv4_addr(), local_ipv4_addr(), local_ipv6_addr()][..],
|
||||||
|
4,
|
||||||
|
unreachable_v4_timeout,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[unreachable_ipv6_addr(), local_ipv6_addr(), local_ipv4_addr()][..],
|
||||||
|
6,
|
||||||
|
unreachable_v6_timeout,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[slow_ipv4_addr(), local_ipv4_addr(), local_ipv6_addr()][..],
|
||||||
|
6,
|
||||||
|
fallback_timeout,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[slow_ipv6_addr(), local_ipv6_addr(), local_ipv4_addr()][..],
|
||||||
|
4,
|
||||||
|
fallback_timeout,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[slow_ipv4_addr(), unreachable_ipv6_addr(), local_ipv6_addr()][..],
|
||||||
|
6,
|
||||||
|
fallback_timeout + unreachable_v6_timeout,
|
||||||
|
false,
|
||||||
|
),
|
||||||
|
(
|
||||||
|
&[slow_ipv6_addr(), unreachable_ipv4_addr(), local_ipv4_addr()][..],
|
||||||
|
4,
|
||||||
|
fallback_timeout + unreachable_v4_timeout,
|
||||||
|
true,
|
||||||
|
),
|
||||||
|
];
|
||||||
|
let ipv6_accessible = measure_connect(slow_ipv6_addr()).0;
|
||||||
|
for &(hosts, family, timeout, needs_ipv6_access) in scenarios {
|
||||||
|
if needs_ipv6_access && !ipv6_accessible {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (start, stream) = rt
|
||||||
|
.block_on(async move {
|
||||||
|
let addrs = hosts
|
||||||
|
.iter()
|
||||||
|
.map(|host| (host.clone(), addr.port()).into())
|
||||||
|
.collect();
|
||||||
|
let cfg = Config {
|
||||||
|
local_address_ipv4: None,
|
||||||
|
local_address_ipv6: None,
|
||||||
|
connect_timeout: None,
|
||||||
|
keep_alive_timeout: None,
|
||||||
|
happy_eyeballs_timeout: Some(fallback_timeout),
|
||||||
|
nodelay: false,
|
||||||
|
reuse_address: false,
|
||||||
|
enforce_http: false,
|
||||||
|
send_buffer_size: None,
|
||||||
|
recv_buffer_size: None,
|
||||||
|
};
|
||||||
|
let connecting_tcp = ConnectingTcp::new(
|
||||||
|
dns::SocketAddrs::new(addrs),
|
||||||
|
&cfg,
|
||||||
|
);
|
||||||
|
let start = Instant::now();
|
||||||
|
Ok::<
|
||||||
|
_,
|
||||||
|
ConnectError,
|
||||||
|
>((start, ConnectingTcp::connect(connecting_tcp).await?))
|
||||||
|
})
|
||||||
|
.unwrap();
|
||||||
|
let res = if stream.peer_addr().unwrap().is_ipv4() { 4 } else { 6 };
|
||||||
|
let duration = start.elapsed();
|
||||||
|
let min_duration = if timeout >= Duration::from_millis(150) {
|
||||||
|
timeout - Duration::from_millis(150)
|
||||||
|
} else {
|
||||||
|
Duration::default()
|
||||||
|
};
|
||||||
|
let max_duration = timeout + Duration::from_millis(150);
|
||||||
|
assert_eq!(res, family);
|
||||||
|
assert!(duration >= min_duration);
|
||||||
|
assert!(duration <= max_duration);
|
||||||
|
}
|
||||||
|
fn local_ipv4_addr() -> IpAddr {
|
||||||
|
Ipv4Addr::new(127, 0, 0, 1).into()
|
||||||
|
}
|
||||||
|
fn local_ipv6_addr() -> IpAddr {
|
||||||
|
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 1).into()
|
||||||
|
}
|
||||||
|
fn unreachable_ipv4_addr() -> IpAddr {
|
||||||
|
Ipv4Addr::new(127, 0, 0, 2).into()
|
||||||
|
}
|
||||||
|
fn unreachable_ipv6_addr() -> IpAddr {
|
||||||
|
Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 2).into()
|
||||||
|
}
|
||||||
|
fn slow_ipv4_addr() -> IpAddr {
|
||||||
|
Ipv4Addr::new(198, 18, 0, 25).into()
|
||||||
|
}
|
||||||
|
fn slow_ipv6_addr() -> IpAddr {
|
||||||
|
Ipv6Addr::new(2001, 2, 0, 0, 0, 0, 0, 254).into()
|
||||||
|
}
|
||||||
|
fn measure_connect(addr: IpAddr) -> (bool, Duration) {
|
||||||
|
let start = Instant::now();
|
||||||
|
let result = std::net::TcpStream::connect_timeout(
|
||||||
|
&(addr, 80).into(),
|
||||||
|
Duration::from_secs(1),
|
||||||
|
);
|
||||||
|
let reachable = result.is_ok()
|
||||||
|
|| result.unwrap_err().kind() == io::ErrorKind::TimedOut;
|
||||||
|
let duration = start.elapsed();
|
||||||
|
(reachable, duration)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
343
hyper/src/client/connect/mod.rs
Normal file
343
hyper/src/client/connect/mod.rs
Normal file
|
|
@ -0,0 +1,343 @@
|
||||||
|
//! Connectors used by the `Client`.
|
||||||
|
//!
|
||||||
|
//! This module contains:
|
||||||
|
//!
|
||||||
|
//! - A default [`HttpConnector`][] that does DNS resolution and establishes
|
||||||
|
//! connections over TCP.
|
||||||
|
//! - Types to build custom connectors.
|
||||||
|
//!
|
||||||
|
//! # Connectors
|
||||||
|
//!
|
||||||
|
//! A "connector" is a [`Service`][] that takes a [`Uri`][] destination, and
|
||||||
|
//! its `Response` is some type implementing [`AsyncRead`][], [`AsyncWrite`][],
|
||||||
|
//! and [`Connection`][].
|
||||||
|
//!
|
||||||
|
//! ## Custom Connectors
|
||||||
|
//!
|
||||||
|
//! A simple connector that ignores the `Uri` destination and always returns
|
||||||
|
//! a TCP connection to the same address could be written like this:
|
||||||
|
//!
|
||||||
|
//! ```rust,ignore
|
||||||
|
//! let connector = tower::service_fn(|_dst| async {
|
||||||
|
//! tokio::net::TcpStream::connect("127.0.0.1:1337")
|
||||||
|
//! })
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Or, fully written out:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(feature = "runtime")]
|
||||||
|
//! # mod rt {
|
||||||
|
//! use std::{future::Future, net::SocketAddr, pin::Pin, task::{self, Poll}};
|
||||||
|
//! use hyper::{service::Service, Uri};
|
||||||
|
//! use tokio::net::TcpStream;
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct LocalConnector;
|
||||||
|
//!
|
||||||
|
//! impl Service<Uri> for LocalConnector {
|
||||||
|
//! type Response = TcpStream;
|
||||||
|
//! type Error = std::io::Error;
|
||||||
|
//! // We can't "name" an `async` generated future.
|
||||||
|
//! type Future = Pin<Box<
|
||||||
|
//! dyn Future<Output = Result<Self::Response, Self::Error>> + Send
|
||||||
|
//! >>;
|
||||||
|
//!
|
||||||
|
//! fn poll_ready(&mut self, _: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
//! // This connector is always ready, but others might not be.
|
||||||
|
//! Poll::Ready(Ok(()))
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! fn call(&mut self, _: Uri) -> Self::Future {
|
||||||
|
//! Box::pin(TcpStream::connect(SocketAddr::from(([127, 0, 0, 1], 1337))))
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! It's worth noting that for `TcpStream`s, the [`HttpConnector`][] is a
|
||||||
|
//! better starting place to extend from.
|
||||||
|
//!
|
||||||
|
//! Using either of the above connector examples, it can be used with the
|
||||||
|
//! `Client` like this:
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(feature = "runtime")]
|
||||||
|
//! # fn rt () {
|
||||||
|
//! # let connector = hyper::client::HttpConnector::new();
|
||||||
|
//! // let connector = ...
|
||||||
|
//!
|
||||||
|
//! let client = hyper::Client::builder()
|
||||||
|
//! .build::<_, hyper::Body>(connector);
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//! [`HttpConnector`]: HttpConnector
|
||||||
|
//! [`Service`]: crate::service::Service
|
||||||
|
//! [`Uri`]: ::http::Uri
|
||||||
|
//! [`AsyncRead`]: tokio::io::AsyncRead
|
||||||
|
//! [`AsyncWrite`]: tokio::io::AsyncWrite
|
||||||
|
//! [`Connection`]: Connection
|
||||||
|
use std::fmt;
|
||||||
|
use ::http::Extensions;
|
||||||
|
cfg_feature! {
|
||||||
|
#![feature = "tcp"] pub use self::http:: { HttpConnector, HttpInfo }; pub mod dns;
|
||||||
|
mod http;
|
||||||
|
}
|
||||||
|
cfg_feature! {
|
||||||
|
#![any(feature = "http1", feature = "http2")] pub use self::sealed::Connect;
|
||||||
|
}
|
||||||
|
/// Describes a type returned by a connector.
|
||||||
|
pub trait Connection {
|
||||||
|
/// Return metadata describing the connection.
|
||||||
|
fn connected(&self) -> Connected;
|
||||||
|
}
|
||||||
|
/// Extra information about the connected transport.
|
||||||
|
///
|
||||||
|
/// This can be used to inform recipients about things like if ALPN
|
||||||
|
/// was used, or if connected to an HTTP proxy.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Connected {
|
||||||
|
pub(super) alpn: Alpn,
|
||||||
|
pub(super) is_proxied: bool,
|
||||||
|
pub(super) extra: Option<Extra>,
|
||||||
|
}
|
||||||
|
pub(super) struct Extra(Box<dyn ExtraInner>);
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
||||||
|
pub(super) enum Alpn {
|
||||||
|
H2,
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
impl Connected {
|
||||||
|
/// Create new `Connected` type with empty metadata.
|
||||||
|
pub(crate) fn new() -> Connected {
|
||||||
|
Connected {
|
||||||
|
alpn: Alpn::None,
|
||||||
|
is_proxied: false,
|
||||||
|
extra: None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Set whether the connected transport is to an HTTP proxy.
|
||||||
|
///
|
||||||
|
/// This setting will affect if HTTP/1 requests written on the transport
|
||||||
|
/// will have the request-target in absolute-form or origin-form:
|
||||||
|
///
|
||||||
|
/// - When `proxy(false)`:
|
||||||
|
///
|
||||||
|
/// ```http
|
||||||
|
/// GET /guide HTTP/1.1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// - When `proxy(true)`:
|
||||||
|
///
|
||||||
|
/// ```http
|
||||||
|
/// GET http://hyper.rs/guide HTTP/1.1
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
pub(crate) fn proxy(mut self, is_proxied: bool) -> Connected {
|
||||||
|
self.is_proxied = is_proxied;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Determines if the connected transport is to an HTTP proxy.
|
||||||
|
pub(crate) fn is_proxied(&self) -> bool {
|
||||||
|
self.is_proxied
|
||||||
|
}
|
||||||
|
/// Set extra connection information to be set in the extensions of every `Response`.
|
||||||
|
pub(crate) fn extra<T: Clone + Send + Sync + 'static>(
|
||||||
|
mut self,
|
||||||
|
extra: T,
|
||||||
|
) -> Connected {
|
||||||
|
if let Some(prev) = self.extra {
|
||||||
|
self.extra = Some(Extra(Box::new(ExtraChain(prev.0, extra))));
|
||||||
|
} else {
|
||||||
|
self.extra = Some(Extra(Box::new(ExtraEnvelope(extra))));
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Copies the extra connection information into an `Extensions` map.
|
||||||
|
pub(crate) fn get_extras(&self, extensions: &mut Extensions) {
|
||||||
|
if let Some(extra) = &self.extra {
|
||||||
|
extra.set(extensions);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Set that the connected transport negotiated HTTP/2 as its next protocol.
|
||||||
|
pub(crate) fn negotiated_h2(mut self) -> Connected {
|
||||||
|
self.alpn = Alpn::H2;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Determines if the connected transport negotiated HTTP/2 as its next protocol.
|
||||||
|
pub(crate) fn is_negotiated_h2(&self) -> bool {
|
||||||
|
self.alpn == Alpn::H2
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(super) fn clone(&self) -> Connected {
|
||||||
|
Connected {
|
||||||
|
alpn: self.alpn.clone(),
|
||||||
|
is_proxied: self.is_proxied,
|
||||||
|
extra: self.extra.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Extra {
|
||||||
|
pub(super) fn set(&self, res: &mut Extensions) {
|
||||||
|
self.0.set(res);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Clone for Extra {
|
||||||
|
fn clone(&self) -> Extra {
|
||||||
|
Extra(self.0.clone_box())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for Extra {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Extra").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trait ExtraInner: Send + Sync {
|
||||||
|
fn clone_box(&self) -> Box<dyn ExtraInner>;
|
||||||
|
fn set(&self, res: &mut Extensions);
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
struct ExtraEnvelope<T>(T);
|
||||||
|
impl<T> ExtraInner for ExtraEnvelope<T>
|
||||||
|
where
|
||||||
|
T: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn ExtraInner> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn set(&self, res: &mut Extensions) {
|
||||||
|
res.insert(self.0.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
struct ExtraChain<T>(Box<dyn ExtraInner>, T);
|
||||||
|
impl<T: Clone> Clone for ExtraChain<T> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
ExtraChain(self.0.clone_box(), self.1.clone())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> ExtraInner for ExtraChain<T>
|
||||||
|
where
|
||||||
|
T: Clone + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn clone_box(&self) -> Box<dyn ExtraInner> {
|
||||||
|
Box::new(self.clone())
|
||||||
|
}
|
||||||
|
fn set(&self, res: &mut Extensions) {
|
||||||
|
self.0.set(res);
|
||||||
|
res.insert(self.1.clone());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pub(super) mod sealed {
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use ::http::Uri;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use super::Connection;
|
||||||
|
use crate::common::{Future, Unpin};
|
||||||
|
/// Connect to a destination, returning an IO transport.
|
||||||
|
///
|
||||||
|
/// A connector receives a [`Uri`](::http::Uri) and returns a `Future` of the
|
||||||
|
/// ready connection.
|
||||||
|
///
|
||||||
|
/// # Trait Alias
|
||||||
|
///
|
||||||
|
/// This is really just an *alias* for the `tower::Service` trait, with
|
||||||
|
/// additional bounds set for convenience *inside* hyper. You don't actually
|
||||||
|
/// implement this trait, but `tower::Service<Uri>` instead.
|
||||||
|
pub trait Connect: Sealed + Sized {
|
||||||
|
#[doc(hidden)]
|
||||||
|
type _Svc: ConnectSvc;
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn connect(
|
||||||
|
self,
|
||||||
|
internal_only: Internal,
|
||||||
|
dst: Uri,
|
||||||
|
) -> <Self::_Svc as ConnectSvc>::Future;
|
||||||
|
}
|
||||||
|
pub trait ConnectSvc {
|
||||||
|
type Connection: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static;
|
||||||
|
type Error: Into<Box<dyn StdError + Send + Sync>>;
|
||||||
|
type Future: Future<Output = Result<Self::Connection, Self::Error>>
|
||||||
|
+ Unpin
|
||||||
|
+ Send
|
||||||
|
+ 'static;
|
||||||
|
fn connect(self, internal_only: Internal, dst: Uri) -> Self::Future;
|
||||||
|
}
|
||||||
|
impl<S, T> Connect for S
|
||||||
|
where
|
||||||
|
S: tower_service::Service<Uri, Response = T> + Send + 'static,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
S::Future: Unpin + Send,
|
||||||
|
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
|
type _Svc = S;
|
||||||
|
fn connect(self, _: Internal, dst: Uri) -> crate::service::Oneshot<S, Uri> {
|
||||||
|
crate::service::oneshot(self, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<S, T> ConnectSvc for S
|
||||||
|
where
|
||||||
|
S: tower_service::Service<Uri, Response = T> + Send + 'static,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
S::Future: Unpin + Send,
|
||||||
|
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
||||||
|
{
|
||||||
|
type Connection = T;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = crate::service::Oneshot<S, Uri>;
|
||||||
|
fn connect(self, _: Internal, dst: Uri) -> Self::Future {
|
||||||
|
crate::service::oneshot(self, dst)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<S, T> Sealed for S
|
||||||
|
where
|
||||||
|
S: tower_service::Service<Uri, Response = T> + Send,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
S::Future: Unpin + Send,
|
||||||
|
T: AsyncRead + AsyncWrite + Connection + Unpin + Send + 'static,
|
||||||
|
{}
|
||||||
|
pub trait Sealed {}
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Internal;
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::Connected;
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct Ex1(usize);
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct Ex2(&'static str);
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
struct Ex3(&'static str);
|
||||||
|
#[test]
|
||||||
|
fn test_connected_extra() {
|
||||||
|
let c1 = Connected::new().extra(Ex1(41));
|
||||||
|
let mut ex = ::http::Extensions::new();
|
||||||
|
assert_eq!(ex.get::< Ex1 > (), None);
|
||||||
|
c1.extra.as_ref().expect("c1 extra").set(&mut ex);
|
||||||
|
assert_eq!(ex.get::< Ex1 > (), Some(& Ex1(41)));
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn test_connected_extra_chain() {
|
||||||
|
let c1 = Connected::new()
|
||||||
|
.extra(Ex1(45))
|
||||||
|
.extra(Ex2("zoom"))
|
||||||
|
.extra(Ex3("pew pew"));
|
||||||
|
let mut ex1 = ::http::Extensions::new();
|
||||||
|
assert_eq!(ex1.get::< Ex1 > (), None);
|
||||||
|
assert_eq!(ex1.get::< Ex2 > (), None);
|
||||||
|
assert_eq!(ex1.get::< Ex3 > (), None);
|
||||||
|
c1.extra.as_ref().expect("c1 extra").set(&mut ex1);
|
||||||
|
assert_eq!(ex1.get::< Ex1 > (), Some(& Ex1(45)));
|
||||||
|
assert_eq!(ex1.get::< Ex2 > (), Some(& Ex2("zoom")));
|
||||||
|
assert_eq!(ex1.get::< Ex3 > (), Some(& Ex3("pew pew")));
|
||||||
|
let c2 = Connected::new().extra(Ex1(33)).extra(Ex2("hiccup")).extra(Ex1(99));
|
||||||
|
let mut ex2 = ::http::Extensions::new();
|
||||||
|
c2.extra.as_ref().expect("c2 extra").set(&mut ex2);
|
||||||
|
assert_eq!(ex2.get::< Ex1 > (), Some(& Ex1(99)));
|
||||||
|
assert_eq!(ex2.get::< Ex2 > (), Some(& Ex2("hiccup")));
|
||||||
|
}
|
||||||
|
}
|
||||||
436
hyper/src/client/dispatch.rs
Normal file
436
hyper/src/client/dispatch.rs
Normal file
|
|
@ -0,0 +1,436 @@
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
use std::future::Future;
|
||||||
|
|
||||||
|
use futures_util::FutureExt;
|
||||||
|
use tokio::sync::{mpsc, oneshot};
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
use crate::common::Pin;
|
||||||
|
use crate::common::{task, Poll};
|
||||||
|
|
||||||
|
pub(crate) type RetryPromise<T, U> = oneshot::Receiver<Result<U, (crate::Error, Option<T>)>>;
|
||||||
|
pub(crate) type Promise<T> = oneshot::Receiver<Result<T, crate::Error>>;
|
||||||
|
|
||||||
|
pub(crate) fn channel<T, U>() -> (Sender<T, U>, Receiver<T, U>) {
|
||||||
|
let (tx, rx) = mpsc::unbounded_channel();
|
||||||
|
let (giver, taker) = want::new();
|
||||||
|
let tx = Sender {
|
||||||
|
buffered_once: false,
|
||||||
|
giver,
|
||||||
|
inner: tx,
|
||||||
|
};
|
||||||
|
let rx = Receiver { inner: rx, taker };
|
||||||
|
(tx, rx)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A bounded sender of requests and callbacks for when responses are ready.
|
||||||
|
///
|
||||||
|
/// While the inner sender is unbounded, the Giver is used to determine
|
||||||
|
/// if the Receiver is ready for another request.
|
||||||
|
pub(crate) struct Sender<T, U> {
|
||||||
|
/// One message is always allowed, even if the Receiver hasn't asked
|
||||||
|
/// for it yet. This boolean keeps track of whether we've sent one
|
||||||
|
/// without notice.
|
||||||
|
buffered_once: bool,
|
||||||
|
/// The Giver helps watch that the the Receiver side has been polled
|
||||||
|
/// when the queue is empty. This helps us know when a request and
|
||||||
|
/// response have been fully processed, and a connection is ready
|
||||||
|
/// for more.
|
||||||
|
giver: want::Giver,
|
||||||
|
/// Actually bounded by the Giver, plus `buffered_once`.
|
||||||
|
inner: mpsc::UnboundedSender<Envelope<T, U>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An unbounded version.
|
||||||
|
///
|
||||||
|
/// Cannot poll the Giver, but can still use it to determine if the Receiver
|
||||||
|
/// has been dropped. However, this version can be cloned.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) struct UnboundedSender<T, U> {
|
||||||
|
/// Only used for `is_closed`, since mpsc::UnboundedSender cannot be checked.
|
||||||
|
giver: want::SharedGiver,
|
||||||
|
inner: mpsc::UnboundedSender<Envelope<T, U>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Sender<T, U> {
|
||||||
|
pub(crate) fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
self.giver
|
||||||
|
.poll_want(cx)
|
||||||
|
.map_err(|_| crate::Error::new_closed())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_ready(&self) -> bool {
|
||||||
|
self.giver.is_wanting()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_closed(&self) -> bool {
|
||||||
|
self.giver.is_canceled()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn can_send(&mut self) -> bool {
|
||||||
|
if self.giver.give() || !self.buffered_once {
|
||||||
|
// If the receiver is ready *now*, then of course we can send.
|
||||||
|
//
|
||||||
|
// If the receiver isn't ready yet, but we don't have anything
|
||||||
|
// in the channel yet, then allow one message.
|
||||||
|
self.buffered_once = true;
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> {
|
||||||
|
if !self.can_send() {
|
||||||
|
return Err(val);
|
||||||
|
}
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.inner
|
||||||
|
.send(Envelope(Some((val, Callback::Retry(Some(tx))))))
|
||||||
|
.map(move |_| rx)
|
||||||
|
.map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send(&mut self, val: T) -> Result<Promise<U>, T> {
|
||||||
|
if !self.can_send() {
|
||||||
|
return Err(val);
|
||||||
|
}
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.inner
|
||||||
|
.send(Envelope(Some((val, Callback::NoRetry(Some(tx))))))
|
||||||
|
.map(move |_| rx)
|
||||||
|
.map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) fn unbound(self) -> UnboundedSender<T, U> {
|
||||||
|
UnboundedSender {
|
||||||
|
giver: self.giver.shared(),
|
||||||
|
inner: self.inner,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
impl<T, U> UnboundedSender<T, U> {
|
||||||
|
pub(crate) fn is_ready(&self) -> bool {
|
||||||
|
!self.giver.is_canceled()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_closed(&self) -> bool {
|
||||||
|
self.giver.is_canceled()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn try_send(&mut self, val: T) -> Result<RetryPromise<T, U>, T> {
|
||||||
|
let (tx, rx) = oneshot::channel();
|
||||||
|
self.inner
|
||||||
|
.send(Envelope(Some((val, Callback::Retry(Some(tx))))))
|
||||||
|
.map(move |_| rx)
|
||||||
|
.map_err(|mut e| (e.0).0.take().expect("envelope not dropped").0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
impl<T, U> Clone for UnboundedSender<T, U> {
|
||||||
|
fn clone(&self) -> Self {
|
||||||
|
UnboundedSender {
|
||||||
|
giver: self.giver.clone(),
|
||||||
|
inner: self.inner.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Receiver<T, U> {
|
||||||
|
inner: mpsc::UnboundedReceiver<Envelope<T, U>>,
|
||||||
|
taker: want::Taker,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Receiver<T, U> {
|
||||||
|
pub(crate) fn poll_recv(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<(T, Callback<T, U>)>> {
|
||||||
|
match self.inner.poll_recv(cx) {
|
||||||
|
Poll::Ready(item) => {
|
||||||
|
Poll::Ready(item.map(|mut env| env.0.take().expect("envelope not dropped")))
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
self.taker.want();
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn close(&mut self) {
|
||||||
|
self.taker.cancel();
|
||||||
|
self.inner.close();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn try_recv(&mut self) -> Option<(T, Callback<T, U>)> {
|
||||||
|
match self.inner.recv().now_or_never() {
|
||||||
|
Some(Some(mut env)) => env.0.take(),
|
||||||
|
_ => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Drop for Receiver<T, U> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// Notify the giver about the closure first, before dropping
|
||||||
|
// the mpsc::Receiver.
|
||||||
|
self.taker.cancel();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Envelope<T, U>(Option<(T, Callback<T, U>)>);
|
||||||
|
|
||||||
|
impl<T, U> Drop for Envelope<T, U> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if let Some((val, cb)) = self.0.take() {
|
||||||
|
cb.send(Err((
|
||||||
|
crate::Error::new_canceled().with("connection closed"),
|
||||||
|
Some(val),
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) enum Callback<T, U> {
|
||||||
|
Retry(Option<oneshot::Sender<Result<U, (crate::Error, Option<T>)>>>),
|
||||||
|
NoRetry(Option<oneshot::Sender<Result<U, crate::Error>>>),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Drop for Callback<T, U> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
// FIXME(nox): What errors do we want here?
|
||||||
|
let error = crate::Error::new_user_dispatch_gone().with(if std::thread::panicking() {
|
||||||
|
"user code panicked"
|
||||||
|
} else {
|
||||||
|
"runtime dropped the dispatch task"
|
||||||
|
});
|
||||||
|
|
||||||
|
match self {
|
||||||
|
Callback::Retry(tx) => {
|
||||||
|
if let Some(tx) = tx.take() {
|
||||||
|
let _ = tx.send(Err((error, None)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Callback::NoRetry(tx) => {
|
||||||
|
if let Some(tx) = tx.take() {
|
||||||
|
let _ = tx.send(Err(error));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, U> Callback<T, U> {
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) fn is_canceled(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Callback::Retry(Some(ref tx)) => tx.is_closed(),
|
||||||
|
Callback::NoRetry(Some(ref tx)) => tx.is_closed(),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn poll_canceled(&mut self, cx: &mut task::Context<'_>) -> Poll<()> {
|
||||||
|
match *self {
|
||||||
|
Callback::Retry(Some(ref mut tx)) => tx.poll_closed(cx),
|
||||||
|
Callback::NoRetry(Some(ref mut tx)) => tx.poll_closed(cx),
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn send(mut self, val: Result<U, (crate::Error, Option<T>)>) {
|
||||||
|
match self {
|
||||||
|
Callback::Retry(ref mut tx) => {
|
||||||
|
let _ = tx.take().unwrap().send(val);
|
||||||
|
}
|
||||||
|
Callback::NoRetry(ref mut tx) => {
|
||||||
|
let _ = tx.take().unwrap().send(val.map_err(|e| e.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) async fn send_when(
|
||||||
|
self,
|
||||||
|
mut when: impl Future<Output = Result<U, (crate::Error, Option<T>)>> + Unpin,
|
||||||
|
) {
|
||||||
|
use futures_util::future;
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
let mut cb = Some(self);
|
||||||
|
|
||||||
|
// "select" on this callback being canceled, and the future completing
|
||||||
|
future::poll_fn(move |cx| {
|
||||||
|
match Pin::new(&mut when).poll(cx) {
|
||||||
|
Poll::Ready(Ok(res)) => {
|
||||||
|
cb.take().expect("polled after complete").send(Ok(res));
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
// check if the callback is canceled
|
||||||
|
ready!(cb.as_mut().unwrap().poll_canceled(cx));
|
||||||
|
trace!("send_when canceled");
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(err)) => {
|
||||||
|
cb.take().expect("polled after complete").send(Err(err));
|
||||||
|
Poll::Ready(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
extern crate test;
|
||||||
|
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
|
use super::{channel, Callback, Receiver};
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct Custom(i32);
|
||||||
|
|
||||||
|
impl<T, U> Future for Receiver<T, U> {
|
||||||
|
type Output = Option<(T, Callback<T, U>)>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.poll_recv(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Helper to check if the future is ready after polling once.
|
||||||
|
struct PollOnce<'a, F>(&'a mut F);
|
||||||
|
|
||||||
|
impl<F, T> Future for PollOnce<'_, F>
|
||||||
|
where
|
||||||
|
F: Future<Output = T> + Unpin,
|
||||||
|
{
|
||||||
|
type Output = Option<()>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
match Pin::new(&mut self.0).poll(cx) {
|
||||||
|
Poll::Ready(_) => Poll::Ready(Some(())),
|
||||||
|
Poll::Pending => Poll::Ready(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn drop_receiver_sends_cancel_errors() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let (mut tx, mut rx) = channel::<Custom, ()>();
|
||||||
|
|
||||||
|
// must poll once for try_send to succeed
|
||||||
|
assert!(PollOnce(&mut rx).await.is_none(), "rx empty");
|
||||||
|
|
||||||
|
let promise = tx.try_send(Custom(43)).unwrap();
|
||||||
|
drop(rx);
|
||||||
|
|
||||||
|
let fulfilled = promise.await;
|
||||||
|
let err = fulfilled
|
||||||
|
.expect("fulfilled")
|
||||||
|
.expect_err("promise should error");
|
||||||
|
match (err.0.kind(), err.1) {
|
||||||
|
(&crate::error::Kind::Canceled, Some(_)) => (),
|
||||||
|
e => panic!("expected Error::Cancel(_), found {:?}", e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn sender_checks_for_want_on_send() {
|
||||||
|
let (mut tx, mut rx) = channel::<Custom, ()>();
|
||||||
|
|
||||||
|
// one is allowed to buffer, second is rejected
|
||||||
|
let _ = tx.try_send(Custom(1)).expect("1 buffered");
|
||||||
|
tx.try_send(Custom(2)).expect_err("2 not ready");
|
||||||
|
|
||||||
|
assert!(PollOnce(&mut rx).await.is_some(), "rx once");
|
||||||
|
|
||||||
|
// Even though 1 has been popped, only 1 could be buffered for the
|
||||||
|
// lifetime of the channel.
|
||||||
|
tx.try_send(Custom(2)).expect_err("2 still not ready");
|
||||||
|
|
||||||
|
assert!(PollOnce(&mut rx).await.is_none(), "rx empty");
|
||||||
|
|
||||||
|
let _ = tx.try_send(Custom(2)).expect("2 ready");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[test]
|
||||||
|
fn unbounded_sender_doesnt_bound_on_want() {
|
||||||
|
let (tx, rx) = channel::<Custom, ()>();
|
||||||
|
let mut tx = tx.unbound();
|
||||||
|
|
||||||
|
let _ = tx.try_send(Custom(1)).unwrap();
|
||||||
|
let _ = tx.try_send(Custom(2)).unwrap();
|
||||||
|
let _ = tx.try_send(Custom(3)).unwrap();
|
||||||
|
|
||||||
|
drop(rx);
|
||||||
|
|
||||||
|
let _ = tx.try_send(Custom(4)).unwrap_err();
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn giver_queue_throughput(b: &mut test::Bencher) {
|
||||||
|
use crate::{Body, Request, Response};
|
||||||
|
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let (mut tx, mut rx) = channel::<Request<Body>, Response<Body>>();
|
||||||
|
|
||||||
|
b.iter(move || {
|
||||||
|
let _ = tx.send(Request::default()).unwrap();
|
||||||
|
rt.block_on(async {
|
||||||
|
loop {
|
||||||
|
let poll_once = PollOnce(&mut rx);
|
||||||
|
let opt = poll_once.await;
|
||||||
|
if opt.is_none() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn giver_queue_not_ready(b: &mut test::Bencher) {
|
||||||
|
let rt = tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.unwrap();
|
||||||
|
let (_tx, mut rx) = channel::<i32, ()>();
|
||||||
|
b.iter(move || {
|
||||||
|
rt.block_on(async {
|
||||||
|
let poll_once = PollOnce(&mut rx);
|
||||||
|
assert!(poll_once.await.is_none());
|
||||||
|
});
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn giver_queue_cancel(b: &mut test::Bencher) {
|
||||||
|
let (_tx, mut rx) = channel::<i32, ()>();
|
||||||
|
|
||||||
|
b.iter(move || {
|
||||||
|
rx.taker.cancel();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
59
hyper/src/client/mod.rs
Normal file
59
hyper/src/client/mod.rs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
//! HTTP Client
|
||||||
|
//!
|
||||||
|
//! There are two levels of APIs provided for construct HTTP clients:
|
||||||
|
//!
|
||||||
|
//! - The higher-level [`Client`](Client) type.
|
||||||
|
//! - The lower-level [`conn`](conn) module.
|
||||||
|
//!
|
||||||
|
//! # Client
|
||||||
|
//!
|
||||||
|
//! The [`Client`](Client) is the main way to send HTTP requests to a server.
|
||||||
|
//! The default `Client` provides these things on top of the lower-level API:
|
||||||
|
//!
|
||||||
|
//! - A default **connector**, able to resolve hostnames and connect to
|
||||||
|
//! destinations over plain-text TCP.
|
||||||
|
//! - A **pool** of existing connections, allowing better performance when
|
||||||
|
//! making multiple requests to the same hostname.
|
||||||
|
//! - Automatic setting of the `Host` header, based on the request `Uri`.
|
||||||
|
//! - Automatic request **retries** when a pooled connection is closed by the
|
||||||
|
//! server before any bytes have been written.
|
||||||
|
//!
|
||||||
|
//! Many of these features can configured, by making use of
|
||||||
|
//! [`Client::builder`](Client::builder).
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//!
|
||||||
|
//! For a small example program simply fetching a URL, take a look at the
|
||||||
|
//! [full client example](https://github.com/hyperium/hyper/blob/master/examples/client.rs).
|
||||||
|
//!
|
||||||
|
//! ```
|
||||||
|
//! # #[cfg(all(feature = "tcp", feature = "client", any(feature = "http1", feature = "http2")))]
|
||||||
|
//! # async fn fetch_httpbin() -> hyper::Result<()> {
|
||||||
|
//! use hyper::{body::HttpBody as _, Client, Uri};
|
||||||
|
//!
|
||||||
|
//! let client = Client::new();
|
||||||
|
//!
|
||||||
|
//! // Make a GET /ip to 'http://httpbin.org'
|
||||||
|
//! let res = client.get(Uri::from_static("http://httpbin.org/ip")).await?;
|
||||||
|
//!
|
||||||
|
//! // And then, if the request gets a response...
|
||||||
|
//! println!("status: {}", res.status());
|
||||||
|
//!
|
||||||
|
//! // Concatenate the body stream into a single buffer...
|
||||||
|
//! let buf = hyper::body::to_bytes(res).await?;
|
||||||
|
//!
|
||||||
|
//! println!("body: {:?}", buf);
|
||||||
|
//! # Ok(())
|
||||||
|
//! # }
|
||||||
|
//! # fn main () {}
|
||||||
|
//! ```
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
pub(crate) use self::connect::HttpConnector;
|
||||||
|
pub(crate) mod connect;
|
||||||
|
#[cfg(all(test, feature = "runtime"))]
|
||||||
|
mod tests;
|
||||||
|
cfg_feature! {
|
||||||
|
#![any(feature = "http1", feature = "http2")] pub use self::client:: { Builder,
|
||||||
|
Client, ResponseFuture }; mod client; pub mod conn; pub (super) mod dispatch; mod
|
||||||
|
pool; pub mod service;
|
||||||
|
}
|
||||||
1044
hyper/src/client/pool.rs
Normal file
1044
hyper/src/client/pool.rs
Normal file
File diff suppressed because it is too large
Load diff
87
hyper/src/client/service.rs
Normal file
87
hyper/src/client/service.rs
Normal file
|
|
@ -0,0 +1,87 @@
|
||||||
|
//! Utilities used to interact with the Tower ecosystem.
|
||||||
|
//!
|
||||||
|
//! This module provides `Connect` which hook-ins into the Tower ecosystem.
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
use tracing::debug;
|
||||||
|
use super::conn::{Builder, SendRequest};
|
||||||
|
use crate::{
|
||||||
|
body::HttpBody, common::{task, Pin, Poll},
|
||||||
|
service::{MakeConnection, Service},
|
||||||
|
};
|
||||||
|
/// Creates a connection via `SendRequest`.
|
||||||
|
///
|
||||||
|
/// This accepts a `hyper::client::conn::Builder` and provides
|
||||||
|
/// a `MakeService` implementation to create connections from some
|
||||||
|
/// target `T`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct Connect<C, B, T> {
|
||||||
|
inner: C,
|
||||||
|
builder: Builder,
|
||||||
|
_pd: PhantomData<fn(T, B)>,
|
||||||
|
}
|
||||||
|
impl<C, B, T> Connect<C, B, T> {
|
||||||
|
/// Create a new `Connect` with some inner connector `C` and a connection
|
||||||
|
/// builder.
|
||||||
|
pub(crate) fn new(inner: C, builder: Builder) -> Self {
|
||||||
|
Self {
|
||||||
|
inner,
|
||||||
|
builder,
|
||||||
|
_pd: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<C, B, T> Service<T> for Connect<C, B, T>
|
||||||
|
where
|
||||||
|
C: MakeConnection<T>,
|
||||||
|
C::Connection: Unpin + Send + 'static,
|
||||||
|
C::Future: Send + 'static,
|
||||||
|
C::Error: Into<Box<dyn StdError + Send + Sync>> + Send,
|
||||||
|
B: HttpBody + Unpin + Send + 'static,
|
||||||
|
B::Data: Send + Unpin,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Response = SendRequest<B>;
|
||||||
|
type Error = crate::Error;
|
||||||
|
type Future = Pin<
|
||||||
|
Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send + 'static>,
|
||||||
|
>;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
self.inner
|
||||||
|
.poll_ready(cx)
|
||||||
|
.map_err(|e| crate::Error::new(crate::error::Kind::Connect).with(e.into()))
|
||||||
|
}
|
||||||
|
fn call(&mut self, req: T) -> Self::Future {
|
||||||
|
let builder = self.builder.clone();
|
||||||
|
let io = self.inner.make_connection(req);
|
||||||
|
let fut = async move {
|
||||||
|
match io.await {
|
||||||
|
Ok(io) => {
|
||||||
|
match builder.handshake(io).await {
|
||||||
|
Ok((sr, conn)) => {
|
||||||
|
builder
|
||||||
|
.exec
|
||||||
|
.execute(async move {
|
||||||
|
if let Err(e) = conn.await {
|
||||||
|
debug!("connection error: {:?}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
Ok(sr)
|
||||||
|
}
|
||||||
|
Err(e) => Err(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
let err = crate::Error::new(crate::error::Kind::Connect)
|
||||||
|
.with(e.into());
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Box::pin(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
286
hyper/src/client/tests.rs
Normal file
286
hyper/src/client/tests.rs
Normal file
|
|
@ -0,0 +1,286 @@
|
||||||
|
use std::io;
|
||||||
|
|
||||||
|
use futures_util::future;
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use super::Client;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn client_connect_uri_argument() {
|
||||||
|
let connector = tower::service_fn(|dst: http::Uri| {
|
||||||
|
assert_eq!(dst.scheme(), Some(&http::uri::Scheme::HTTP));
|
||||||
|
assert_eq!(dst.host(), Some("example.local"));
|
||||||
|
assert_eq!(dst.port(), None);
|
||||||
|
assert_eq!(dst.path(), "/", "path should be removed");
|
||||||
|
|
||||||
|
future::err::<TcpStream, _>(io::Error::new(io::ErrorKind::Other, "expect me"))
|
||||||
|
});
|
||||||
|
|
||||||
|
let client = Client::builder().build::<_, crate::Body>(connector);
|
||||||
|
let _ = client
|
||||||
|
.get("http://example.local/and/a/path".parse().unwrap())
|
||||||
|
.await
|
||||||
|
.expect_err("response should fail");
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
// FIXME: re-implement tests with `async/await`
|
||||||
|
#[test]
|
||||||
|
fn retryable_request() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let mut rt = Runtime::new().expect("new rt");
|
||||||
|
let mut connector = MockConnector::new();
|
||||||
|
|
||||||
|
let sock1 = connector.mock("http://mock.local");
|
||||||
|
let sock2 = connector.mock("http://mock.local");
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.build::<_, crate::Body>(connector);
|
||||||
|
|
||||||
|
client.pool.no_timer();
|
||||||
|
|
||||||
|
{
|
||||||
|
|
||||||
|
let req = Request::builder()
|
||||||
|
.uri("http://mock.local/a")
|
||||||
|
.body(Default::default())
|
||||||
|
.unwrap();
|
||||||
|
let res1 = client.request(req);
|
||||||
|
let srv1 = poll_fn(|| {
|
||||||
|
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||||
|
rt.block_on(res1.join(srv1)).expect("res1");
|
||||||
|
}
|
||||||
|
drop(sock1);
|
||||||
|
|
||||||
|
let req = Request::builder()
|
||||||
|
.uri("http://mock.local/b")
|
||||||
|
.body(Default::default())
|
||||||
|
.unwrap();
|
||||||
|
let res2 = client.request(req)
|
||||||
|
.map(|res| {
|
||||||
|
assert_eq!(res.status().as_u16(), 222);
|
||||||
|
});
|
||||||
|
let srv2 = poll_fn(|| {
|
||||||
|
try_ready!(sock2.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock2.write(b"HTTP/1.1 222 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv2 poll_fn error: {}", e));
|
||||||
|
|
||||||
|
rt.block_on(res2.join(srv2)).expect("res2");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn conn_reset_after_write() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let mut rt = Runtime::new().expect("new rt");
|
||||||
|
let mut connector = MockConnector::new();
|
||||||
|
|
||||||
|
let sock1 = connector.mock("http://mock.local");
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.build::<_, crate::Body>(connector);
|
||||||
|
|
||||||
|
client.pool.no_timer();
|
||||||
|
|
||||||
|
{
|
||||||
|
let req = Request::builder()
|
||||||
|
.uri("http://mock.local/a")
|
||||||
|
.body(Default::default())
|
||||||
|
.unwrap();
|
||||||
|
let res1 = client.request(req);
|
||||||
|
let srv1 = poll_fn(|| {
|
||||||
|
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||||
|
rt.block_on(res1.join(srv1)).expect("res1");
|
||||||
|
}
|
||||||
|
|
||||||
|
let req = Request::builder()
|
||||||
|
.uri("http://mock.local/a")
|
||||||
|
.body(Default::default())
|
||||||
|
.unwrap();
|
||||||
|
let res2 = client.request(req);
|
||||||
|
let mut sock1 = Some(sock1);
|
||||||
|
let srv2 = poll_fn(|| {
|
||||||
|
// We purposefully keep the socket open until the client
|
||||||
|
// has written the second request, and THEN disconnect.
|
||||||
|
//
|
||||||
|
// Not because we expect servers to be jerks, but to trigger
|
||||||
|
// state where we write on an assumedly good connection, and
|
||||||
|
// only reset the close AFTER we wrote bytes.
|
||||||
|
try_ready!(sock1.as_mut().unwrap().read(&mut [0u8; 512]));
|
||||||
|
sock1.take();
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv2 poll_fn error: {}", e));
|
||||||
|
let err = rt.block_on(res2.join(srv2)).expect_err("res2");
|
||||||
|
assert!(err.is_incomplete_message(), "{:?}", err);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn checkout_win_allows_connect_future_to_be_pooled() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let mut rt = Runtime::new().expect("new rt");
|
||||||
|
let mut connector = MockConnector::new();
|
||||||
|
|
||||||
|
|
||||||
|
let (tx, rx) = oneshot::channel::<()>();
|
||||||
|
let sock1 = connector.mock("http://mock.local");
|
||||||
|
let sock2 = connector.mock_fut("http://mock.local", rx);
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.build::<_, crate::Body>(connector);
|
||||||
|
|
||||||
|
client.pool.no_timer();
|
||||||
|
|
||||||
|
let uri = "http://mock.local/a".parse::<crate::Uri>().expect("uri parse");
|
||||||
|
|
||||||
|
// First request just sets us up to have a connection able to be put
|
||||||
|
// back in the pool. *However*, it doesn't insert immediately. The
|
||||||
|
// body has 1 pending byte, and we will only drain in request 2, once
|
||||||
|
// the connect future has been started.
|
||||||
|
let mut body = {
|
||||||
|
let res1 = client.get(uri.clone())
|
||||||
|
.map(|res| res.into_body().concat2());
|
||||||
|
let srv1 = poll_fn(|| {
|
||||||
|
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||||
|
// Chunked is used so as to force 2 body reads.
|
||||||
|
try_ready!(sock1.write(b"\
|
||||||
|
HTTP/1.1 200 OK\r\n\
|
||||||
|
transfer-encoding: chunked\r\n\
|
||||||
|
\r\n\
|
||||||
|
1\r\nx\r\n\
|
||||||
|
0\r\n\r\n\
|
||||||
|
"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||||
|
|
||||||
|
rt.block_on(res1.join(srv1)).expect("res1").0
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
// The second request triggers the only mocked connect future, but then
|
||||||
|
// the drained body allows the first socket to go back to the pool,
|
||||||
|
// "winning" the checkout race.
|
||||||
|
{
|
||||||
|
let res2 = client.get(uri.clone());
|
||||||
|
let drain = poll_fn(move || {
|
||||||
|
body.poll()
|
||||||
|
});
|
||||||
|
let srv2 = poll_fn(|| {
|
||||||
|
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nConnection: close\r\n\r\nx"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv2 poll_fn error: {}", e));
|
||||||
|
|
||||||
|
rt.block_on(res2.join(drain).join(srv2)).expect("res2");
|
||||||
|
}
|
||||||
|
|
||||||
|
// "Release" the mocked connect future, and let the runtime spin once so
|
||||||
|
// it's all setup...
|
||||||
|
{
|
||||||
|
let mut tx = Some(tx);
|
||||||
|
let client = &client;
|
||||||
|
let key = client.pool.h1_key("http://mock.local");
|
||||||
|
let mut tick_cnt = 0;
|
||||||
|
let fut = poll_fn(move || {
|
||||||
|
tx.take();
|
||||||
|
|
||||||
|
if client.pool.idle_count(&key) == 0 {
|
||||||
|
tick_cnt += 1;
|
||||||
|
assert!(tick_cnt < 10, "ticked too many times waiting for idle");
|
||||||
|
trace!("no idle yet; tick count: {}", tick_cnt);
|
||||||
|
::futures::task::current().notify();
|
||||||
|
Ok(Async::NotReady)
|
||||||
|
} else {
|
||||||
|
Ok::<_, ()>(Async::Ready(()))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
rt.block_on(fut).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Third request just tests out that the "loser" connection was pooled. If
|
||||||
|
// it isn't, this will panic since the MockConnector doesn't have any more
|
||||||
|
// mocks to give out.
|
||||||
|
{
|
||||||
|
let res3 = client.get(uri);
|
||||||
|
let srv3 = poll_fn(|| {
|
||||||
|
try_ready!(sock2.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock2.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv3 poll_fn error: {}", e));
|
||||||
|
|
||||||
|
rt.block_on(res3.join(srv3)).expect("res3");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_http1_get_0b(b: &mut test::Bencher) {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let mut rt = Runtime::new().expect("new rt");
|
||||||
|
let mut connector = MockConnector::new();
|
||||||
|
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.build::<_, crate::Body>(connector.clone());
|
||||||
|
|
||||||
|
client.pool.no_timer();
|
||||||
|
|
||||||
|
let uri = Uri::from_static("http://mock.local/a");
|
||||||
|
|
||||||
|
b.iter(move || {
|
||||||
|
let sock1 = connector.mock("http://mock.local");
|
||||||
|
let res1 = client
|
||||||
|
.get(uri.clone())
|
||||||
|
.and_then(|res| {
|
||||||
|
res.into_body().for_each(|_| Ok(()))
|
||||||
|
});
|
||||||
|
let srv1 = poll_fn(|| {
|
||||||
|
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\n\r\n"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||||
|
rt.block_on(res1.join(srv1)).expect("res1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_http1_get_10b(b: &mut test::Bencher) {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let mut rt = Runtime::new().expect("new rt");
|
||||||
|
let mut connector = MockConnector::new();
|
||||||
|
|
||||||
|
|
||||||
|
let client = Client::builder()
|
||||||
|
.build::<_, crate::Body>(connector.clone());
|
||||||
|
|
||||||
|
client.pool.no_timer();
|
||||||
|
|
||||||
|
let uri = Uri::from_static("http://mock.local/a");
|
||||||
|
|
||||||
|
b.iter(move || {
|
||||||
|
let sock1 = connector.mock("http://mock.local");
|
||||||
|
let res1 = client
|
||||||
|
.get(uri.clone())
|
||||||
|
.and_then(|res| {
|
||||||
|
res.into_body().for_each(|_| Ok(()))
|
||||||
|
});
|
||||||
|
let srv1 = poll_fn(|| {
|
||||||
|
try_ready!(sock1.read(&mut [0u8; 512]));
|
||||||
|
try_ready!(sock1.write(b"HTTP/1.1 200 OK\r\nContent-Length: 10\r\n\r\n0123456789"));
|
||||||
|
Ok(Async::Ready(()))
|
||||||
|
}).map_err(|e: std::io::Error| panic!("srv1 poll_fn error: {}", e));
|
||||||
|
rt.block_on(res1.join(srv1)).expect("res1");
|
||||||
|
});
|
||||||
|
}
|
||||||
|
*/
|
||||||
151
hyper/src/common/buf.rs
Normal file
151
hyper/src/common/buf.rs
Normal file
|
|
@ -0,0 +1,151 @@
|
||||||
|
use std::collections::VecDeque;
|
||||||
|
use std::io::IoSlice;
|
||||||
|
|
||||||
|
use bytes::{Buf, BufMut, Bytes, BytesMut};
|
||||||
|
|
||||||
|
pub(crate) struct BufList<T> {
|
||||||
|
bufs: VecDeque<T>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Buf> BufList<T> {
|
||||||
|
pub(crate) fn new() -> BufList<T> {
|
||||||
|
BufList {
|
||||||
|
bufs: VecDeque::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub(crate) fn push(&mut self, buf: T) {
|
||||||
|
debug_assert!(buf.has_remaining());
|
||||||
|
self.bufs.push_back(buf);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn bufs_cnt(&self) -> usize {
|
||||||
|
self.bufs.len()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: Buf> Buf for BufList<T> {
|
||||||
|
#[inline]
|
||||||
|
fn remaining(&self) -> usize {
|
||||||
|
self.bufs.iter().map(|buf| buf.remaining()).sum()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn chunk(&self) -> &[u8] {
|
||||||
|
self.bufs.front().map(Buf::chunk).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn advance(&mut self, mut cnt: usize) {
|
||||||
|
while cnt > 0 {
|
||||||
|
{
|
||||||
|
let front = &mut self.bufs[0];
|
||||||
|
let rem = front.remaining();
|
||||||
|
if rem > cnt {
|
||||||
|
front.advance(cnt);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
front.advance(rem);
|
||||||
|
cnt -= rem;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.bufs.pop_front();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn chunks_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize {
|
||||||
|
if dst.is_empty() {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
let mut vecs = 0;
|
||||||
|
for buf in &self.bufs {
|
||||||
|
vecs += buf.chunks_vectored(&mut dst[vecs..]);
|
||||||
|
if vecs == dst.len() {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
vecs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn copy_to_bytes(&mut self, len: usize) -> Bytes {
|
||||||
|
// Our inner buffer may have an optimized version of copy_to_bytes, and if the whole
|
||||||
|
// request can be fulfilled by the front buffer, we can take advantage.
|
||||||
|
match self.bufs.front_mut() {
|
||||||
|
Some(front) if front.remaining() == len => {
|
||||||
|
let b = front.copy_to_bytes(len);
|
||||||
|
self.bufs.pop_front();
|
||||||
|
b
|
||||||
|
}
|
||||||
|
Some(front) if front.remaining() > len => front.copy_to_bytes(len),
|
||||||
|
_ => {
|
||||||
|
assert!(len <= self.remaining(), "`len` greater than remaining");
|
||||||
|
let mut bm = BytesMut::with_capacity(len);
|
||||||
|
bm.put(self.take(len));
|
||||||
|
bm.freeze()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::ptr;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn hello_world_buf() -> BufList<Bytes> {
|
||||||
|
BufList {
|
||||||
|
bufs: vec![Bytes::from("Hello"), Bytes::from(" "), Bytes::from("World")].into(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_bytes_shorter() {
|
||||||
|
let mut bufs = hello_world_buf();
|
||||||
|
let old_ptr = bufs.chunk().as_ptr();
|
||||||
|
let start = bufs.copy_to_bytes(4);
|
||||||
|
assert_eq!(start, "Hell");
|
||||||
|
assert!(ptr::eq(old_ptr, start.as_ptr()));
|
||||||
|
assert_eq!(bufs.chunk(), b"o");
|
||||||
|
assert!(ptr::eq(old_ptr.wrapping_add(4), bufs.chunk().as_ptr()));
|
||||||
|
assert_eq!(bufs.remaining(), 7);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_bytes_eq() {
|
||||||
|
let mut bufs = hello_world_buf();
|
||||||
|
let old_ptr = bufs.chunk().as_ptr();
|
||||||
|
let start = bufs.copy_to_bytes(5);
|
||||||
|
assert_eq!(start, "Hello");
|
||||||
|
assert!(ptr::eq(old_ptr, start.as_ptr()));
|
||||||
|
assert_eq!(bufs.chunk(), b" ");
|
||||||
|
assert_eq!(bufs.remaining(), 6);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn to_bytes_longer() {
|
||||||
|
let mut bufs = hello_world_buf();
|
||||||
|
let start = bufs.copy_to_bytes(7);
|
||||||
|
assert_eq!(start, "Hello W");
|
||||||
|
assert_eq!(bufs.remaining(), 4);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn one_long_buf_to_bytes() {
|
||||||
|
let mut buf = BufList::new();
|
||||||
|
buf.push(b"Hello World" as &[_]);
|
||||||
|
assert_eq!(buf.copy_to_bytes(5), "Hello");
|
||||||
|
assert_eq!(buf.chunk(), b" World");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[should_panic(expected = "`len` greater than remaining")]
|
||||||
|
fn buf_to_bytes_too_many() {
|
||||||
|
hello_world_buf().copy_to_bytes(42);
|
||||||
|
}
|
||||||
|
}
|
||||||
124
hyper/src/common/date.rs
Normal file
124
hyper/src/common/date.rs
Normal file
|
|
@ -0,0 +1,124 @@
|
||||||
|
use std::cell::RefCell;
|
||||||
|
use std::fmt::{self, Write};
|
||||||
|
use std::str;
|
||||||
|
use std::time::{Duration, SystemTime};
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
use http::header::HeaderValue;
|
||||||
|
use httpdate::HttpDate;
|
||||||
|
|
||||||
|
// "Sun, 06 Nov 1994 08:49:37 GMT".len()
|
||||||
|
pub(crate) const DATE_VALUE_LENGTH: usize = 29;
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn extend(dst: &mut Vec<u8>) {
|
||||||
|
CACHED.with(|cache| {
|
||||||
|
dst.extend_from_slice(cache.borrow().buffer());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn update() {
|
||||||
|
CACHED.with(|cache| {
|
||||||
|
cache.borrow_mut().check();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) fn update_and_header_value() -> HeaderValue {
|
||||||
|
CACHED.with(|cache| {
|
||||||
|
let mut cache = cache.borrow_mut();
|
||||||
|
cache.check();
|
||||||
|
HeaderValue::from_bytes(cache.buffer()).expect("Date format should be valid HeaderValue")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
struct CachedDate {
|
||||||
|
bytes: [u8; DATE_VALUE_LENGTH],
|
||||||
|
pos: usize,
|
||||||
|
next_update: SystemTime,
|
||||||
|
}
|
||||||
|
|
||||||
|
thread_local!(static CACHED: RefCell<CachedDate> = RefCell::new(CachedDate::new()));
|
||||||
|
|
||||||
|
impl CachedDate {
|
||||||
|
fn new() -> Self {
|
||||||
|
let mut cache = CachedDate {
|
||||||
|
bytes: [0; DATE_VALUE_LENGTH],
|
||||||
|
pos: 0,
|
||||||
|
next_update: SystemTime::now(),
|
||||||
|
};
|
||||||
|
cache.update(cache.next_update);
|
||||||
|
cache
|
||||||
|
}
|
||||||
|
|
||||||
|
fn buffer(&self) -> &[u8] {
|
||||||
|
&self.bytes[..]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn check(&mut self) {
|
||||||
|
let now = SystemTime::now();
|
||||||
|
if now > self.next_update {
|
||||||
|
self.update(now);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self, now: SystemTime) {
|
||||||
|
self.render(now);
|
||||||
|
self.next_update = now + Duration::new(1, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn render(&mut self, now: SystemTime) {
|
||||||
|
self.pos = 0;
|
||||||
|
let _ = write!(self, "{}", HttpDate::from(now));
|
||||||
|
debug_assert!(self.pos == DATE_VALUE_LENGTH);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for CachedDate {
|
||||||
|
fn write_str(&mut self, s: &str) -> fmt::Result {
|
||||||
|
let len = s.len();
|
||||||
|
self.bytes[self.pos..self.pos + len].copy_from_slice(s.as_bytes());
|
||||||
|
self.pos += len;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
use test::Bencher;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_date_len() {
|
||||||
|
assert_eq!(DATE_VALUE_LENGTH, "Sun, 06 Nov 1994 08:49:37 GMT".len());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_date_check(b: &mut Bencher) {
|
||||||
|
let mut date = CachedDate::new();
|
||||||
|
// cache the first update
|
||||||
|
date.check();
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
date.check();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_date_render(b: &mut Bencher) {
|
||||||
|
let mut date = CachedDate::new();
|
||||||
|
let now = SystemTime::now();
|
||||||
|
date.render(now);
|
||||||
|
b.bytes = date.buffer().len() as u64;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
date.render(now);
|
||||||
|
test::black_box(&date);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
217
hyper/src/common/drain.rs
Normal file
217
hyper/src/common/drain.rs
Normal file
|
|
@ -0,0 +1,217 @@
|
||||||
|
use std::mem;
|
||||||
|
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use tokio::sync::watch;
|
||||||
|
|
||||||
|
use super::{task, Future, Pin, Poll};
|
||||||
|
|
||||||
|
pub(crate) fn channel() -> (Signal, Watch) {
|
||||||
|
let (tx, rx) = watch::channel(());
|
||||||
|
(Signal { tx }, Watch { rx })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Signal {
|
||||||
|
tx: watch::Sender<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Draining(Pin<Box<dyn Future<Output = ()> + Send + Sync>>);
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Watch {
|
||||||
|
rx: watch::Receiver<()>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Watching<F, FN> {
|
||||||
|
#[pin]
|
||||||
|
future: F,
|
||||||
|
state: State<FN>,
|
||||||
|
watch: Pin<Box<dyn Future<Output = ()> + Send + Sync>>,
|
||||||
|
_rx: watch::Receiver<()>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<F> {
|
||||||
|
Watch(F),
|
||||||
|
Draining,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Signal {
|
||||||
|
pub(crate) fn drain(self) -> Draining {
|
||||||
|
let _ = self.tx.send(());
|
||||||
|
Draining(Box::pin(async move { self.tx.closed().await }))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for Draining {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
Pin::new(&mut self.as_mut().0).poll(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Watch {
|
||||||
|
pub(crate) fn watch<F, FN>(self, future: F, on_drain: FN) -> Watching<F, FN>
|
||||||
|
where
|
||||||
|
F: Future,
|
||||||
|
FN: FnOnce(Pin<&mut F>),
|
||||||
|
{
|
||||||
|
let Self { mut rx } = self;
|
||||||
|
let _rx = rx.clone();
|
||||||
|
Watching {
|
||||||
|
future,
|
||||||
|
state: State::Watch(on_drain),
|
||||||
|
watch: Box::pin(async move {
|
||||||
|
let _ = rx.changed().await;
|
||||||
|
}),
|
||||||
|
// Keep the receiver alive until the future completes, so that
|
||||||
|
// dropping it can signal that draining has completed.
|
||||||
|
_rx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, FN> Future for Watching<F, FN>
|
||||||
|
where
|
||||||
|
F: Future,
|
||||||
|
FN: FnOnce(Pin<&mut F>),
|
||||||
|
{
|
||||||
|
type Output = F::Output;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut me = self.project();
|
||||||
|
loop {
|
||||||
|
match mem::replace(me.state, State::Draining) {
|
||||||
|
State::Watch(on_drain) => {
|
||||||
|
match Pin::new(&mut me.watch).poll(cx) {
|
||||||
|
Poll::Ready(()) => {
|
||||||
|
// Drain has been triggered!
|
||||||
|
on_drain(me.future.as_mut());
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
*me.state = State::Watch(on_drain);
|
||||||
|
return me.future.poll(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
State::Draining => return me.future.poll(cx),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
struct TestMe {
|
||||||
|
draining: bool,
|
||||||
|
finished: bool,
|
||||||
|
poll_cnt: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Future for TestMe {
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, _: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.poll_cnt += 1;
|
||||||
|
if self.finished {
|
||||||
|
Poll::Ready(())
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn watch() {
|
||||||
|
let mut mock = tokio_test::task::spawn(());
|
||||||
|
mock.enter(|cx, _| {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
let fut = TestMe {
|
||||||
|
draining: false,
|
||||||
|
finished: false,
|
||||||
|
poll_cnt: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut watch = rx.watch(fut, |mut fut| {
|
||||||
|
fut.draining = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
assert_eq!(watch.future.poll_cnt, 0);
|
||||||
|
|
||||||
|
// First poll should poll the inner future
|
||||||
|
assert!(Pin::new(&mut watch).poll(cx).is_pending());
|
||||||
|
assert_eq!(watch.future.poll_cnt, 1);
|
||||||
|
|
||||||
|
// Second poll should poll the inner future again
|
||||||
|
assert!(Pin::new(&mut watch).poll(cx).is_pending());
|
||||||
|
assert_eq!(watch.future.poll_cnt, 2);
|
||||||
|
|
||||||
|
let mut draining = tx.drain();
|
||||||
|
// Drain signaled, but needs another poll to be noticed.
|
||||||
|
assert!(!watch.future.draining);
|
||||||
|
assert_eq!(watch.future.poll_cnt, 2);
|
||||||
|
|
||||||
|
// Now, poll after drain has been signaled.
|
||||||
|
assert!(Pin::new(&mut watch).poll(cx).is_pending());
|
||||||
|
assert_eq!(watch.future.poll_cnt, 3);
|
||||||
|
assert!(watch.future.draining);
|
||||||
|
|
||||||
|
// Draining is not ready until watcher completes
|
||||||
|
assert!(Pin::new(&mut draining).poll(cx).is_pending());
|
||||||
|
|
||||||
|
// Finishing up the watch future
|
||||||
|
watch.future.finished = true;
|
||||||
|
assert!(Pin::new(&mut watch).poll(cx).is_ready());
|
||||||
|
assert_eq!(watch.future.poll_cnt, 4);
|
||||||
|
drop(watch);
|
||||||
|
|
||||||
|
assert!(Pin::new(&mut draining).poll(cx).is_ready());
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn watch_clones() {
|
||||||
|
let mut mock = tokio_test::task::spawn(());
|
||||||
|
mock.enter(|cx, _| {
|
||||||
|
let (tx, rx) = channel();
|
||||||
|
|
||||||
|
let fut1 = TestMe {
|
||||||
|
draining: false,
|
||||||
|
finished: false,
|
||||||
|
poll_cnt: 0,
|
||||||
|
};
|
||||||
|
let fut2 = TestMe {
|
||||||
|
draining: false,
|
||||||
|
finished: false,
|
||||||
|
poll_cnt: 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
let watch1 = rx.clone().watch(fut1, |mut fut| {
|
||||||
|
fut.draining = true;
|
||||||
|
});
|
||||||
|
let watch2 = rx.watch(fut2, |mut fut| {
|
||||||
|
fut.draining = true;
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut draining = tx.drain();
|
||||||
|
|
||||||
|
// Still 2 outstanding watchers
|
||||||
|
assert!(Pin::new(&mut draining).poll(cx).is_pending());
|
||||||
|
|
||||||
|
// drop 1 for whatever reason
|
||||||
|
drop(watch1);
|
||||||
|
|
||||||
|
// Still not ready, 1 other watcher still pending
|
||||||
|
assert!(Pin::new(&mut draining).poll(cx).is_pending());
|
||||||
|
|
||||||
|
drop(watch2);
|
||||||
|
|
||||||
|
// Now all watchers are gone, draining is complete
|
||||||
|
assert!(Pin::new(&mut draining).poll(cx).is_ready());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
116
hyper/src/common/exec.rs
Normal file
116
hyper/src/common/exec.rs
Normal file
|
|
@ -0,0 +1,116 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::Arc;
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
use crate::body::Body;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
use crate::body::HttpBody;
|
||||||
|
#[cfg(all(feature = "http2", feature = "server"))]
|
||||||
|
use crate::proto::h2::server::H2Stream;
|
||||||
|
use crate::rt::Executor;
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
use crate::server::server::{new_svc::NewSvcTask, Watcher};
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
use crate::service::HttpService;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub trait ConnStreamExec<F, B: HttpBody>: Clone {
|
||||||
|
fn execute_h2stream(&mut self, fut: H2Stream<F, B>);
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
pub trait NewSvcExec<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>>: Clone {
|
||||||
|
fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>);
|
||||||
|
}
|
||||||
|
pub(crate) type BoxSendFuture = Pin<Box<dyn Future<Output = ()> + Send>>;
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum Exec {
|
||||||
|
Default,
|
||||||
|
Executor(Arc<dyn Executor<BoxSendFuture> + Send + Sync>),
|
||||||
|
}
|
||||||
|
impl Exec {
|
||||||
|
pub(crate) fn execute<F>(&self, fut: F)
|
||||||
|
where
|
||||||
|
F: Future<Output = ()> + Send + 'static,
|
||||||
|
{
|
||||||
|
match *self {
|
||||||
|
Exec::Default => {
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
{
|
||||||
|
tokio::task::spawn(fut);
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "tcp"))] { panic!("executor must be set") }
|
||||||
|
}
|
||||||
|
Exec::Executor(ref e) => {
|
||||||
|
e.execute(Box::pin(fut));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for Exec {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Exec").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl<F, B> ConnStreamExec<F, B> for Exec
|
||||||
|
where
|
||||||
|
H2Stream<F, B>: Future<Output = ()> + Send + 'static,
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
fn execute_h2stream(&mut self, fut: H2Stream<F, B>) {
|
||||||
|
self.execute(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
impl<I, N, S, E, W> NewSvcExec<I, N, S, E, W> for Exec
|
||||||
|
where
|
||||||
|
NewSvcTask<I, N, S, E, W>: Future<Output = ()> + Send + 'static,
|
||||||
|
S: HttpService<Body>,
|
||||||
|
W: Watcher<I, S, E>,
|
||||||
|
{
|
||||||
|
fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>) {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
impl<E, F, B> ConnStreamExec<F, B> for E
|
||||||
|
where
|
||||||
|
E: Executor<H2Stream<F, B>> + Clone,
|
||||||
|
H2Stream<F, B>: Future<Output = ()>,
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
fn execute_h2stream(&mut self, fut: H2Stream<F, B>) {
|
||||||
|
self.execute(fut)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
impl<I, N, S, E, W> NewSvcExec<I, N, S, E, W> for E
|
||||||
|
where
|
||||||
|
E: Executor<NewSvcTask<I, N, S, E, W>> + Clone,
|
||||||
|
NewSvcTask<I, N, S, E, W>: Future<Output = ()>,
|
||||||
|
S: HttpService<Body>,
|
||||||
|
W: Watcher<I, S, E>,
|
||||||
|
{
|
||||||
|
fn execute_new_svc(&mut self, fut: NewSvcTask<I, N, S, E, W>) {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub(crate) struct H2Stream<F, B>(std::marker::PhantomData<(F, B)>);
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
impl<F, B, E> Future for H2Stream<F, B>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<http::Response<B>, E>>,
|
||||||
|
B: crate::body::HttpBody,
|
||||||
|
B::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Output = ();
|
||||||
|
fn poll(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_cx: &mut std::task::Context<'_>,
|
||||||
|
) -> std::task::Poll<Self::Output> {
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
}
|
||||||
3
hyper/src/common/io/mod.rs
Normal file
3
hyper/src/common/io/mod.rs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
mod rewind;
|
||||||
|
|
||||||
|
pub(crate) use self::rewind::Rewind;
|
||||||
155
hyper/src/common/io/rewind.rs
Normal file
155
hyper/src/common/io/rewind.rs
Normal file
|
|
@ -0,0 +1,155 @@
|
||||||
|
use std::marker::Unpin;
|
||||||
|
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<T> {
|
||||||
|
pre: Option<Bytes>,
|
||||||
|
inner: T,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> Rewind<T> {
|
||||||
|
#[cfg(any(all(feature = "http2", feature = "server"), test))]
|
||||||
|
pub(crate) fn new(io: T) -> Self {
|
||||||
|
Rewind {
|
||||||
|
pre: None,
|
||||||
|
inner: io,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn new_buffered(io: T, buf: Bytes) -> Self {
|
||||||
|
Rewind {
|
||||||
|
pre: Some(buf),
|
||||||
|
inner: io,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(all(feature = "http1", feature = "http2", feature = "server"), test))]
|
||||||
|
pub(crate) fn rewind(&mut self, bs: Bytes) {
|
||||||
|
debug_assert!(self.pre.is_none());
|
||||||
|
self.pre = Some(bs);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_inner(self) -> (T, Bytes) {
|
||||||
|
(self.inner, self.pre.unwrap_or_else(Bytes::new))
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||||
|
// &mut self.inner
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsyncRead for Rewind<T>
|
||||||
|
where
|
||||||
|
T: AsyncRead + Unpin,
|
||||||
|
{
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
|
if let Some(mut prefix) = self.pre.take() {
|
||||||
|
// If there are no remaining bytes, let the bytes get dropped.
|
||||||
|
if !prefix.is_empty() {
|
||||||
|
let copy_len = cmp::min(prefix.len(), buf.remaining());
|
||||||
|
// TODO: There should be a way to do following two lines cleaner...
|
||||||
|
buf.put_slice(&prefix[..copy_len]);
|
||||||
|
prefix.advance(copy_len);
|
||||||
|
// Put back what's left
|
||||||
|
if !prefix.is_empty() {
|
||||||
|
self.pre = Some(prefix);
|
||||||
|
}
|
||||||
|
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Pin::new(&mut self.inner).poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> AsyncWrite for Rewind<T>
|
||||||
|
where
|
||||||
|
T: AsyncWrite + Unpin,
|
||||||
|
{
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
Pin::new(&mut self.inner).poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_write_vectored(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
bufs: &[io::IoSlice<'_>],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
Pin::new(&mut self.inner).poll_write_vectored(cx, bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
Pin::new(&mut self.inner).poll_flush(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
Pin::new(&mut self.inner).poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_write_vectored(&self) -> bool {
|
||||||
|
self.inner.is_write_vectored()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
// FIXME: re-implement tests with `async/await`, this import should
|
||||||
|
// trigger a warning to remind us
|
||||||
|
use super::Rewind;
|
||||||
|
use bytes::Bytes;
|
||||||
|
use tokio::io::AsyncReadExt;
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn partial_rewind() {
|
||||||
|
let underlying = [104, 101, 108, 108, 111];
|
||||||
|
|
||||||
|
let mock = tokio_test::io::Builder::new().read(&underlying).build();
|
||||||
|
|
||||||
|
let mut stream = Rewind::new(mock);
|
||||||
|
|
||||||
|
// Read off some bytes, ensure we filled o1
|
||||||
|
let mut buf = [0; 2];
|
||||||
|
stream.read_exact(&mut buf).await.expect("read1");
|
||||||
|
|
||||||
|
// Rewind the stream so that it is as if we never read in the first place.
|
||||||
|
stream.rewind(Bytes::copy_from_slice(&buf[..]));
|
||||||
|
|
||||||
|
let mut buf = [0; 5];
|
||||||
|
stream.read_exact(&mut buf).await.expect("read1");
|
||||||
|
|
||||||
|
// At this point we should have read everything that was in the MockStream
|
||||||
|
assert_eq!(&buf, &underlying);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn full_rewind() {
|
||||||
|
let underlying = [104, 101, 108, 108, 111];
|
||||||
|
|
||||||
|
let mock = tokio_test::io::Builder::new().read(&underlying).build();
|
||||||
|
|
||||||
|
let mut stream = Rewind::new(mock);
|
||||||
|
|
||||||
|
let mut buf = [0; 5];
|
||||||
|
stream.read_exact(&mut buf).await.expect("read1");
|
||||||
|
|
||||||
|
// Rewind the stream so that it is as if we never read in the first place.
|
||||||
|
stream.rewind(Bytes::copy_from_slice(&buf[..]));
|
||||||
|
|
||||||
|
let mut buf = [0; 5];
|
||||||
|
stream.read_exact(&mut buf).await.expect("read1");
|
||||||
|
}
|
||||||
|
}
|
||||||
76
hyper/src/common/lazy.rs
Normal file
76
hyper/src/common/lazy.rs
Normal file
|
|
@ -0,0 +1,76 @@
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
|
||||||
|
use super::{task, Future, Pin, Poll};
|
||||||
|
|
||||||
|
pub(crate) trait Started: Future {
|
||||||
|
fn started(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn lazy<F, R>(func: F) -> Lazy<F, R>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
R: Future + Unpin,
|
||||||
|
{
|
||||||
|
Lazy {
|
||||||
|
inner: Inner::Init { func },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FIXME: allow() required due to `impl Trait` leaking types to this lint
|
||||||
|
pin_project! {
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub(crate) struct Lazy<F, R> {
|
||||||
|
#[pin]
|
||||||
|
inner: Inner<F, R>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[project = InnerProj]
|
||||||
|
#[project_replace = InnerProjReplace]
|
||||||
|
enum Inner<F, R> {
|
||||||
|
Init { func: F },
|
||||||
|
Fut { #[pin] fut: R },
|
||||||
|
Empty,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, R> Started for Lazy<F, R>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
R: Future,
|
||||||
|
{
|
||||||
|
fn started(&self) -> bool {
|
||||||
|
match self.inner {
|
||||||
|
Inner::Init { .. } => false,
|
||||||
|
Inner::Fut { .. } | Inner::Empty => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, R> Future for Lazy<F, R>
|
||||||
|
where
|
||||||
|
F: FnOnce() -> R,
|
||||||
|
R: Future,
|
||||||
|
{
|
||||||
|
type Output = R::Output;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut this = self.project();
|
||||||
|
|
||||||
|
if let InnerProj::Fut { fut } = this.inner.as_mut().project() {
|
||||||
|
return fut.poll(cx);
|
||||||
|
}
|
||||||
|
|
||||||
|
match this.inner.as_mut().project_replace(Inner::Empty) {
|
||||||
|
InnerProjReplace::Init { func } => {
|
||||||
|
this.inner.set(Inner::Fut { fut: func() });
|
||||||
|
if let InnerProj::Fut { fut } = this.inner.project() {
|
||||||
|
return fut.poll(cx);
|
||||||
|
}
|
||||||
|
unreachable!()
|
||||||
|
}
|
||||||
|
_ => unreachable!("lazy state wrong"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
39
hyper/src/common/mod.rs
Normal file
39
hyper/src/common/mod.rs
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
macro_rules! ready {
|
||||||
|
($e:expr) => {
|
||||||
|
match $e {
|
||||||
|
std::task::Poll::Ready(v) => v,
|
||||||
|
std::task::Poll::Pending => return std::task::Poll::Pending,
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) mod buf;
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
pub(crate) mod date;
|
||||||
|
#[cfg(all(feature = "server", any(feature = "http1", feature = "http2")))]
|
||||||
|
pub(crate) mod drain;
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2", feature = "server"))]
|
||||||
|
pub(crate) mod exec;
|
||||||
|
pub(crate) mod io;
|
||||||
|
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||||
|
mod lazy;
|
||||||
|
mod never;
|
||||||
|
#[cfg(any(
|
||||||
|
feature = "stream",
|
||||||
|
all(feature = "client", any(feature = "http1", feature = "http2"))
|
||||||
|
))]
|
||||||
|
pub(crate) mod sync_wrapper;
|
||||||
|
pub(crate) mod task;
|
||||||
|
pub(crate) mod watch;
|
||||||
|
|
||||||
|
#[cfg(all(feature = "client", any(feature = "http1", feature = "http2")))]
|
||||||
|
pub(crate) use self::lazy::{lazy, Started as Lazy};
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2", feature = "runtime"))]
|
||||||
|
pub(crate) use self::never::Never;
|
||||||
|
pub(crate) use self::task::Poll;
|
||||||
|
|
||||||
|
// group up types normally needed for `Future`
|
||||||
|
cfg_proto! {
|
||||||
|
pub(crate) use std::marker::Unpin;
|
||||||
|
}
|
||||||
|
pub(crate) use std::{future::Future, pin::Pin};
|
||||||
21
hyper/src/common/never.rs
Normal file
21
hyper/src/common/never.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
||||||
|
//! 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 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
110
hyper/src/common/sync_wrapper.rs
Normal file
110
hyper/src/common/sync_wrapper.rs
Normal file
|
|
@ -0,0 +1,110 @@
|
||||||
|
/*
|
||||||
|
* This is a copy of the sync_wrapper crate.
|
||||||
|
*/
|
||||||
|
|
||||||
|
/// A mutual exclusion primitive that relies on static type information only
|
||||||
|
///
|
||||||
|
/// In some cases synchronization can be proven statically: whenever you hold an exclusive `&mut`
|
||||||
|
/// reference, the Rust type system ensures that no other part of the program can hold another
|
||||||
|
/// reference to the data. Therefore it is safe to access it even if the current thread obtained
|
||||||
|
/// this reference via a channel. Whenever this is the case, the overhead of allocating and locking
|
||||||
|
/// a [`Mutex`] can be avoided by using this static version.
|
||||||
|
///
|
||||||
|
/// One example where this is often applicable is [`Future`], which requires an exclusive reference
|
||||||
|
/// for its [`poll`] method: While a given `Future` implementation may not be safe to access by
|
||||||
|
/// multiple threads concurrently, the executor can only run the `Future` on one thread at any
|
||||||
|
/// given time, making it [`Sync`] in practice as long as the implementation is `Send`. You can
|
||||||
|
/// therefore use the sync wrapper to prove that your data structure is `Sync` even though it
|
||||||
|
/// contains such a `Future`.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||||
|
/// use std::future::Future;
|
||||||
|
///
|
||||||
|
/// struct MyThing {
|
||||||
|
/// future: SyncWrapper<Box<dyn Future<Output = String> + Send>>,
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// impl MyThing {
|
||||||
|
/// // all accesses to `self.future` now require an exclusive reference or ownership
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// fn assert_sync<T: Sync>() {}
|
||||||
|
///
|
||||||
|
/// assert_sync::<MyThing>();
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`Mutex`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html
|
||||||
|
/// [`Future`]: https://doc.rust-lang.org/std/future/trait.Future.html
|
||||||
|
/// [`poll`]: https://doc.rust-lang.org/std/future/trait.Future.html#method.poll
|
||||||
|
/// [`Sync`]: https://doc.rust-lang.org/std/marker/trait.Sync.html
|
||||||
|
#[repr(transparent)]
|
||||||
|
pub(crate) struct SyncWrapper<T>(T);
|
||||||
|
|
||||||
|
impl<T> SyncWrapper<T> {
|
||||||
|
/// Creates a new SyncWrapper containing the given value.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||||
|
///
|
||||||
|
/// let wrapped = SyncWrapper::new(42);
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn new(value: T) -> Self {
|
||||||
|
Self(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Acquires a reference to the protected value.
|
||||||
|
///
|
||||||
|
/// This is safe because it requires an exclusive reference to the wrapper. Therefore this method
|
||||||
|
/// neither panics nor does it return an error. This is in contrast to [`Mutex::get_mut`] which
|
||||||
|
/// returns an error if another thread panicked while holding the lock. It is not recommended
|
||||||
|
/// to send an exclusive reference to a potentially damaged value to another thread for further
|
||||||
|
/// processing.
|
||||||
|
///
|
||||||
|
/// [`Mutex::get_mut`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.get_mut
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||||
|
///
|
||||||
|
/// let mut wrapped = SyncWrapper::new(42);
|
||||||
|
/// let value = wrapped.get_mut();
|
||||||
|
/// *value = 0;
|
||||||
|
/// assert_eq!(*wrapped.get_mut(), 0);
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn get_mut(&mut self) -> &mut T {
|
||||||
|
&mut self.0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes this wrapper, returning the underlying data.
|
||||||
|
///
|
||||||
|
/// This is safe because it requires ownership of the wrapper, aherefore this method will neither
|
||||||
|
/// panic nor does it return an error. This is in contrast to [`Mutex::into_inner`] which
|
||||||
|
/// returns an error if another thread panicked while holding the lock. It is not recommended
|
||||||
|
/// to send an exclusive reference to a potentially damaged value to another thread for further
|
||||||
|
/// processing.
|
||||||
|
///
|
||||||
|
/// [`Mutex::into_inner`]: https://doc.rust-lang.org/std/sync/struct.Mutex.html#method.into_inner
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// use hyper::common::sync_wrapper::SyncWrapper;
|
||||||
|
///
|
||||||
|
/// let mut wrapped = SyncWrapper::new(42);
|
||||||
|
/// assert_eq!(wrapped.into_inner(), 42);
|
||||||
|
/// ```
|
||||||
|
#[allow(dead_code)]
|
||||||
|
pub(crate) fn into_inner(self) -> T {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// this is safe because the only operations permitted on this data structure require exclusive
|
||||||
|
// access or ownership
|
||||||
|
unsafe impl<T: Send> Sync for SyncWrapper<T> {}
|
||||||
12
hyper/src/common/task.rs
Normal file
12
hyper/src/common/task.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
use super::Never;
|
||||||
|
pub(crate) use std::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<Never> {
|
||||||
|
cx.waker().wake_by_ref();
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
73
hyper/src/common/watch.rs
Normal file
73
hyper/src/common/watch.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
//! An SPSC broadcast channel.
|
||||||
|
//!
|
||||||
|
//! - The value can only be a `usize`.
|
||||||
|
//! - The consumer is only notified if the value is different.
|
||||||
|
//! - The value `0` is reserved for closed.
|
||||||
|
|
||||||
|
use futures_util::task::AtomicWaker;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicUsize, Ordering},
|
||||||
|
Arc,
|
||||||
|
};
|
||||||
|
use std::task;
|
||||||
|
|
||||||
|
type Value = usize;
|
||||||
|
|
||||||
|
pub(crate) const CLOSED: usize = 0;
|
||||||
|
|
||||||
|
pub(crate) fn channel(initial: Value) -> (Sender, Receiver) {
|
||||||
|
debug_assert!(
|
||||||
|
initial != CLOSED,
|
||||||
|
"watch::channel initial state of 0 is reserved"
|
||||||
|
);
|
||||||
|
|
||||||
|
let shared = Arc::new(Shared {
|
||||||
|
value: AtomicUsize::new(initial),
|
||||||
|
waker: AtomicWaker::new(),
|
||||||
|
});
|
||||||
|
|
||||||
|
(
|
||||||
|
Sender {
|
||||||
|
shared: shared.clone(),
|
||||||
|
},
|
||||||
|
Receiver { shared },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Sender {
|
||||||
|
shared: Arc<Shared>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct Receiver {
|
||||||
|
shared: Arc<Shared>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Shared {
|
||||||
|
value: AtomicUsize,
|
||||||
|
waker: AtomicWaker,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Sender {
|
||||||
|
pub(crate) fn send(&mut self, value: Value) {
|
||||||
|
if self.shared.value.swap(value, Ordering::SeqCst) != value {
|
||||||
|
self.shared.waker.wake();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for Sender {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
self.send(CLOSED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Receiver {
|
||||||
|
pub(crate) fn load(&mut self, cx: &mut task::Context<'_>) -> Value {
|
||||||
|
self.shared.waker.register(cx.waker());
|
||||||
|
self.shared.value.load(Ordering::SeqCst)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn peek(&self) -> Value {
|
||||||
|
self.shared.value.load(Ordering::Relaxed)
|
||||||
|
}
|
||||||
|
}
|
||||||
558
hyper/src/error.rs
Normal file
558
hyper/src/error.rs
Normal file
|
|
@ -0,0 +1,558 @@
|
||||||
|
//! Error and Result module.
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
/// Result type often returned from methods that can have hyper `Error`s.
|
||||||
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
type Cause = Box<dyn StdError + Send + Sync>;
|
||||||
|
/// Represents errors that can occur handling HTTP streams.
|
||||||
|
pub struct Error {
|
||||||
|
inner: Box<ErrorImpl>,
|
||||||
|
}
|
||||||
|
struct ErrorImpl {
|
||||||
|
kind: Kind,
|
||||||
|
cause: Option<Cause>,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum Kind {
|
||||||
|
Parse(Parse),
|
||||||
|
User(User),
|
||||||
|
/// A message reached EOF, but is not complete.
|
||||||
|
#[allow(unused)]
|
||||||
|
IncompleteMessage,
|
||||||
|
/// A connection received a message (or bytes) when not waiting for one.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
UnexpectedMessage,
|
||||||
|
/// A pending item was dropped before ever being processed.
|
||||||
|
Canceled,
|
||||||
|
/// Indicates a channel (client or body sender) is closed.
|
||||||
|
ChannelClosed,
|
||||||
|
/// An `io::Error` that occurred while trying to read or write to a network stream.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Io,
|
||||||
|
/// Error occurred while connecting.
|
||||||
|
#[allow(unused)]
|
||||||
|
Connect,
|
||||||
|
/// Error creating a TcpListener.
|
||||||
|
#[cfg(all(feature = "tcp", feature = "server"))]
|
||||||
|
Listen,
|
||||||
|
/// Error accepting on an Incoming stream.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Accept,
|
||||||
|
/// User took too long to send headers
|
||||||
|
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
|
||||||
|
HeaderTimeout,
|
||||||
|
/// Error while reading a body from connection.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
|
||||||
|
Body,
|
||||||
|
/// Error while writing a body to connection.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
BodyWrite,
|
||||||
|
/// Error calling AsyncWrite::shutdown()
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Shutdown,
|
||||||
|
/// A general error from h2.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
Http2,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum Parse {
|
||||||
|
Method,
|
||||||
|
Version,
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
VersionH2,
|
||||||
|
Uri,
|
||||||
|
#[cfg_attr(not(all(feature = "http1", feature = "server")), allow(unused))]
|
||||||
|
UriTooLong,
|
||||||
|
Header(Header),
|
||||||
|
TooLarge,
|
||||||
|
Status,
|
||||||
|
#[cfg_attr(debug_assertions, allow(unused))]
|
||||||
|
Internal,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum Header {
|
||||||
|
Token,
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
ContentLengthInvalid,
|
||||||
|
#[cfg(all(feature = "http1", feature = "server"))]
|
||||||
|
TransferEncodingInvalid,
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
TransferEncodingUnexpected,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) enum User {
|
||||||
|
/// Error calling user's HttpBody::poll_data().
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Body,
|
||||||
|
/// The user aborted writing of the outgoing body.
|
||||||
|
BodyWriteAborted,
|
||||||
|
/// Error calling user's MakeService.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
MakeService,
|
||||||
|
/// Error from future of user's Service.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Service,
|
||||||
|
/// User tried to send a certain header in an unexpected context.
|
||||||
|
///
|
||||||
|
/// For example, sending both `content-length` and `transfer-encoding`.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
UnexpectedHeader,
|
||||||
|
/// User tried to create a Request with bad version.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
UnsupportedVersion,
|
||||||
|
/// User tried to create a CONNECT Request with the Client.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
UnsupportedRequestMethod,
|
||||||
|
/// User tried to respond with a 1xx (not 101) response code.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
UnsupportedStatusCode,
|
||||||
|
/// User tried to send a Request with Client with non-absolute URI.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
AbsoluteUriRequired,
|
||||||
|
/// User tried polling for an upgrade that doesn't exist.
|
||||||
|
NoUpgrade,
|
||||||
|
/// User polled for an upgrade, but low-level API is not using upgrades.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
ManualUpgrade,
|
||||||
|
/// User called `server::Connection::without_shutdown()` on an HTTP/2 conn.
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
WithoutShutdownNonHttp1,
|
||||||
|
/// The dispatch task is gone.
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
DispatchGone,
|
||||||
|
/// User aborted in an FFI callback.
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
AbortedByCallback,
|
||||||
|
}
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct TimedOut;
|
||||||
|
impl Error {
|
||||||
|
/// Returns true if this was an HTTP parse error.
|
||||||
|
pub(crate) fn is_parse(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::Parse(_))
|
||||||
|
}
|
||||||
|
/// Returns true if this was an HTTP parse error caused by a message that was too large.
|
||||||
|
pub(crate) fn is_parse_too_large(&self) -> bool {
|
||||||
|
matches!(
|
||||||
|
self.inner.kind, Kind::Parse(Parse::TooLarge) |
|
||||||
|
Kind::Parse(Parse::UriTooLong)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
/// Returns true if this was an HTTP parse error caused by an invalid response status code or
|
||||||
|
/// reason phrase.
|
||||||
|
pub(crate) fn is_parse_status(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::Parse(Parse::Status))
|
||||||
|
}
|
||||||
|
/// Returns true if this error was caused by user code.
|
||||||
|
pub(crate) fn is_user(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::User(_))
|
||||||
|
}
|
||||||
|
/// Returns true if this was about a `Request` that was canceled.
|
||||||
|
pub(crate) fn is_canceled(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::Canceled)
|
||||||
|
}
|
||||||
|
/// Returns true if a sender's channel is closed.
|
||||||
|
pub(crate) fn is_closed(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::ChannelClosed)
|
||||||
|
}
|
||||||
|
/// Returns true if this was an error from `Connect`.
|
||||||
|
pub(crate) fn is_connect(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::Connect)
|
||||||
|
}
|
||||||
|
/// Returns true if the connection closed before a message could complete.
|
||||||
|
pub(crate) fn is_incomplete_message(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::IncompleteMessage)
|
||||||
|
}
|
||||||
|
/// Returns true if the body write was aborted.
|
||||||
|
pub(crate) fn is_body_write_aborted(&self) -> bool {
|
||||||
|
matches!(self.inner.kind, Kind::User(User::BodyWriteAborted))
|
||||||
|
}
|
||||||
|
/// Returns true if the error was caused by a timeout.
|
||||||
|
pub(crate) fn is_timeout(&self) -> bool {
|
||||||
|
self.find_source::<TimedOut>().is_some()
|
||||||
|
}
|
||||||
|
/// Consumes the error, returning its cause.
|
||||||
|
pub(crate) fn into_cause(self) -> Option<Box<dyn StdError + Send + Sync>> {
|
||||||
|
self.inner.cause
|
||||||
|
}
|
||||||
|
pub(super) fn new(kind: Kind) -> Error {
|
||||||
|
Error {
|
||||||
|
inner: Box::new(ErrorImpl { kind, cause: None }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) fn with<C: Into<Cause>>(mut self, cause: C) -> Error {
|
||||||
|
self.inner.cause = Some(cause.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#[cfg(any(all(feature = "http1", feature = "server"), feature = "ffi"))]
|
||||||
|
pub(super) fn kind(&self) -> &Kind {
|
||||||
|
&self.inner.kind
|
||||||
|
}
|
||||||
|
pub(crate) fn find_source<E: StdError + 'static>(&self) -> Option<&E> {
|
||||||
|
let mut cause = self.source();
|
||||||
|
while let Some(err) = cause {
|
||||||
|
if let Some(ref typed) = err.downcast_ref() {
|
||||||
|
return Some(typed);
|
||||||
|
}
|
||||||
|
cause = err.source();
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(super) fn h2_reason(&self) -> h2::Reason {
|
||||||
|
self.find_source::<h2::Error>()
|
||||||
|
.and_then(|h2_err| h2_err.reason())
|
||||||
|
.unwrap_or(h2::Reason::INTERNAL_ERROR)
|
||||||
|
}
|
||||||
|
pub(super) fn new_canceled() -> Error {
|
||||||
|
Error::new(Kind::Canceled)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn new_incomplete() -> Error {
|
||||||
|
Error::new(Kind::IncompleteMessage)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn new_too_large() -> Error {
|
||||||
|
Error::new(Kind::Parse(Parse::TooLarge))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn new_version_h2() -> Error {
|
||||||
|
Error::new(Kind::Parse(Parse::VersionH2))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn new_unexpected_message() -> Error {
|
||||||
|
Error::new(Kind::UnexpectedMessage)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pub(super) fn new_io(cause: std::io::Error) -> Error {
|
||||||
|
Error::new(Kind::Io).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "server", feature = "tcp"))]
|
||||||
|
pub(super) fn new_listen<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new(Kind::Listen).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(super) fn new_accept<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new(Kind::Accept).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(super) fn new_connect<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new(Kind::Connect).with(cause)
|
||||||
|
}
|
||||||
|
pub(super) fn new_closed() -> Error {
|
||||||
|
Error::new(Kind::ChannelClosed)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
|
||||||
|
pub(super) fn new_body<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new(Kind::Body).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pub(super) fn new_body_write<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new(Kind::BodyWrite).with(cause)
|
||||||
|
}
|
||||||
|
pub(super) fn new_body_write_aborted() -> Error {
|
||||||
|
Error::new(Kind::User(User::BodyWriteAborted))
|
||||||
|
}
|
||||||
|
fn new_user(user: User) -> Error {
|
||||||
|
Error::new(Kind::User(user))
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(super) fn new_user_header() -> Error {
|
||||||
|
Error::new_user(User::UnexpectedHeader)
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
|
||||||
|
pub(super) fn new_header_timeout() -> Error {
|
||||||
|
Error::new(Kind::HeaderTimeout)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(super) fn new_user_unsupported_version() -> Error {
|
||||||
|
Error::new_user(User::UnsupportedVersion)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(super) fn new_user_unsupported_request_method() -> Error {
|
||||||
|
Error::new_user(User::UnsupportedRequestMethod)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(super) fn new_user_unsupported_status_code() -> Error {
|
||||||
|
Error::new_user(User::UnsupportedStatusCode)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(super) fn new_user_absolute_uri_required() -> Error {
|
||||||
|
Error::new_user(User::AbsoluteUriRequired)
|
||||||
|
}
|
||||||
|
pub(super) fn new_user_no_upgrade() -> Error {
|
||||||
|
Error::new_user(User::NoUpgrade)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn new_user_manual_upgrade() -> Error {
|
||||||
|
Error::new_user(User::ManualUpgrade)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(super) fn new_user_make_service<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new_user(User::MakeService).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pub(super) fn new_user_service<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new_user(User::Service).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pub(super) fn new_user_body<E: Into<Cause>>(cause: E) -> Error {
|
||||||
|
Error::new_user(User::Body).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(super) fn new_without_shutdown_not_h1() -> Error {
|
||||||
|
Error::new(Kind::User(User::WithoutShutdownNonHttp1))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn new_shutdown(cause: std::io::Error) -> Error {
|
||||||
|
Error::new(Kind::Shutdown).with(cause)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
pub(super) fn new_user_aborted_by_callback() -> Error {
|
||||||
|
Error::new_user(User::AbortedByCallback)
|
||||||
|
}
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(super) fn new_user_dispatch_gone() -> Error {
|
||||||
|
Error::new(Kind::User(User::DispatchGone))
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(super) fn new_h2(cause: ::h2::Error) -> Error {
|
||||||
|
if cause.is_io() {
|
||||||
|
Error::new_io(cause.into_io().expect("h2::Error::is_io"))
|
||||||
|
} else {
|
||||||
|
Error::new(Kind::Http2).with(cause)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// The error's standalone message, without the message from the source.
|
||||||
|
pub(crate) fn message(&self) -> impl fmt::Display + '_ {
|
||||||
|
self.description()
|
||||||
|
}
|
||||||
|
fn description(&self) -> &str {
|
||||||
|
match self.inner.kind {
|
||||||
|
Kind::Parse(Parse::Method) => "invalid HTTP method parsed",
|
||||||
|
Kind::Parse(Parse::Version) => "invalid HTTP version parsed",
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Kind::Parse(Parse::VersionH2) => {
|
||||||
|
"invalid HTTP version parsed (found HTTP2 preface)"
|
||||||
|
}
|
||||||
|
Kind::Parse(Parse::Uri) => "invalid URI",
|
||||||
|
Kind::Parse(Parse::UriTooLong) => "URI too long",
|
||||||
|
Kind::Parse(Parse::Header(Header::Token)) => "invalid HTTP header parsed",
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Kind::Parse(Parse::Header(Header::ContentLengthInvalid)) => {
|
||||||
|
"invalid content-length parsed"
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "server"))]
|
||||||
|
Kind::Parse(Parse::Header(Header::TransferEncodingInvalid)) => {
|
||||||
|
"invalid transfer-encoding parsed"
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Kind::Parse(Parse::Header(Header::TransferEncodingUnexpected)) => {
|
||||||
|
"unexpected transfer-encoding parsed"
|
||||||
|
}
|
||||||
|
Kind::Parse(Parse::TooLarge) => "message head is too large",
|
||||||
|
Kind::Parse(Parse::Status) => "invalid HTTP status-code parsed",
|
||||||
|
Kind::Parse(Parse::Internal) => {
|
||||||
|
"internal error inside Hyper and/or its dependencies, please report"
|
||||||
|
}
|
||||||
|
Kind::IncompleteMessage => "connection closed before message completed",
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Kind::UnexpectedMessage => "received unexpected message from connection",
|
||||||
|
Kind::ChannelClosed => "channel closed",
|
||||||
|
Kind::Connect => "error trying to connect",
|
||||||
|
Kind::Canceled => "operation was canceled",
|
||||||
|
#[cfg(all(feature = "server", feature = "tcp"))]
|
||||||
|
Kind::Listen => "error creating server listener",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::Accept => "error accepting connection",
|
||||||
|
#[cfg(all(feature = "http1", feature = "server", feature = "runtime"))]
|
||||||
|
Kind::HeaderTimeout => "read header from client timeout",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2", feature = "stream"))]
|
||||||
|
Kind::Body => "error reading a body from connection",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Kind::BodyWrite => "error writing a body to connection",
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Kind::Shutdown => "error shutting down connection",
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
Kind::Http2 => "http2 error",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Kind::Io => "connection error",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Kind::User(User::Body) => "error from user's HttpBody stream",
|
||||||
|
Kind::User(User::BodyWriteAborted) => "user body write aborted",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::User(User::MakeService) => "error from user's MakeService",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
Kind::User(User::Service) => "error from user's Service",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::User(User::UnexpectedHeader) => "user sent unexpected header",
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Kind::User(User::UnsupportedVersion) => {
|
||||||
|
"request has unsupported HTTP version"
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Kind::User(User::UnsupportedRequestMethod) => {
|
||||||
|
"request has unsupported HTTP method"
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::User(User::UnsupportedStatusCode) => {
|
||||||
|
"response has 1xx status code, not supported by server"
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Kind::User(User::AbsoluteUriRequired) => "client requires absolute-form URIs",
|
||||||
|
Kind::User(User::NoUpgrade) => "no upgrade available",
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Kind::User(User::ManualUpgrade) => {
|
||||||
|
"upgrade expected but low level API in use"
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::User(User::WithoutShutdownNonHttp1) => {
|
||||||
|
"without_shutdown() called on a non-HTTP/1 connection"
|
||||||
|
}
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
Kind::User(User::DispatchGone) => "dispatch task is gone",
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
Kind::User(User::AbortedByCallback) => {
|
||||||
|
"operation aborted by an application callback"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Debug for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut f = f.debug_tuple("hyper::Error");
|
||||||
|
f.field(&self.inner.kind);
|
||||||
|
if let Some(ref cause) = self.inner.cause {
|
||||||
|
f.field(cause);
|
||||||
|
}
|
||||||
|
f.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl fmt::Display for Error {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
if let Some(ref cause) = self.inner.cause {
|
||||||
|
write!(f, "{}: {}", self.description(), cause)
|
||||||
|
} else {
|
||||||
|
f.write_str(self.description())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl StdError for Error {
|
||||||
|
fn source(&self) -> Option<&(dyn StdError + 'static)> {
|
||||||
|
self.inner.cause.as_ref().map(|cause| &**cause as &(dyn StdError + 'static))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl From<Parse> for Error {
|
||||||
|
fn from(err: Parse) -> Error {
|
||||||
|
Error::new(Kind::Parse(err))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
impl Parse {
|
||||||
|
pub(crate) fn content_length_invalid() -> Self {
|
||||||
|
Parse::Header(Header::ContentLengthInvalid)
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "server"))]
|
||||||
|
pub(crate) fn transfer_encoding_invalid() -> Self {
|
||||||
|
Parse::Header(Header::TransferEncodingInvalid)
|
||||||
|
}
|
||||||
|
pub(crate) fn transfer_encoding_unexpected() -> Self {
|
||||||
|
Parse::Header(Header::TransferEncodingUnexpected)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<httparse::Error> for Parse {
|
||||||
|
fn from(err: httparse::Error) -> Parse {
|
||||||
|
match err {
|
||||||
|
httparse::Error::HeaderName
|
||||||
|
| httparse::Error::HeaderValue
|
||||||
|
| httparse::Error::NewLine
|
||||||
|
| httparse::Error::Token => Parse::Header(Header::Token),
|
||||||
|
httparse::Error::Status => Parse::Status,
|
||||||
|
httparse::Error::TooManyHeaders => Parse::TooLarge,
|
||||||
|
httparse::Error::Version => Parse::Version,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<http::method::InvalidMethod> for Parse {
|
||||||
|
fn from(_: http::method::InvalidMethod) -> Parse {
|
||||||
|
Parse::Method
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<http::status::InvalidStatusCode> for Parse {
|
||||||
|
fn from(_: http::status::InvalidStatusCode) -> Parse {
|
||||||
|
Parse::Status
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<http::uri::InvalidUri> for Parse {
|
||||||
|
fn from(_: http::uri::InvalidUri) -> Parse {
|
||||||
|
Parse::Uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl From<http::uri::InvalidUriParts> for Parse {
|
||||||
|
fn from(_: http::uri::InvalidUriParts) -> Parse {
|
||||||
|
Parse::Uri
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
trait AssertSendSync: Send + Sync + 'static {}
|
||||||
|
#[doc(hidden)]
|
||||||
|
impl AssertSendSync for Error {}
|
||||||
|
impl fmt::Display for TimedOut {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("operation timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl StdError for TimedOut {}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::mem;
|
||||||
|
#[test]
|
||||||
|
fn error_size_of() {
|
||||||
|
assert_eq!(mem::size_of::< Error > (), mem::size_of::< usize > ());
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[test]
|
||||||
|
fn h2_reason_unknown() {
|
||||||
|
let closed = Error::new_closed();
|
||||||
|
assert_eq!(closed.h2_reason(), h2::Reason::INTERNAL_ERROR);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[test]
|
||||||
|
fn h2_reason_one_level() {
|
||||||
|
let body_err = Error::new_user_body(
|
||||||
|
h2::Error::from(h2::Reason::ENHANCE_YOUR_CALM),
|
||||||
|
);
|
||||||
|
assert_eq!(body_err.h2_reason(), h2::Reason::ENHANCE_YOUR_CALM);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[test]
|
||||||
|
fn h2_reason_nested() {
|
||||||
|
let recvd = Error::new_h2(h2::Error::from(h2::Reason::HTTP_1_1_REQUIRED));
|
||||||
|
let svc_err = Error::new_user_service(recvd);
|
||||||
|
assert_eq!(svc_err.h2_reason(), h2::Reason::HTTP_1_1_REQUIRED);
|
||||||
|
}
|
||||||
|
}
|
||||||
204
hyper/src/ext.rs
Normal file
204
hyper/src/ext.rs
Normal file
|
|
@ -0,0 +1,204 @@
|
||||||
|
//! HTTP extensions.
|
||||||
|
use bytes::Bytes;
|
||||||
|
#[cfg(any(feature = "http1", feature = "ffi"))]
|
||||||
|
use http::header::HeaderName;
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
use http::header::{IntoHeaderName, ValueIter};
|
||||||
|
use http::HeaderMap;
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
use std::fmt;
|
||||||
|
#[cfg(any(feature = "http1", feature = "ffi"))]
|
||||||
|
mod h1_reason_phrase;
|
||||||
|
#[cfg(any(feature = "http1", feature = "ffi"))]
|
||||||
|
pub(crate) use h1_reason_phrase::ReasonPhrase;
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
/// Represents the `:protocol` pseudo-header used by
|
||||||
|
/// the [Extended CONNECT Protocol].
|
||||||
|
///
|
||||||
|
/// [Extended CONNECT Protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
||||||
|
#[derive(Clone, Eq, PartialEq)]
|
||||||
|
pub(crate) struct Protocol {
|
||||||
|
inner: h2::ext::Protocol,
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
impl Protocol {
|
||||||
|
/// Converts a static string to a protocol name.
|
||||||
|
pub(crate) const fn from_static(value: &'static str) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: h2::ext::Protocol::from_static(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Returns a str representation of the header.
|
||||||
|
pub(crate) fn as_str(&self) -> &str {
|
||||||
|
self.inner.as_str()
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(crate) fn from_inner(inner: h2::ext::Protocol) -> Self {
|
||||||
|
Self { inner }
|
||||||
|
}
|
||||||
|
pub(crate) fn into_inner(self) -> h2::ext::Protocol {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
impl<'a> From<&'a str> for Protocol {
|
||||||
|
fn from(value: &'a str) -> Self {
|
||||||
|
Self {
|
||||||
|
inner: h2::ext::Protocol::from(value),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
impl AsRef<[u8]> for Protocol {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
self.inner.as_ref()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
impl fmt::Debug for Protocol {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
self.inner.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// A map from header names to their original casing as received in an HTTP message.
|
||||||
|
///
|
||||||
|
/// If an HTTP/1 response `res` is parsed on a connection whose option
|
||||||
|
/// [`http1_preserve_header_case`] was set to true and the response included
|
||||||
|
/// the following headers:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// x-Bread: Baguette
|
||||||
|
/// X-BREAD: Pain
|
||||||
|
/// x-bread: Ficelle
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// Then `res.extensions().get::<HeaderCaseMap>()` will return a map with:
|
||||||
|
///
|
||||||
|
/// ```ignore
|
||||||
|
/// HeaderCaseMap({
|
||||||
|
/// "x-bread": ["x-Bread", "X-BREAD", "x-bread"],
|
||||||
|
/// })
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// [`http1_preserve_header_case`]: /client/struct.Client.html#method.http1_preserve_header_case
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct HeaderCaseMap(HeaderMap<Bytes>);
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
impl HeaderCaseMap {
|
||||||
|
/// Returns a view of all spellings associated with that header name,
|
||||||
|
/// in the order they were found.
|
||||||
|
pub(crate) fn get_all<'a>(
|
||||||
|
&'a self,
|
||||||
|
name: &HeaderName,
|
||||||
|
) -> impl Iterator<Item = impl AsRef<[u8]> + 'a> + 'a {
|
||||||
|
self.get_all_internal(name).into_iter()
|
||||||
|
}
|
||||||
|
/// Returns a view of all spellings associated with that header name,
|
||||||
|
/// in the order they were found.
|
||||||
|
pub(crate) fn get_all_internal<'a>(
|
||||||
|
&'a self,
|
||||||
|
name: &HeaderName,
|
||||||
|
) -> ValueIter<'_, Bytes> {
|
||||||
|
self.0.get_all(name).into_iter()
|
||||||
|
}
|
||||||
|
pub(crate) fn default() -> Self {
|
||||||
|
Self(Default::default())
|
||||||
|
}
|
||||||
|
#[cfg(any(test, feature = "ffi"))]
|
||||||
|
pub(crate) fn insert(&mut self, name: HeaderName, orig: Bytes) {
|
||||||
|
self.0.insert(name, orig);
|
||||||
|
}
|
||||||
|
pub(crate) fn append<N>(&mut self, name: N, orig: Bytes)
|
||||||
|
where
|
||||||
|
N: IntoHeaderName,
|
||||||
|
{
|
||||||
|
self.0.append(name, orig);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
/// Hashmap<Headername, numheaders with that name>
|
||||||
|
pub(crate) struct OriginalHeaderOrder {
|
||||||
|
/// Stores how many entries a Headername maps to. This is used
|
||||||
|
/// for accounting.
|
||||||
|
num_entries: HashMap<HeaderName, usize>,
|
||||||
|
/// Stores the ordering of the headers. ex: `vec[i] = (headerName, idx)`,
|
||||||
|
/// The vector is ordered such that the ith element
|
||||||
|
/// represents the ith header that came in off the line.
|
||||||
|
/// The `HeaderName` and `idx` are then used elsewhere to index into
|
||||||
|
/// the multi map that stores the header values.
|
||||||
|
entry_order: Vec<(HeaderName, usize)>,
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "ffi"))]
|
||||||
|
impl OriginalHeaderOrder {
|
||||||
|
pub(crate) fn default() -> Self {
|
||||||
|
OriginalHeaderOrder {
|
||||||
|
num_entries: HashMap::new(),
|
||||||
|
entry_order: Vec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn insert(&mut self, name: HeaderName) {
|
||||||
|
if !self.num_entries.contains_key(&name) {
|
||||||
|
let idx = 0;
|
||||||
|
self.num_entries.insert(name.clone(), 1);
|
||||||
|
self.entry_order.push((name, idx));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn append<N>(&mut self, name: N)
|
||||||
|
where
|
||||||
|
N: IntoHeaderName + Into<HeaderName> + Clone,
|
||||||
|
{
|
||||||
|
let name: HeaderName = name.into();
|
||||||
|
let idx;
|
||||||
|
if self.num_entries.contains_key(&name) {
|
||||||
|
idx = self.num_entries[&name];
|
||||||
|
*self.num_entries.get_mut(&name).unwrap() += 1;
|
||||||
|
} else {
|
||||||
|
idx = 0;
|
||||||
|
self.num_entries.insert(name.clone(), 1);
|
||||||
|
}
|
||||||
|
self.entry_order.push((name, idx));
|
||||||
|
}
|
||||||
|
/// This returns an iterator that provides header names and indexes
|
||||||
|
/// in the original order received.
|
||||||
|
///
|
||||||
|
/// # Examples
|
||||||
|
/// ```no_run
|
||||||
|
/// use hyper::ext::OriginalHeaderOrder;
|
||||||
|
/// use hyper::header::{HeaderName, HeaderValue, HeaderMap};
|
||||||
|
///
|
||||||
|
/// let mut h_order = OriginalHeaderOrder::default();
|
||||||
|
/// let mut h_map = Headermap::new();
|
||||||
|
///
|
||||||
|
/// let name1 = b"Set-CookiE";
|
||||||
|
/// let value1 = b"a=b";
|
||||||
|
/// h_map.append(name1);
|
||||||
|
/// h_order.append(name1);
|
||||||
|
///
|
||||||
|
/// let name2 = b"Content-Encoding";
|
||||||
|
/// let value2 = b"gzip";
|
||||||
|
/// h_map.append(name2, value2);
|
||||||
|
/// h_order.append(name2);
|
||||||
|
///
|
||||||
|
/// let name3 = b"SET-COOKIE";
|
||||||
|
/// let value3 = b"c=d";
|
||||||
|
/// h_map.append(name3, value3);
|
||||||
|
/// h_order.append(name3)
|
||||||
|
///
|
||||||
|
/// let mut iter = h_order.get_in_order()
|
||||||
|
///
|
||||||
|
/// let (name, idx) = iter.next();
|
||||||
|
/// assert_eq!(b"a=b", h_map.get_all(name).nth(idx).unwrap());
|
||||||
|
///
|
||||||
|
/// let (name, idx) = iter.next();
|
||||||
|
/// assert_eq!(b"gzip", h_map.get_all(name).nth(idx).unwrap());
|
||||||
|
///
|
||||||
|
/// let (name, idx) = iter.next();
|
||||||
|
/// assert_eq!(b"c=d", h_map.get_all(name).nth(idx).unwrap());
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn get_in_order(&self) -> impl Iterator<Item = &(HeaderName, usize)> {
|
||||||
|
self.entry_order.iter()
|
||||||
|
}
|
||||||
|
}
|
||||||
182
hyper/src/ext/h1_reason_phrase.rs
Normal file
182
hyper/src/ext/h1_reason_phrase.rs
Normal file
|
|
@ -0,0 +1,182 @@
|
||||||
|
use std::convert::TryFrom;
|
||||||
|
use bytes::Bytes;
|
||||||
|
/// A reason phrase in an HTTP/1 response.
|
||||||
|
///
|
||||||
|
/// # Clients
|
||||||
|
///
|
||||||
|
/// For clients, a `ReasonPhrase` will be present in the extensions of the `http::Response` returned
|
||||||
|
/// for a request if the reason phrase is different from the canonical reason phrase for the
|
||||||
|
/// response's status code. For example, if a server returns `HTTP/1.1 200 Awesome`, the
|
||||||
|
/// `ReasonPhrase` will be present and contain `Awesome`, but if a server returns `HTTP/1.1 200 OK`,
|
||||||
|
/// the response will not contain a `ReasonPhrase`.
|
||||||
|
///
|
||||||
|
/// ```no_run
|
||||||
|
/// # #[cfg(all(feature = "tcp", feature = "client", feature = "http1"))]
|
||||||
|
/// # async fn fake_fetch() -> hyper::Result<()> {
|
||||||
|
/// use hyper::{Client, Uri};
|
||||||
|
/// use hyper::ext::ReasonPhrase;
|
||||||
|
///
|
||||||
|
/// let res = Client::new().get(Uri::from_static("http://example.com/non_canonical_reason")).await?;
|
||||||
|
///
|
||||||
|
/// // Print out the non-canonical reason phrase, if it has one...
|
||||||
|
/// if let Some(reason) = res.extensions().get::<ReasonPhrase>() {
|
||||||
|
/// println!("non-canonical reason: {}", std::str::from_utf8(reason.as_bytes()).unwrap());
|
||||||
|
/// }
|
||||||
|
/// # Ok(())
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// # Servers
|
||||||
|
///
|
||||||
|
/// When a `ReasonPhrase` is present in the extensions of the `http::Response` written by a server,
|
||||||
|
/// its contents will be written in place of the canonical reason phrase when responding via HTTP/1.
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||||
|
pub(crate) struct ReasonPhrase(Bytes);
|
||||||
|
impl ReasonPhrase {
|
||||||
|
/// Gets the reason phrase as bytes.
|
||||||
|
pub(crate) fn as_bytes(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
/// Converts a static byte slice to a reason phrase.
|
||||||
|
pub(crate) fn from_static(reason: &'static [u8]) -> Self {
|
||||||
|
if find_invalid_byte(reason).is_some() {
|
||||||
|
panic!("invalid byte in static reason phrase");
|
||||||
|
}
|
||||||
|
Self(Bytes::from_static(reason))
|
||||||
|
}
|
||||||
|
/// Converts a `Bytes` directly into a `ReasonPhrase` without validating.
|
||||||
|
///
|
||||||
|
/// Use with care; invalid bytes in a reason phrase can cause serious security problems if
|
||||||
|
/// emitted in a response.
|
||||||
|
pub(crate) unsafe fn from_bytes_unchecked(reason: Bytes) -> Self {
|
||||||
|
Self(reason)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<&[u8]> for ReasonPhrase {
|
||||||
|
type Error = InvalidReasonPhrase;
|
||||||
|
fn try_from(reason: &[u8]) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(bad_byte) = find_invalid_byte(reason) {
|
||||||
|
Err(InvalidReasonPhrase { bad_byte })
|
||||||
|
} else {
|
||||||
|
Ok(Self(Bytes::copy_from_slice(reason)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<Vec<u8>> for ReasonPhrase {
|
||||||
|
type Error = InvalidReasonPhrase;
|
||||||
|
fn try_from(reason: Vec<u8>) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(bad_byte) = find_invalid_byte(&reason) {
|
||||||
|
Err(InvalidReasonPhrase { bad_byte })
|
||||||
|
} else {
|
||||||
|
Ok(Self(Bytes::from(reason)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<String> for ReasonPhrase {
|
||||||
|
type Error = InvalidReasonPhrase;
|
||||||
|
fn try_from(reason: String) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(bad_byte) = find_invalid_byte(reason.as_bytes()) {
|
||||||
|
Err(InvalidReasonPhrase { bad_byte })
|
||||||
|
} else {
|
||||||
|
Ok(Self(Bytes::from(reason)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl TryFrom<Bytes> for ReasonPhrase {
|
||||||
|
type Error = InvalidReasonPhrase;
|
||||||
|
fn try_from(reason: Bytes) -> Result<Self, Self::Error> {
|
||||||
|
if let Some(bad_byte) = find_invalid_byte(&reason) {
|
||||||
|
Err(InvalidReasonPhrase { bad_byte })
|
||||||
|
} else {
|
||||||
|
Ok(Self(reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Into<Bytes> for ReasonPhrase {
|
||||||
|
fn into(self) -> Bytes {
|
||||||
|
self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsRef<[u8]> for ReasonPhrase {
|
||||||
|
fn as_ref(&self) -> &[u8] {
|
||||||
|
&self.0
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Error indicating an invalid byte when constructing a `ReasonPhrase`.
|
||||||
|
///
|
||||||
|
/// See [the spec][spec] for details on allowed bytes.
|
||||||
|
///
|
||||||
|
/// [spec]: https://httpwg.org/http-core/draft-ietf-httpbis-messaging-latest.html#rfc.section.4.p.7
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct InvalidReasonPhrase {
|
||||||
|
bad_byte: u8,
|
||||||
|
}
|
||||||
|
impl std::fmt::Display for InvalidReasonPhrase {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
write!(f, "Invalid byte in reason phrase: {}", self.bad_byte)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl std::error::Error for InvalidReasonPhrase {}
|
||||||
|
const fn is_valid_byte(b: u8) -> bool {
|
||||||
|
const fn is_vchar(b: u8) -> bool {
|
||||||
|
0x21 <= b && b <= 0x7E
|
||||||
|
}
|
||||||
|
#[allow(unused_comparisons)]
|
||||||
|
const fn is_obs_text(b: u8) -> bool {
|
||||||
|
0x80 <= b && b <= 0xFF
|
||||||
|
}
|
||||||
|
b == b'\t' || b == b' ' || is_vchar(b) || is_obs_text(b)
|
||||||
|
}
|
||||||
|
const fn find_invalid_byte(bytes: &[u8]) -> Option<u8> {
|
||||||
|
let mut i = 0;
|
||||||
|
while i < bytes.len() {
|
||||||
|
let b = bytes[i];
|
||||||
|
if !is_valid_byte(b) {
|
||||||
|
return Some(b);
|
||||||
|
}
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn basic_valid() {
|
||||||
|
const PHRASE: &'static [u8] = b"OK";
|
||||||
|
assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
|
||||||
|
assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn empty_valid() {
|
||||||
|
const PHRASE: &'static [u8] = b"";
|
||||||
|
assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
|
||||||
|
assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn obs_text_valid() {
|
||||||
|
const PHRASE: &'static [u8] = b"hyp\xe9r";
|
||||||
|
assert_eq!(ReasonPhrase::from_static(PHRASE).as_bytes(), PHRASE);
|
||||||
|
assert_eq!(ReasonPhrase::try_from(PHRASE).unwrap().as_bytes(), PHRASE);
|
||||||
|
}
|
||||||
|
const NEWLINE_PHRASE: &'static [u8] = b"hyp\ner";
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn newline_invalid_panic() {
|
||||||
|
ReasonPhrase::from_static(NEWLINE_PHRASE);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn newline_invalid_err() {
|
||||||
|
assert!(ReasonPhrase::try_from(NEWLINE_PHRASE).is_err());
|
||||||
|
}
|
||||||
|
const CR_PHRASE: &'static [u8] = b"hyp\rer";
|
||||||
|
#[test]
|
||||||
|
#[should_panic]
|
||||||
|
fn cr_invalid_panic() {
|
||||||
|
ReasonPhrase::from_static(CR_PHRASE);
|
||||||
|
}
|
||||||
|
#[test]
|
||||||
|
fn cr_invalid_err() {
|
||||||
|
assert!(ReasonPhrase::try_from(CR_PHRASE).is_err());
|
||||||
|
}
|
||||||
|
}
|
||||||
174
hyper/src/ffi/body.rs
Normal file
174
hyper/src/ffi/body.rs
Normal file
|
|
@ -0,0 +1,174 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::mem::ManuallyDrop;
|
||||||
|
use std::ptr;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use http::HeaderMap;
|
||||||
|
use libc::{c_int, size_t};
|
||||||
|
use super::task::{hyper_context, hyper_task, hyper_task_return_type, AsTaskType};
|
||||||
|
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
|
||||||
|
use crate::body::{Body, Bytes, HttpBody as _};
|
||||||
|
/// A streaming HTTP body.
|
||||||
|
pub(crate) struct hyper_body(pub(super) Body);
|
||||||
|
/// A buffer of bytes that is sent or received on a `hyper_body`.
|
||||||
|
pub(crate) struct hyper_buf(pub(crate) Bytes);
|
||||||
|
pub(crate) struct UserBody {
|
||||||
|
data_func: hyper_body_data_callback,
|
||||||
|
userdata: *mut c_void,
|
||||||
|
}
|
||||||
|
type hyper_body_foreach_callback = extern "C" fn(*mut c_void, *const hyper_buf) -> c_int;
|
||||||
|
type hyper_body_data_callback = extern "C" fn(
|
||||||
|
*mut c_void,
|
||||||
|
*mut hyper_context<'_>,
|
||||||
|
*mut *mut hyper_buf,
|
||||||
|
) -> c_int;
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Create a new \"empty\" body."] #[doc = ""] #[doc =
|
||||||
|
" If not configured, this body acts as an empty payload."] fn hyper_body_new() -> *
|
||||||
|
mut hyper_body { Box::into_raw(Box::new(hyper_body(Body::empty()))) } ?=
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free a `hyper_body *`."] fn hyper_body_free(body : * mut hyper_body) {
|
||||||
|
drop(non_null!(Box::from_raw(body) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Return a task that will poll the body for the next buffer of data."] #[doc
|
||||||
|
= ""] #[doc = " The task value may have different types depending on the outcome:"]
|
||||||
|
#[doc = ""] #[doc = " - `HYPER_TASK_BUF`: Success, and more data was received."]
|
||||||
|
#[doc = " - `HYPER_TASK_ERROR`: An error retrieving the data."] #[doc =
|
||||||
|
" - `HYPER_TASK_EMPTY`: The body has finished streaming data."] #[doc = ""] #[doc =
|
||||||
|
" This does not consume the `hyper_body *`, so it may be used to again."] #[doc =
|
||||||
|
" However, it MUST NOT be used or freed until the related task completes."] fn
|
||||||
|
hyper_body_data(body : * mut hyper_body) -> * mut hyper_task { let mut body =
|
||||||
|
ManuallyDrop::new(non_null!(Box::from_raw(body) ?= ptr::null_mut()));
|
||||||
|
Box::into_raw(hyper_task::boxed(async move { body.0.data().await.map(| res | res
|
||||||
|
.map(hyper_buf)) })) } ?= ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Return a task that will poll the body and execute the callback with each"]
|
||||||
|
#[doc = " body chunk that is received."] #[doc = ""] #[doc =
|
||||||
|
" The `hyper_buf` pointer is only a borrowed reference, it cannot live outside"]
|
||||||
|
#[doc = " the execution of the callback. You must make a copy to retain it."] #[doc =
|
||||||
|
""] #[doc =
|
||||||
|
" The callback should return `HYPER_ITER_CONTINUE` to continue iterating"] #[doc =
|
||||||
|
" chunks as they are received, or `HYPER_ITER_BREAK` to cancel."] #[doc = ""] #[doc =
|
||||||
|
" This will consume the `hyper_body *`, you shouldn't use it anymore or free it."] fn
|
||||||
|
hyper_body_foreach(body : * mut hyper_body, func : hyper_body_foreach_callback,
|
||||||
|
userdata : * mut c_void) -> * mut hyper_task { let mut body =
|
||||||
|
non_null!(Box::from_raw(body) ?= ptr::null_mut()); let userdata =
|
||||||
|
UserDataPointer(userdata); Box::into_raw(hyper_task::boxed(async move { while let
|
||||||
|
Some(item) = body.0.data().await { let chunk = item ?; if HYPER_ITER_CONTINUE !=
|
||||||
|
func(userdata.0, & hyper_buf(chunk)) { return Err(crate
|
||||||
|
::Error::new_user_aborted_by_callback()); } } Ok(()) })) } ?= ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set userdata on this body, which will be passed to callback functions."] fn
|
||||||
|
hyper_body_set_userdata(body : * mut hyper_body, userdata : * mut c_void) { let b =
|
||||||
|
non_null!(& mut * body ?= ()); b.0.as_ffi_mut().userdata = userdata; }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the data callback for this body."] #[doc = ""] #[doc =
|
||||||
|
" The callback is called each time hyper needs to send more data for the"] #[doc =
|
||||||
|
" body. It is passed the value from `hyper_body_set_userdata`."] #[doc = ""] #[doc =
|
||||||
|
" If there is data available, the `hyper_buf **` argument should be set"] #[doc =
|
||||||
|
" to a `hyper_buf *` containing the data, and `HYPER_POLL_READY` should"] #[doc =
|
||||||
|
" be returned."] #[doc = ""] #[doc =
|
||||||
|
" Returning `HYPER_POLL_READY` while the `hyper_buf **` argument points"] #[doc =
|
||||||
|
" to `NULL` will indicate the body has completed all data."] #[doc = ""] #[doc =
|
||||||
|
" If there is more data to send, but it isn't yet available, a"] #[doc =
|
||||||
|
" `hyper_waker` should be saved from the `hyper_context *` argument, and"] #[doc =
|
||||||
|
" `HYPER_POLL_PENDING` should be returned. You must wake the saved waker"] #[doc =
|
||||||
|
" to signal the task when data is available."] #[doc = ""] #[doc =
|
||||||
|
" If some error has occurred, you can return `HYPER_POLL_ERROR` to abort"] #[doc =
|
||||||
|
" the body."] fn hyper_body_set_data_func(body : * mut hyper_body, func :
|
||||||
|
hyper_body_data_callback) { let b = non_null! { & mut * body ?= () }; b.0
|
||||||
|
.as_ffi_mut().data_func = func; }
|
||||||
|
}
|
||||||
|
impl UserBody {
|
||||||
|
pub(crate) fn new() -> UserBody {
|
||||||
|
UserBody {
|
||||||
|
data_func: data_noop,
|
||||||
|
userdata: std::ptr::null_mut(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn poll_data(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Option<crate::Result<Bytes>>> {
|
||||||
|
let mut out = std::ptr::null_mut();
|
||||||
|
match (self.data_func)(self.userdata, hyper_context::wrap(cx), &mut out) {
|
||||||
|
super::task::HYPER_POLL_READY => {
|
||||||
|
if out.is_null() {
|
||||||
|
Poll::Ready(None)
|
||||||
|
} else {
|
||||||
|
let buf = unsafe { Box::from_raw(out) };
|
||||||
|
Poll::Ready(Some(Ok(buf.0)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super::task::HYPER_POLL_PENDING => Poll::Pending,
|
||||||
|
super::task::HYPER_POLL_ERROR => {
|
||||||
|
Poll::Ready(Some(Err(crate::Error::new_body_write_aborted())))
|
||||||
|
}
|
||||||
|
unexpected => {
|
||||||
|
Poll::Ready(
|
||||||
|
Some(
|
||||||
|
Err(
|
||||||
|
crate::Error::new_body_write(
|
||||||
|
format!(
|
||||||
|
"unexpected hyper_body_data_func return code {}", unexpected
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn poll_trailers(
|
||||||
|
&mut self,
|
||||||
|
_cx: &mut Context<'_>,
|
||||||
|
) -> Poll<crate::Result<Option<HeaderMap>>> {
|
||||||
|
Poll::Ready(Ok(None))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// cbindgen:ignore
|
||||||
|
extern "C" fn data_noop(
|
||||||
|
_userdata: *mut c_void,
|
||||||
|
_: *mut hyper_context<'_>,
|
||||||
|
_: *mut *mut hyper_buf,
|
||||||
|
) -> c_int {
|
||||||
|
super::task::HYPER_POLL_READY
|
||||||
|
}
|
||||||
|
unsafe impl Send for UserBody {}
|
||||||
|
unsafe impl Sync for UserBody {}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Create a new `hyper_buf *` by copying the provided bytes."] #[doc = ""]
|
||||||
|
#[doc = " This makes an owned copy of the bytes, so the `buf` argument can be"] #[doc
|
||||||
|
= " freed or changed afterwards."] #[doc = ""] #[doc =
|
||||||
|
" This returns `NULL` if allocating a new buffer fails."] fn hyper_buf_copy(buf : *
|
||||||
|
const u8, len : size_t) -> * mut hyper_buf { let slice = unsafe {
|
||||||
|
std::slice::from_raw_parts(buf, len) };
|
||||||
|
Box::into_raw(Box::new(hyper_buf(Bytes::copy_from_slice(slice)))) } ?=
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get a pointer to the bytes in this buffer."] #[doc = ""] #[doc =
|
||||||
|
" This should be used in conjunction with `hyper_buf_len` to get the length"] #[doc =
|
||||||
|
" of the bytes data."] #[doc = ""] #[doc =
|
||||||
|
" This pointer is borrowed data, and not valid once the `hyper_buf` is"] #[doc =
|
||||||
|
" consumed/freed."] fn hyper_buf_bytes(buf : * const hyper_buf) -> * const u8 {
|
||||||
|
unsafe { (* buf).0.as_ptr() } } ?= ptr::null()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get the length of the bytes this buffer contains."] fn hyper_buf_len(buf :
|
||||||
|
* const hyper_buf) -> size_t { unsafe { (* buf).0.len() } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free this buffer."] fn hyper_buf_free(buf : * mut hyper_buf) { drop(unsafe
|
||||||
|
{ Box::from_raw(buf) }); }
|
||||||
|
}
|
||||||
|
unsafe impl AsTaskType for hyper_buf {
|
||||||
|
fn as_task_type(&self) -> hyper_task_return_type {
|
||||||
|
hyper_task_return_type::HYPER_TASK_BUF
|
||||||
|
}
|
||||||
|
}
|
||||||
111
hyper/src/ffi/client.rs
Normal file
111
hyper/src/ffi/client.rs
Normal file
|
|
@ -0,0 +1,111 @@
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::Arc;
|
||||||
|
use libc::c_int;
|
||||||
|
use crate::client::conn;
|
||||||
|
use crate::rt::Executor as _;
|
||||||
|
use super::error::hyper_code;
|
||||||
|
use super::http_types::{hyper_request, hyper_response};
|
||||||
|
use super::io::hyper_io;
|
||||||
|
use super::task::{
|
||||||
|
hyper_executor, hyper_task, hyper_task_return_type, AsTaskType, WeakExec,
|
||||||
|
};
|
||||||
|
/// An options builder to configure an HTTP client connection.
|
||||||
|
pub(crate) struct hyper_clientconn_options {
|
||||||
|
builder: conn::Builder,
|
||||||
|
/// Use a `Weak` to prevent cycles.
|
||||||
|
exec: WeakExec,
|
||||||
|
}
|
||||||
|
/// An HTTP client connection handle.
|
||||||
|
///
|
||||||
|
/// These are used to send a request on a single connection. It's possible to
|
||||||
|
/// send multiple requests on a single connection, such as when HTTP/1
|
||||||
|
/// keep-alive or HTTP/2 is used.
|
||||||
|
pub(crate) struct hyper_clientconn {
|
||||||
|
tx: conn::SendRequest<crate::Body>,
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc =
|
||||||
|
" Starts an HTTP client connection handshake using the provided IO transport"] #[doc
|
||||||
|
= " and options."] #[doc = ""] #[doc =
|
||||||
|
" Both the `io` and the `options` are consumed in this function call."] #[doc = ""]
|
||||||
|
#[doc = " The returned `hyper_task *` must be polled with an executor until the"]
|
||||||
|
#[doc = " handshake completes, at which point the value can be taken."] fn
|
||||||
|
hyper_clientconn_handshake(io : * mut hyper_io, options : * mut
|
||||||
|
hyper_clientconn_options) -> * mut hyper_task { let options = non_null! {
|
||||||
|
Box::from_raw(options) ?= ptr::null_mut() }; let io = non_null! { Box::from_raw(io)
|
||||||
|
?= ptr::null_mut() }; Box::into_raw(hyper_task::boxed(async move { options.builder
|
||||||
|
.handshake::< _, crate ::Body > (io).await.map(| (tx, conn) | { options.exec
|
||||||
|
.execute(Box::pin(async move { let _ = conn.await; })); hyper_clientconn { tx } })
|
||||||
|
})) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Send a request on the client connection."] #[doc = ""] #[doc =
|
||||||
|
" Returns a task that needs to be polled until it is ready. When ready, the"] #[doc =
|
||||||
|
" task yields a `hyper_response *`."] fn hyper_clientconn_send(conn : * mut
|
||||||
|
hyper_clientconn, req : * mut hyper_request) -> * mut hyper_task { let mut req =
|
||||||
|
non_null! { Box::from_raw(req) ?= ptr::null_mut() }; req.finalize_request(); let fut
|
||||||
|
= non_null! { & mut * conn ?= ptr::null_mut() } .tx.send_request(req.0); let fut =
|
||||||
|
async move { fut.await.map(hyper_response::wrap) };
|
||||||
|
Box::into_raw(hyper_task::boxed(fut)) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free a `hyper_clientconn *`."] fn hyper_clientconn_free(conn : * mut
|
||||||
|
hyper_clientconn) { drop(non_null! { Box::from_raw(conn) ?= () }); }
|
||||||
|
}
|
||||||
|
unsafe impl AsTaskType for hyper_clientconn {
|
||||||
|
fn as_task_type(&self) -> hyper_task_return_type {
|
||||||
|
hyper_task_return_type::HYPER_TASK_CLIENTCONN
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Creates a new set of HTTP clientconn options to be used in a handshake."]
|
||||||
|
fn hyper_clientconn_options_new() -> * mut hyper_clientconn_options { let builder =
|
||||||
|
conn::Builder::new(); Box::into_raw(Box::new(hyper_clientconn_options { builder, exec
|
||||||
|
: WeakExec::new(), })) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the whether or not header case is preserved."] #[doc = ""] #[doc =
|
||||||
|
" Pass `0` to allow lowercase normalization (default), `1` to retain original case."]
|
||||||
|
fn hyper_clientconn_options_set_preserve_header_case(opts : * mut
|
||||||
|
hyper_clientconn_options, enabled : c_int) { let opts = non_null! { & mut * opts ?=
|
||||||
|
() }; opts.builder.http1_preserve_header_case(enabled != 0); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the whether or not header order is preserved."] #[doc = ""] #[doc =
|
||||||
|
" Pass `0` to allow reordering (default), `1` to retain original ordering."] fn
|
||||||
|
hyper_clientconn_options_set_preserve_header_order(opts : * mut
|
||||||
|
hyper_clientconn_options, enabled : c_int) { let opts = non_null! { & mut * opts ?=
|
||||||
|
() }; opts.builder.http1_preserve_header_order(enabled != 0); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free a `hyper_clientconn_options *`."] fn
|
||||||
|
hyper_clientconn_options_free(opts : * mut hyper_clientconn_options) { drop(non_null!
|
||||||
|
{ Box::from_raw(opts) ?= () }); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the client background task executor."] #[doc = ""] #[doc =
|
||||||
|
" This does not consume the `options` or the `exec`."] fn
|
||||||
|
hyper_clientconn_options_exec(opts : * mut hyper_clientconn_options, exec : * const
|
||||||
|
hyper_executor) { let opts = non_null! { & mut * opts ?= () }; let exec = non_null! {
|
||||||
|
Arc::from_raw(exec) ?= () }; let weak_exec = hyper_executor::downgrade(& exec);
|
||||||
|
std::mem::forget(exec); opts.builder.executor(weak_exec.clone()); opts.exec =
|
||||||
|
weak_exec; }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the whether to use HTTP2."] #[doc = ""] #[doc =
|
||||||
|
" Pass `0` to disable, `1` to enable."] fn hyper_clientconn_options_http2(opts : *
|
||||||
|
mut hyper_clientconn_options, enabled : c_int) -> hyper_code { #[cfg(feature =
|
||||||
|
"http2")] { let opts = non_null! { & mut * opts ?= hyper_code::HYPERE_INVALID_ARG };
|
||||||
|
opts.builder.http2_only(enabled != 0); hyper_code::HYPERE_OK } #[cfg(not(feature =
|
||||||
|
"http2"))] { drop(opts); drop(enabled); hyper_code::HYPERE_FEATURE_NOT_ENABLED } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the whether to include a copy of the raw headers in responses"] #[doc =
|
||||||
|
" received on this connection."] #[doc = ""] #[doc =
|
||||||
|
" Pass `0` to disable, `1` to enable."] #[doc = ""] #[doc =
|
||||||
|
" If enabled, see `hyper_response_headers_raw()` for usage."] fn
|
||||||
|
hyper_clientconn_options_headers_raw(opts : * mut hyper_clientconn_options, enabled :
|
||||||
|
c_int) -> hyper_code { let opts = non_null! { & mut * opts ?=
|
||||||
|
hyper_code::HYPERE_INVALID_ARG }; opts.builder.http1_headers_raw(enabled != 0);
|
||||||
|
hyper_code::HYPERE_OK }
|
||||||
|
}
|
||||||
63
hyper/src/ffi/error.rs
Normal file
63
hyper/src/ffi/error.rs
Normal file
|
|
@ -0,0 +1,63 @@
|
||||||
|
use libc::size_t;
|
||||||
|
/// A more detailed error object returned by some hyper functions.
|
||||||
|
pub(crate) struct hyper_error(crate::Error);
|
||||||
|
/// A return code for many of hyper's methods.
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) enum hyper_code {
|
||||||
|
/// All is well.
|
||||||
|
HYPERE_OK,
|
||||||
|
/// General error, details in the `hyper_error *`.
|
||||||
|
HYPERE_ERROR,
|
||||||
|
/// A function argument was invalid.
|
||||||
|
HYPERE_INVALID_ARG,
|
||||||
|
/// The IO transport returned an EOF when one wasn't expected.
|
||||||
|
///
|
||||||
|
/// This typically means an HTTP request or response was expected, but the
|
||||||
|
/// connection closed cleanly without sending (all of) it.
|
||||||
|
HYPERE_UNEXPECTED_EOF,
|
||||||
|
/// Aborted by a user supplied callback.
|
||||||
|
HYPERE_ABORTED_BY_CALLBACK,
|
||||||
|
/// An optional hyper feature was not enabled.
|
||||||
|
#[cfg_attr(feature = "http2", allow(unused))]
|
||||||
|
HYPERE_FEATURE_NOT_ENABLED,
|
||||||
|
/// The peer sent an HTTP message that could not be parsed.
|
||||||
|
HYPERE_INVALID_PEER_MESSAGE,
|
||||||
|
}
|
||||||
|
impl hyper_error {
|
||||||
|
fn code(&self) -> hyper_code {
|
||||||
|
use crate::error::Kind as ErrorKind;
|
||||||
|
use crate::error::User;
|
||||||
|
match self.0.kind() {
|
||||||
|
ErrorKind::Parse(_) => hyper_code::HYPERE_INVALID_PEER_MESSAGE,
|
||||||
|
ErrorKind::IncompleteMessage => hyper_code::HYPERE_UNEXPECTED_EOF,
|
||||||
|
ErrorKind::User(User::AbortedByCallback) => {
|
||||||
|
hyper_code::HYPERE_ABORTED_BY_CALLBACK
|
||||||
|
}
|
||||||
|
_ => hyper_code::HYPERE_ERROR,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn print_to(&self, dst: &mut [u8]) -> usize {
|
||||||
|
use std::io::Write;
|
||||||
|
let mut dst = std::io::Cursor::new(dst);
|
||||||
|
let _ = write!(dst, "{}", & self.0);
|
||||||
|
dst.position() as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Frees a `hyper_error`."] fn hyper_error_free(err : * mut hyper_error) {
|
||||||
|
drop(non_null!(Box::from_raw(err) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get an equivalent `hyper_code` from this error."] fn hyper_error_code(err :
|
||||||
|
* const hyper_error) -> hyper_code { non_null!(&* err ?=
|
||||||
|
hyper_code::HYPERE_INVALID_ARG) .code() }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Print the details of this error to a buffer."] #[doc = ""] #[doc =
|
||||||
|
" The `dst_len` value must be the maximum length that the buffer can"] #[doc =
|
||||||
|
" store."] #[doc = ""] #[doc =
|
||||||
|
" The return value is number of bytes that were written to `dst`."] fn
|
||||||
|
hyper_error_print(err : * const hyper_error, dst : * mut u8, dst_len : size_t) ->
|
||||||
|
size_t { let dst = unsafe { std::slice::from_raw_parts_mut(dst, dst_len) };
|
||||||
|
non_null!(&* err ?= 0) .print_to(dst) }
|
||||||
|
}
|
||||||
443
hyper/src/ffi/http_types.rs
Normal file
443
hyper/src/ffi/http_types.rs
Normal file
|
|
@ -0,0 +1,443 @@
|
||||||
|
use bytes::Bytes;
|
||||||
|
use libc::{c_int, size_t};
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use super::body::{hyper_body, hyper_buf};
|
||||||
|
use super::error::hyper_code;
|
||||||
|
use super::task::{hyper_task_return_type, AsTaskType};
|
||||||
|
use super::{UserDataPointer, HYPER_ITER_CONTINUE};
|
||||||
|
use crate::ext::{HeaderCaseMap, OriginalHeaderOrder, ReasonPhrase};
|
||||||
|
use crate::header::{HeaderName, HeaderValue};
|
||||||
|
use crate::{Body, HeaderMap, Method, Request, Response, Uri};
|
||||||
|
/// An HTTP request.
|
||||||
|
pub(crate) struct hyper_request(pub(super) Request<Body>);
|
||||||
|
/// An HTTP response.
|
||||||
|
pub(crate) struct hyper_response(pub(super) Response<Body>);
|
||||||
|
/// An HTTP header map.
|
||||||
|
///
|
||||||
|
/// These can be part of a request or response.
|
||||||
|
pub(crate) struct hyper_headers {
|
||||||
|
pub(super) headers: HeaderMap,
|
||||||
|
orig_casing: HeaderCaseMap,
|
||||||
|
orig_order: OriginalHeaderOrder,
|
||||||
|
}
|
||||||
|
pub(crate) struct RawHeaders(pub(crate) hyper_buf);
|
||||||
|
pub(crate) struct OnInformational {
|
||||||
|
func: hyper_request_on_informational_callback,
|
||||||
|
data: UserDataPointer,
|
||||||
|
}
|
||||||
|
type hyper_request_on_informational_callback = extern "C" fn(
|
||||||
|
*mut c_void,
|
||||||
|
*mut hyper_response,
|
||||||
|
);
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Construct a new HTTP request."] fn hyper_request_new() -> * mut
|
||||||
|
hyper_request { Box::into_raw(Box::new(hyper_request(Request::new(Body::empty())))) }
|
||||||
|
?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free an HTTP request if not going to send it on a client."] fn
|
||||||
|
hyper_request_free(req : * mut hyper_request) { drop(non_null!(Box::from_raw(req) ?=
|
||||||
|
())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the HTTP Method of the request."] fn hyper_request_set_method(req : *
|
||||||
|
mut hyper_request, method : * const u8, method_len : size_t) -> hyper_code { let
|
||||||
|
bytes = unsafe { std::slice::from_raw_parts(method, method_len as usize) }; let req =
|
||||||
|
non_null!(& mut * req ?= hyper_code::HYPERE_INVALID_ARG); match
|
||||||
|
Method::from_bytes(bytes) { Ok(m) => { * req.0.method_mut() = m;
|
||||||
|
hyper_code::HYPERE_OK }, Err(_) => { hyper_code::HYPERE_INVALID_ARG } } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the URI of the request."] #[doc = ""] #[doc =
|
||||||
|
" The request's URI is best described as the `request-target` from the RFCs. So in HTTP/1,"]
|
||||||
|
#[doc =
|
||||||
|
" whatever is set will get sent as-is in the first line (GET $uri HTTP/1.1). It"]
|
||||||
|
#[doc =
|
||||||
|
" supports the 4 defined variants, origin-form, absolute-form, authority-form, and"]
|
||||||
|
#[doc = " asterisk-form."] #[doc = ""] #[doc =
|
||||||
|
" The underlying type was built to efficiently support HTTP/2 where the request-target is"]
|
||||||
|
#[doc =
|
||||||
|
" split over :scheme, :authority, and :path. As such, each part can be set explicitly, or the"]
|
||||||
|
#[doc =
|
||||||
|
" type can parse a single contiguous string and if a scheme is found, that slot is \"set\". If"]
|
||||||
|
#[doc =
|
||||||
|
" the string just starts with a path, only the path portion is set. All pseudo headers that"]
|
||||||
|
#[doc = " have been parsed/set are sent when the connection type is HTTP/2."] #[doc =
|
||||||
|
""] #[doc = " To set each slot explicitly, use `hyper_request_set_uri_parts`."] fn
|
||||||
|
hyper_request_set_uri(req : * mut hyper_request, uri : * const u8, uri_len : size_t)
|
||||||
|
-> hyper_code { let bytes = unsafe { std::slice::from_raw_parts(uri, uri_len as
|
||||||
|
usize) }; let req = non_null!(& mut * req ?= hyper_code::HYPERE_INVALID_ARG); match
|
||||||
|
Uri::from_maybe_shared(bytes) { Ok(u) => { * req.0.uri_mut() = u;
|
||||||
|
hyper_code::HYPERE_OK }, Err(_) => { hyper_code::HYPERE_INVALID_ARG } } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the URI of the request with separate scheme, authority, and"] #[doc =
|
||||||
|
" path/query strings."] #[doc = ""] #[doc =
|
||||||
|
" Each of `scheme`, `authority`, and `path_and_query` should either be"] #[doc =
|
||||||
|
" null, to skip providing a component, or point to a UTF-8 encoded"] #[doc =
|
||||||
|
" string. If any string pointer argument is non-null, its corresponding"] #[doc =
|
||||||
|
" `len` parameter must be set to the string's length."] fn
|
||||||
|
hyper_request_set_uri_parts(req : * mut hyper_request, scheme : * const u8,
|
||||||
|
scheme_len : size_t, authority : * const u8, authority_len : size_t, path_and_query :
|
||||||
|
* const u8, path_and_query_len : size_t) -> hyper_code { let mut builder =
|
||||||
|
Uri::builder(); if ! scheme.is_null() { let scheme_bytes = unsafe {
|
||||||
|
std::slice::from_raw_parts(scheme, scheme_len as usize) }; builder = builder
|
||||||
|
.scheme(scheme_bytes); } if ! authority.is_null() { let authority_bytes = unsafe {
|
||||||
|
std::slice::from_raw_parts(authority, authority_len as usize) }; builder = builder
|
||||||
|
.authority(authority_bytes); } if ! path_and_query.is_null() { let
|
||||||
|
path_and_query_bytes = unsafe { std::slice::from_raw_parts(path_and_query,
|
||||||
|
path_and_query_len as usize) }; builder = builder
|
||||||
|
.path_and_query(path_and_query_bytes); } match builder.build() { Ok(u) => { * unsafe
|
||||||
|
{ & mut * req } .0.uri_mut() = u; hyper_code::HYPERE_OK }, Err(_) => {
|
||||||
|
hyper_code::HYPERE_INVALID_ARG } } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the preferred HTTP version of the request."] #[doc = ""] #[doc =
|
||||||
|
" The version value should be one of the `HYPER_HTTP_VERSION_` constants."] #[doc =
|
||||||
|
""] #[doc = " Note that this won't change the major HTTP version of the connection,"]
|
||||||
|
#[doc = " since that is determined at the handshake step."] fn
|
||||||
|
hyper_request_set_version(req : * mut hyper_request, version : c_int) -> hyper_code {
|
||||||
|
use http::Version; let req = non_null!(& mut * req ?=
|
||||||
|
hyper_code::HYPERE_INVALID_ARG); * req.0.version_mut() = match version {
|
||||||
|
super::HYPER_HTTP_VERSION_NONE => Version::HTTP_11, super::HYPER_HTTP_VERSION_1_0 =>
|
||||||
|
Version::HTTP_10, super::HYPER_HTTP_VERSION_1_1 => Version::HTTP_11,
|
||||||
|
super::HYPER_HTTP_VERSION_2 => Version::HTTP_2, _ => { return
|
||||||
|
hyper_code::HYPERE_INVALID_ARG; } }; hyper_code::HYPERE_OK }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Gets a reference to the HTTP headers of this request"] #[doc = ""] #[doc =
|
||||||
|
" This is not an owned reference, so it should not be accessed after the"] #[doc =
|
||||||
|
" `hyper_request` has been consumed."] fn hyper_request_headers(req : * mut
|
||||||
|
hyper_request) -> * mut hyper_headers { hyper_headers::get_or_default(unsafe { & mut
|
||||||
|
* req } .0.extensions_mut()) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the body of the request."] #[doc = ""] #[doc =
|
||||||
|
" The default is an empty body."] #[doc = ""] #[doc =
|
||||||
|
" This takes ownership of the `hyper_body *`, you must not use it or"] #[doc =
|
||||||
|
" free it after setting it on the request."] fn hyper_request_set_body(req : * mut
|
||||||
|
hyper_request, body : * mut hyper_body) -> hyper_code { let body =
|
||||||
|
non_null!(Box::from_raw(body) ?= hyper_code::HYPERE_INVALID_ARG); let req =
|
||||||
|
non_null!(& mut * req ?= hyper_code::HYPERE_INVALID_ARG); * req.0.body_mut() = body
|
||||||
|
.0; hyper_code::HYPERE_OK }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set an informational (1xx) response callback."] #[doc = ""] #[doc =
|
||||||
|
" The callback is called each time hyper receives an informational (1xx)"] #[doc =
|
||||||
|
" response for this request."] #[doc = ""] #[doc =
|
||||||
|
" The third argument is an opaque user data pointer, which is passed to"] #[doc =
|
||||||
|
" the callback each time."] #[doc = ""] #[doc =
|
||||||
|
" The callback is passed the `void *` data pointer, and a"] #[doc =
|
||||||
|
" `hyper_response *` which can be inspected as any other response. The"] #[doc =
|
||||||
|
" body of the response will always be empty."] #[doc = ""] #[doc =
|
||||||
|
" NOTE: The `hyper_response *` is just borrowed data, and will not"] #[doc =
|
||||||
|
" be valid after the callback finishes. You must copy any data you wish"] #[doc =
|
||||||
|
" to persist."] fn hyper_request_on_informational(req : * mut hyper_request, callback
|
||||||
|
: hyper_request_on_informational_callback, data : * mut c_void) -> hyper_code { let
|
||||||
|
ext = OnInformational { func : callback, data : UserDataPointer(data), }; let req =
|
||||||
|
non_null!(& mut * req ?= hyper_code::HYPERE_INVALID_ARG); req.0.extensions_mut()
|
||||||
|
.insert(ext); hyper_code::HYPERE_OK }
|
||||||
|
}
|
||||||
|
impl hyper_request {
|
||||||
|
pub(super) fn finalize_request(&mut self) {
|
||||||
|
if let Some(headers) = self.0.extensions_mut().remove::<hyper_headers>() {
|
||||||
|
*self.0.headers_mut() = headers.headers;
|
||||||
|
self.0.extensions_mut().insert(headers.orig_casing);
|
||||||
|
self.0.extensions_mut().insert(headers.orig_order);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free an HTTP response after using it."] fn hyper_response_free(resp : * mut
|
||||||
|
hyper_response) { drop(non_null!(Box::from_raw(resp) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get the HTTP-Status code of this response."] #[doc = ""] #[doc =
|
||||||
|
" It will always be within the range of 100-599."] fn hyper_response_status(resp : *
|
||||||
|
const hyper_response) -> u16 { non_null!(&* resp ?= 0) .0.status().as_u16() }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get a pointer to the reason-phrase of this response."] #[doc = ""] #[doc =
|
||||||
|
" This buffer is not null-terminated."] #[doc = ""] #[doc =
|
||||||
|
" This buffer is owned by the response, and should not be used after"] #[doc =
|
||||||
|
" the response has been freed."] #[doc = ""] #[doc =
|
||||||
|
" Use `hyper_response_reason_phrase_len()` to get the length of this"] #[doc =
|
||||||
|
" buffer."] fn hyper_response_reason_phrase(resp : * const hyper_response) -> * const
|
||||||
|
u8 { non_null!(&* resp ?= std::ptr::null()) .reason_phrase().as_ptr() } ?=
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get the length of the reason-phrase of this response."] #[doc = ""] #[doc =
|
||||||
|
" Use `hyper_response_reason_phrase()` to get the buffer pointer."] fn
|
||||||
|
hyper_response_reason_phrase_len(resp : * const hyper_response) -> size_t {
|
||||||
|
non_null!(&* resp ?= 0) .reason_phrase().len() }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get a reference to the full raw headers of this response."] #[doc = ""]
|
||||||
|
#[doc = " You must have enabled `hyper_clientconn_options_headers_raw()`, or this"]
|
||||||
|
#[doc = " will return NULL."] #[doc = ""] #[doc =
|
||||||
|
" The returned `hyper_buf *` is just a reference, owned by the response."] #[doc =
|
||||||
|
" You need to make a copy if you wish to use it after freeing the"] #[doc =
|
||||||
|
" response."] #[doc = ""] #[doc =
|
||||||
|
" The buffer is not null-terminated, see the `hyper_buf` functions for"] #[doc =
|
||||||
|
" getting the bytes and length."] fn hyper_response_headers_raw(resp : * const
|
||||||
|
hyper_response) -> * const hyper_buf { let resp = non_null!(&* resp ?=
|
||||||
|
std::ptr::null()); match resp.0.extensions().get::< RawHeaders > () { Some(raw) => &
|
||||||
|
raw.0, None => std::ptr::null(), } } ?= std::ptr::null()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Get the HTTP version used by this response."] #[doc = ""] #[doc =
|
||||||
|
" The returned value could be:"] #[doc = ""] #[doc = " - `HYPER_HTTP_VERSION_1_0`"]
|
||||||
|
#[doc = " - `HYPER_HTTP_VERSION_1_1`"] #[doc = " - `HYPER_HTTP_VERSION_2`"] #[doc =
|
||||||
|
" - `HYPER_HTTP_VERSION_NONE` if newer (or older)."] fn hyper_response_version(resp :
|
||||||
|
* const hyper_response) -> c_int { use http::Version; match non_null!(&* resp ?= 0)
|
||||||
|
.0.version() { Version::HTTP_10 => super::HYPER_HTTP_VERSION_1_0, Version::HTTP_11 =>
|
||||||
|
super::HYPER_HTTP_VERSION_1_1, Version::HTTP_2 => super::HYPER_HTTP_VERSION_2, _ =>
|
||||||
|
super::HYPER_HTTP_VERSION_NONE, } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Gets a reference to the HTTP headers of this response."] #[doc = ""] #[doc
|
||||||
|
= " This is not an owned reference, so it should not be accessed after the"] #[doc =
|
||||||
|
" `hyper_response` has been freed."] fn hyper_response_headers(resp : * mut
|
||||||
|
hyper_response) -> * mut hyper_headers { hyper_headers::get_or_default(unsafe { & mut
|
||||||
|
* resp } .0.extensions_mut()) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Take ownership of the body of this response."] #[doc = ""] #[doc =
|
||||||
|
" It is safe to free the response even after taking ownership of its body."] fn
|
||||||
|
hyper_response_body(resp : * mut hyper_response) -> * mut hyper_body { let body =
|
||||||
|
std::mem::take(non_null!(& mut * resp ?= std::ptr::null_mut()) .0.body_mut());
|
||||||
|
Box::into_raw(Box::new(hyper_body(body))) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
impl hyper_response {
|
||||||
|
pub(super) fn wrap(mut resp: Response<Body>) -> hyper_response {
|
||||||
|
let headers = std::mem::take(resp.headers_mut());
|
||||||
|
let orig_casing = resp
|
||||||
|
.extensions_mut()
|
||||||
|
.remove::<HeaderCaseMap>()
|
||||||
|
.unwrap_or_else(HeaderCaseMap::default);
|
||||||
|
let orig_order = resp
|
||||||
|
.extensions_mut()
|
||||||
|
.remove::<OriginalHeaderOrder>()
|
||||||
|
.unwrap_or_else(OriginalHeaderOrder::default);
|
||||||
|
resp.extensions_mut()
|
||||||
|
.insert(hyper_headers {
|
||||||
|
headers,
|
||||||
|
orig_casing,
|
||||||
|
orig_order,
|
||||||
|
});
|
||||||
|
hyper_response(resp)
|
||||||
|
}
|
||||||
|
fn reason_phrase(&self) -> &[u8] {
|
||||||
|
if let Some(reason) = self.0.extensions().get::<ReasonPhrase>() {
|
||||||
|
return reason.as_bytes();
|
||||||
|
}
|
||||||
|
if let Some(reason) = self.0.status().canonical_reason() {
|
||||||
|
return reason.as_bytes();
|
||||||
|
}
|
||||||
|
&[]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe impl AsTaskType for hyper_response {
|
||||||
|
fn as_task_type(&self) -> hyper_task_return_type {
|
||||||
|
hyper_task_return_type::HYPER_TASK_RESPONSE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
type hyper_headers_foreach_callback = extern "C" fn(
|
||||||
|
*mut c_void,
|
||||||
|
*const u8,
|
||||||
|
size_t,
|
||||||
|
*const u8,
|
||||||
|
size_t,
|
||||||
|
) -> c_int;
|
||||||
|
impl hyper_headers {
|
||||||
|
pub(super) fn get_or_default(ext: &mut http::Extensions) -> &mut hyper_headers {
|
||||||
|
if let None = ext.get_mut::<hyper_headers>() {
|
||||||
|
ext.insert(hyper_headers::default());
|
||||||
|
}
|
||||||
|
ext.get_mut::<hyper_headers>().unwrap()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Iterates the headers passing each name and value pair to the callback."]
|
||||||
|
#[doc = ""] #[doc = " The `userdata` pointer is also passed to the callback."] #[doc
|
||||||
|
= ""] #[doc =
|
||||||
|
" The callback should return `HYPER_ITER_CONTINUE` to keep iterating, or"] #[doc =
|
||||||
|
" `HYPER_ITER_BREAK` to stop."] fn hyper_headers_foreach(headers : * const
|
||||||
|
hyper_headers, func : hyper_headers_foreach_callback, userdata : * mut c_void) { let
|
||||||
|
headers = non_null!(&* headers ?= ()); let mut ordered_iter = headers.orig_order
|
||||||
|
.get_in_order().peekable(); if ordered_iter.peek().is_some() { for (name, idx) in
|
||||||
|
ordered_iter { let (name_ptr, name_len) = if let Some(orig_name) = headers
|
||||||
|
.orig_casing.get_all(name).nth(* idx) { (orig_name.as_ref().as_ptr(), orig_name
|
||||||
|
.as_ref().len()) } else { (name.as_str().as_bytes().as_ptr(), name.as_str()
|
||||||
|
.as_bytes().len(),) }; let val_ptr; let val_len; if let Some(value) = headers.headers
|
||||||
|
.get_all(name).iter().nth(* idx) { val_ptr = value.as_bytes().as_ptr(); val_len =
|
||||||
|
value.as_bytes().len(); } else { return; } if HYPER_ITER_CONTINUE != func(userdata,
|
||||||
|
name_ptr, name_len, val_ptr, val_len) { return; } } } else { for name in headers
|
||||||
|
.headers.keys() { let mut names = headers.orig_casing.get_all(name); for value in
|
||||||
|
headers.headers.get_all(name) { let (name_ptr, name_len) = if let Some(orig_name) =
|
||||||
|
names.next() { (orig_name.as_ref().as_ptr(), orig_name.as_ref().len()) } else { (name
|
||||||
|
.as_str().as_bytes().as_ptr(), name.as_str().as_bytes().len(),) }; let val_ptr =
|
||||||
|
value.as_bytes().as_ptr(); let val_len = value.as_bytes().len(); if
|
||||||
|
HYPER_ITER_CONTINUE != func(userdata, name_ptr, name_len, val_ptr, val_len) { return;
|
||||||
|
} } } } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Sets the header with the provided name to the provided value."] #[doc = ""]
|
||||||
|
#[doc = " This overwrites any previous value set for the header."] fn
|
||||||
|
hyper_headers_set(headers : * mut hyper_headers, name : * const u8, name_len :
|
||||||
|
size_t, value : * const u8, value_len : size_t) -> hyper_code { let headers =
|
||||||
|
non_null!(& mut * headers ?= hyper_code::HYPERE_INVALID_ARG); match unsafe {
|
||||||
|
raw_name_value(name, name_len, value, value_len) } { Ok((name, value, orig_name)) =>
|
||||||
|
{ headers.headers.insert(& name, value); headers.orig_casing.insert(name.clone(),
|
||||||
|
orig_name.clone()); headers.orig_order.insert(name); hyper_code::HYPERE_OK }
|
||||||
|
Err(code) => code, } }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Adds the provided value to the list of the provided name."] #[doc = ""]
|
||||||
|
#[doc = " If there were already existing values for the name, this will append the"]
|
||||||
|
#[doc = " new value to the internal list."] fn hyper_headers_add(headers : * mut
|
||||||
|
hyper_headers, name : * const u8, name_len : size_t, value : * const u8, value_len :
|
||||||
|
size_t) -> hyper_code { let headers = non_null!(& mut * headers ?=
|
||||||
|
hyper_code::HYPERE_INVALID_ARG); match unsafe { raw_name_value(name, name_len, value,
|
||||||
|
value_len) } { Ok((name, value, orig_name)) => { headers.headers.append(& name,
|
||||||
|
value); headers.orig_casing.append(& name, orig_name.clone()); headers.orig_order
|
||||||
|
.append(name); hyper_code::HYPERE_OK } Err(code) => code, } }
|
||||||
|
}
|
||||||
|
impl Default for hyper_headers {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self {
|
||||||
|
headers: Default::default(),
|
||||||
|
orig_casing: HeaderCaseMap::default(),
|
||||||
|
orig_order: OriginalHeaderOrder::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe fn raw_name_value(
|
||||||
|
name: *const u8,
|
||||||
|
name_len: size_t,
|
||||||
|
value: *const u8,
|
||||||
|
value_len: size_t,
|
||||||
|
) -> Result<(HeaderName, HeaderValue, Bytes), hyper_code> {
|
||||||
|
let name = std::slice::from_raw_parts(name, name_len);
|
||||||
|
let orig_name = Bytes::copy_from_slice(name);
|
||||||
|
let name = match HeaderName::from_bytes(name) {
|
||||||
|
Ok(name) => name,
|
||||||
|
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
|
||||||
|
};
|
||||||
|
let value = std::slice::from_raw_parts(value, value_len);
|
||||||
|
let value = match HeaderValue::from_bytes(value) {
|
||||||
|
Ok(val) => val,
|
||||||
|
Err(_) => return Err(hyper_code::HYPERE_INVALID_ARG),
|
||||||
|
};
|
||||||
|
Ok((name, value, orig_name))
|
||||||
|
}
|
||||||
|
impl OnInformational {
|
||||||
|
pub(crate) fn call(&mut self, resp: Response<Body>) {
|
||||||
|
let mut resp = hyper_response::wrap(resp);
|
||||||
|
(self.func)(self.data.0, &mut resp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
#[test]
|
||||||
|
fn test_headers_foreach_cases_preserved() {
|
||||||
|
let mut headers = hyper_headers::default();
|
||||||
|
let name1 = b"Set-CookiE";
|
||||||
|
let value1 = b"a=b";
|
||||||
|
hyper_headers_add(
|
||||||
|
&mut headers,
|
||||||
|
name1.as_ptr(),
|
||||||
|
name1.len(),
|
||||||
|
value1.as_ptr(),
|
||||||
|
value1.len(),
|
||||||
|
);
|
||||||
|
let name2 = b"SET-COOKIE";
|
||||||
|
let value2 = b"c=d";
|
||||||
|
hyper_headers_add(
|
||||||
|
&mut headers,
|
||||||
|
name2.as_ptr(),
|
||||||
|
name2.len(),
|
||||||
|
value2.as_ptr(),
|
||||||
|
value2.len(),
|
||||||
|
);
|
||||||
|
let mut vec = Vec::<u8>::new();
|
||||||
|
hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void);
|
||||||
|
assert_eq!(vec, b"Set-CookiE: a=b\r\nSET-COOKIE: c=d\r\n");
|
||||||
|
extern "C" fn concat(
|
||||||
|
vec: *mut c_void,
|
||||||
|
name: *const u8,
|
||||||
|
name_len: usize,
|
||||||
|
value: *const u8,
|
||||||
|
value_len: usize,
|
||||||
|
) -> c_int {
|
||||||
|
unsafe {
|
||||||
|
let vec = &mut *(vec as *mut Vec<u8>);
|
||||||
|
let name = std::slice::from_raw_parts(name, name_len);
|
||||||
|
let value = std::slice::from_raw_parts(value, value_len);
|
||||||
|
vec.extend(name);
|
||||||
|
vec.extend(b": ");
|
||||||
|
vec.extend(value);
|
||||||
|
vec.extend(b"\r\n");
|
||||||
|
}
|
||||||
|
HYPER_ITER_CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "ffi"))]
|
||||||
|
#[test]
|
||||||
|
fn test_headers_foreach_order_preserved() {
|
||||||
|
let mut headers = hyper_headers::default();
|
||||||
|
let name1 = b"Set-CookiE";
|
||||||
|
let value1 = b"a=b";
|
||||||
|
hyper_headers_add(
|
||||||
|
&mut headers,
|
||||||
|
name1.as_ptr(),
|
||||||
|
name1.len(),
|
||||||
|
value1.as_ptr(),
|
||||||
|
value1.len(),
|
||||||
|
);
|
||||||
|
let name2 = b"Content-Encoding";
|
||||||
|
let value2 = b"gzip";
|
||||||
|
hyper_headers_add(
|
||||||
|
&mut headers,
|
||||||
|
name2.as_ptr(),
|
||||||
|
name2.len(),
|
||||||
|
value2.as_ptr(),
|
||||||
|
value2.len(),
|
||||||
|
);
|
||||||
|
let name3 = b"SET-COOKIE";
|
||||||
|
let value3 = b"c=d";
|
||||||
|
hyper_headers_add(
|
||||||
|
&mut headers,
|
||||||
|
name3.as_ptr(),
|
||||||
|
name3.len(),
|
||||||
|
value3.as_ptr(),
|
||||||
|
value3.len(),
|
||||||
|
);
|
||||||
|
let mut vec = Vec::<u8>::new();
|
||||||
|
hyper_headers_foreach(&headers, concat, &mut vec as *mut _ as *mut c_void);
|
||||||
|
println!("{}", std::str::from_utf8(& vec).unwrap());
|
||||||
|
assert_eq!(
|
||||||
|
vec, b"Set-CookiE: a=b\r\nContent-Encoding: gzip\r\nSET-COOKIE: c=d\r\n"
|
||||||
|
);
|
||||||
|
extern "C" fn concat(
|
||||||
|
vec: *mut c_void,
|
||||||
|
name: *const u8,
|
||||||
|
name_len: usize,
|
||||||
|
value: *const u8,
|
||||||
|
value_len: usize,
|
||||||
|
) -> c_int {
|
||||||
|
unsafe {
|
||||||
|
let vec = &mut *(vec as *mut Vec<u8>);
|
||||||
|
let name = std::slice::from_raw_parts(name, name_len);
|
||||||
|
let value = std::slice::from_raw_parts(value, value_len);
|
||||||
|
vec.extend(name);
|
||||||
|
vec.extend(b": ");
|
||||||
|
vec.extend(value);
|
||||||
|
vec.extend(b"\r\n");
|
||||||
|
}
|
||||||
|
HYPER_ITER_CONTINUE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
153
hyper/src/ffi/io.rs
Normal file
153
hyper/src/ffi/io.rs
Normal file
|
|
@ -0,0 +1,153 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use libc::size_t;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use super::task::hyper_context;
|
||||||
|
/// Sentinel value to return from a read or write callback that the operation
|
||||||
|
/// is pending.
|
||||||
|
pub(crate) const HYPER_IO_PENDING: size_t = 0xFFFFFFFF;
|
||||||
|
/// Sentinel value to return from a read or write callback that the operation
|
||||||
|
/// has errored.
|
||||||
|
pub(crate) const HYPER_IO_ERROR: size_t = 0xFFFFFFFE;
|
||||||
|
type hyper_io_read_callback = extern "C" fn(
|
||||||
|
*mut c_void,
|
||||||
|
*mut hyper_context<'_>,
|
||||||
|
*mut u8,
|
||||||
|
size_t,
|
||||||
|
) -> size_t;
|
||||||
|
type hyper_io_write_callback = extern "C" fn(
|
||||||
|
*mut c_void,
|
||||||
|
*mut hyper_context<'_>,
|
||||||
|
*const u8,
|
||||||
|
size_t,
|
||||||
|
) -> size_t;
|
||||||
|
/// An IO object used to represent a socket or similar concept.
|
||||||
|
pub(crate) struct hyper_io {
|
||||||
|
read: hyper_io_read_callback,
|
||||||
|
write: hyper_io_write_callback,
|
||||||
|
userdata: *mut c_void,
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Create a new IO type used to represent a transport."] #[doc = ""] #[doc =
|
||||||
|
" The read and write functions of this transport should be set with"] #[doc =
|
||||||
|
" `hyper_io_set_read` and `hyper_io_set_write`."] fn hyper_io_new() -> * mut hyper_io
|
||||||
|
{ Box::into_raw(Box::new(hyper_io { read : read_noop, write : write_noop, userdata :
|
||||||
|
std::ptr::null_mut(), })) } ?= std::ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free an unused `hyper_io *`."] #[doc = ""] #[doc =
|
||||||
|
" This is typically only useful if you aren't going to pass ownership"] #[doc =
|
||||||
|
" of the IO handle to hyper, such as with `hyper_clientconn_handshake()`."] fn
|
||||||
|
hyper_io_free(io : * mut hyper_io) { drop(non_null!(Box::from_raw(io) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the user data pointer for this IO to some value."] #[doc = ""] #[doc =
|
||||||
|
" This value is passed as an argument to the read and write callbacks."] fn
|
||||||
|
hyper_io_set_userdata(io : * mut hyper_io, data : * mut c_void) { non_null!(& mut *
|
||||||
|
io ?= ()) .userdata = data; }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the read function for this IO transport."] #[doc = ""] #[doc =
|
||||||
|
" Data that is read from the transport should be put in the `buf` pointer,"] #[doc =
|
||||||
|
" up to `buf_len` bytes. The number of bytes read should be the return value."] #[doc
|
||||||
|
= ""] #[doc =
|
||||||
|
" It is undefined behavior to try to access the bytes in the `buf` pointer,"] #[doc =
|
||||||
|
" unless you have already written them yourself. It is also undefined behavior"]
|
||||||
|
#[doc =
|
||||||
|
" to return that more bytes have been written than actually set on the `buf`."] #[doc
|
||||||
|
= ""] #[doc =
|
||||||
|
" If there is no data currently available, a waker should be claimed from"] #[doc =
|
||||||
|
" the `ctx` and registered with whatever polling mechanism is used to signal"] #[doc
|
||||||
|
= " when data is available later on. The return value should be"] #[doc =
|
||||||
|
" `HYPER_IO_PENDING`."] #[doc = ""] #[doc =
|
||||||
|
" If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`"] #[doc =
|
||||||
|
" should be the return value."] fn hyper_io_set_read(io : * mut hyper_io, func :
|
||||||
|
hyper_io_read_callback) { non_null!(& mut * io ?= ()) .read = func; }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set the write function for this IO transport."] #[doc = ""] #[doc =
|
||||||
|
" Data from the `buf` pointer should be written to the transport, up to"] #[doc =
|
||||||
|
" `buf_len` bytes. The number of bytes written should be the return value."] #[doc =
|
||||||
|
""] #[doc = " If no data can currently be written, the `waker` should be cloned and"]
|
||||||
|
#[doc = " registered with whatever polling mechanism is used to signal when data"]
|
||||||
|
#[doc = " is available later on. The return value should be `HYPER_IO_PENDING`."]
|
||||||
|
#[doc = ""] #[doc = " Yeet."] #[doc = ""] #[doc =
|
||||||
|
" If there is an irrecoverable error reading data, then `HYPER_IO_ERROR`"] #[doc =
|
||||||
|
" should be the return value."] fn hyper_io_set_write(io : * mut hyper_io, func :
|
||||||
|
hyper_io_write_callback) { non_null!(& mut * io ?= ()) .write = func; }
|
||||||
|
}
|
||||||
|
/// cbindgen:ignore
|
||||||
|
extern "C" fn read_noop(
|
||||||
|
_userdata: *mut c_void,
|
||||||
|
_: *mut hyper_context<'_>,
|
||||||
|
_buf: *mut u8,
|
||||||
|
_buf_len: size_t,
|
||||||
|
) -> size_t {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
/// cbindgen:ignore
|
||||||
|
extern "C" fn write_noop(
|
||||||
|
_userdata: *mut c_void,
|
||||||
|
_: *mut hyper_context<'_>,
|
||||||
|
_buf: *const u8,
|
||||||
|
_buf_len: size_t,
|
||||||
|
) -> size_t {
|
||||||
|
0
|
||||||
|
}
|
||||||
|
impl AsyncRead for hyper_io {
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &mut tokio::io::ReadBuf<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
let buf_ptr = unsafe { buf.unfilled_mut() }.as_mut_ptr() as *mut u8;
|
||||||
|
let buf_len = buf.remaining();
|
||||||
|
match (self.read)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
|
||||||
|
HYPER_IO_PENDING => Poll::Pending,
|
||||||
|
HYPER_IO_ERROR => {
|
||||||
|
Poll::Ready(
|
||||||
|
Err(std::io::Error::new(std::io::ErrorKind::Other, "io error")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ok => {
|
||||||
|
unsafe { buf.assume_init(ok) };
|
||||||
|
buf.advance(ok);
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl AsyncWrite for hyper_io {
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<std::io::Result<usize>> {
|
||||||
|
let buf_ptr = buf.as_ptr();
|
||||||
|
let buf_len = buf.len();
|
||||||
|
match (self.write)(self.userdata, hyper_context::wrap(cx), buf_ptr, buf_len) {
|
||||||
|
HYPER_IO_PENDING => Poll::Pending,
|
||||||
|
HYPER_IO_ERROR => {
|
||||||
|
Poll::Ready(
|
||||||
|
Err(std::io::Error::new(std::io::ErrorKind::Other, "io error")),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
ok => Poll::Ready(Ok(ok)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn poll_flush(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_: &mut Context<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
fn poll_shutdown(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
_: &mut Context<'_>,
|
||||||
|
) -> Poll<std::io::Result<()>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe impl Send for hyper_io {}
|
||||||
|
unsafe impl Sync for hyper_io {}
|
||||||
53
hyper/src/ffi/macros.rs
Normal file
53
hyper/src/ffi/macros.rs
Normal file
|
|
@ -0,0 +1,53 @@
|
||||||
|
macro_rules! ffi_fn {
|
||||||
|
($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty $body:block ?= $default:expr) => {
|
||||||
|
$(#[$doc])*
|
||||||
|
#[no_mangle]
|
||||||
|
pub extern fn $name($($arg: $arg_ty),*) -> $ret {
|
||||||
|
use std::panic::{self, AssertUnwindSafe};
|
||||||
|
|
||||||
|
match panic::catch_unwind(AssertUnwindSafe(move || $body)) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(_) => {
|
||||||
|
$default
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) -> $ret:ty $body:block) => {
|
||||||
|
ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> $ret $body ?= {
|
||||||
|
eprintln!("panic unwind caught, aborting");
|
||||||
|
std::process::abort()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) $body:block ?= $default:expr) => {
|
||||||
|
ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> () $body ?= $default);
|
||||||
|
};
|
||||||
|
|
||||||
|
($(#[$doc:meta])* fn $name:ident($($arg:ident: $arg_ty:ty),*) $body:block) => {
|
||||||
|
ffi_fn!($(#[$doc])* fn $name($($arg: $arg_ty),*) -> () $body);
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! non_null {
|
||||||
|
($ptr:ident, $eval:expr, $err:expr) => {{
|
||||||
|
debug_assert!(!$ptr.is_null(), "{:?} must not be null", stringify!($ptr));
|
||||||
|
if $ptr.is_null() {
|
||||||
|
return $err;
|
||||||
|
}
|
||||||
|
unsafe { $eval }
|
||||||
|
}};
|
||||||
|
(&*$ptr:ident ?= $err:expr) => {{
|
||||||
|
non_null!($ptr, &*$ptr, $err)
|
||||||
|
}};
|
||||||
|
(&mut *$ptr:ident ?= $err:expr) => {{
|
||||||
|
non_null!($ptr, &mut *$ptr, $err)
|
||||||
|
}};
|
||||||
|
(Box::from_raw($ptr:ident) ?= $err:expr) => {{
|
||||||
|
non_null!($ptr, Box::from_raw($ptr), $err)
|
||||||
|
}};
|
||||||
|
(Arc::from_raw($ptr:ident) ?= $err:expr) => {{
|
||||||
|
non_null!($ptr, Arc::from_raw($ptr), $err)
|
||||||
|
}};
|
||||||
|
}
|
||||||
73
hyper/src/ffi/mod.rs
Normal file
73
hyper/src/ffi/mod.rs
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
#![allow(non_camel_case_types)]
|
||||||
|
#![allow(missing_debug_implementations)]
|
||||||
|
#![allow(unreachable_pub)]
|
||||||
|
//! # hyper C API
|
||||||
|
//!
|
||||||
|
//! This part of the documentation describes the C API for hyper. That is, how
|
||||||
|
//! to *use* the hyper library in C code. This is **not** a regular Rust
|
||||||
|
//! module, and thus it is not accessible in Rust.
|
||||||
|
//!
|
||||||
|
//! ## Unstable
|
||||||
|
//!
|
||||||
|
//! The C API of hyper is currently **unstable**, which means it's not part of
|
||||||
|
//! the semver contract as the rest of the Rust API is. Because of that, it's
|
||||||
|
//! only accessible if `--cfg hyper_unstable_ffi` is passed to `rustc` when
|
||||||
|
//! compiling. The easiest way to do that is setting the `RUSTFLAGS`
|
||||||
|
//! environment variable.
|
||||||
|
//!
|
||||||
|
//! ## Building
|
||||||
|
//!
|
||||||
|
//! The C API is part of the Rust library, but isn't compiled by default. Using
|
||||||
|
//! `cargo`, it can be compiled with the following command:
|
||||||
|
//!
|
||||||
|
//! ```notrust
|
||||||
|
//! RUSTFLAGS="--cfg hyper_unstable_ffi" cargo build --features client,http1,http2,ffi
|
||||||
|
//! ```
|
||||||
|
#[cfg(not(all(feature = "client", feature = "http1")))]
|
||||||
|
compile_error!(
|
||||||
|
"The `ffi` feature currently requires the `client` and `http1` features."
|
||||||
|
);
|
||||||
|
#[cfg(not(hyper_unstable_ffi))]
|
||||||
|
compile_error!(
|
||||||
|
"\
|
||||||
|
The `ffi` feature is unstable, and requires the \
|
||||||
|
`RUSTFLAGS='--cfg hyper_unstable_ffi'` environment variable to be set.\
|
||||||
|
"
|
||||||
|
);
|
||||||
|
#[macro_use]
|
||||||
|
mod macros;
|
||||||
|
mod body;
|
||||||
|
mod client;
|
||||||
|
mod error;
|
||||||
|
mod http_types;
|
||||||
|
mod io;
|
||||||
|
mod task;
|
||||||
|
pub(crate) use self::body::*;
|
||||||
|
pub(crate) use self::client::*;
|
||||||
|
pub(crate) use self::error::*;
|
||||||
|
pub(crate) use self::http_types::*;
|
||||||
|
pub(crate) use self::io::*;
|
||||||
|
pub(crate) use self::task::*;
|
||||||
|
/// Return in iter functions to continue iterating.
|
||||||
|
pub(crate) const HYPER_ITER_CONTINUE: libc::c_int = 0;
|
||||||
|
/// Return in iter functions to stop iterating.
|
||||||
|
#[allow(unused)]
|
||||||
|
pub(crate) const HYPER_ITER_BREAK: libc::c_int = 1;
|
||||||
|
/// An HTTP Version that is unspecified.
|
||||||
|
pub(crate) const HYPER_HTTP_VERSION_NONE: libc::c_int = 0;
|
||||||
|
/// The HTTP/1.0 version.
|
||||||
|
pub(crate) const HYPER_HTTP_VERSION_1_0: libc::c_int = 10;
|
||||||
|
/// The HTTP/1.1 version.
|
||||||
|
pub(crate) const HYPER_HTTP_VERSION_1_1: libc::c_int = 11;
|
||||||
|
/// The HTTP/2 version.
|
||||||
|
pub(crate) const HYPER_HTTP_VERSION_2: libc::c_int = 20;
|
||||||
|
struct UserDataPointer(*mut std::ffi::c_void);
|
||||||
|
unsafe impl Send for UserDataPointer {}
|
||||||
|
unsafe impl Sync for UserDataPointer {}
|
||||||
|
/// cbindgen:ignore
|
||||||
|
static VERSION_CSTR: &str = concat!(env!("CARGO_PKG_VERSION"), "\0");
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Returns a static ASCII (null terminated) string of the hyper version."] fn
|
||||||
|
hyper_version() -> * const libc::c_char { VERSION_CSTR.as_ptr() as _ } ?=
|
||||||
|
std::ptr::null()
|
||||||
|
}
|
||||||
296
hyper/src/ffi/task.rs
Normal file
296
hyper/src/ffi/task.rs
Normal file
|
|
@ -0,0 +1,296 @@
|
||||||
|
use std::ffi::c_void;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::ptr;
|
||||||
|
use std::sync::{
|
||||||
|
atomic::{AtomicBool, Ordering},
|
||||||
|
Arc, Mutex, Weak,
|
||||||
|
};
|
||||||
|
use std::task::{Context, Poll};
|
||||||
|
use futures_util::stream::{FuturesUnordered, Stream};
|
||||||
|
use libc::c_int;
|
||||||
|
use super::error::hyper_code;
|
||||||
|
use super::UserDataPointer;
|
||||||
|
type BoxFuture<T> = Pin<Box<dyn Future<Output = T> + Send>>;
|
||||||
|
type BoxAny = Box<dyn AsTaskType + Send + Sync>;
|
||||||
|
/// Return in a poll function to indicate it was ready.
|
||||||
|
pub(crate) const HYPER_POLL_READY: c_int = 0;
|
||||||
|
/// Return in a poll function to indicate it is still pending.
|
||||||
|
///
|
||||||
|
/// The passed in `hyper_waker` should be registered to wake up the task at
|
||||||
|
/// some later point.
|
||||||
|
pub(crate) const HYPER_POLL_PENDING: c_int = 1;
|
||||||
|
/// Return in a poll function indicate an error.
|
||||||
|
pub(crate) const HYPER_POLL_ERROR: c_int = 3;
|
||||||
|
/// A task executor for `hyper_task`s.
|
||||||
|
pub(crate) struct hyper_executor {
|
||||||
|
/// The executor of all task futures.
|
||||||
|
///
|
||||||
|
/// There should never be contention on the mutex, as it is only locked
|
||||||
|
/// to drive the futures. However, we cannot guarantee proper usage from
|
||||||
|
/// `hyper_executor_poll()`, which in C could potentially be called inside
|
||||||
|
/// one of the stored futures. The mutex isn't re-entrant, so doing so
|
||||||
|
/// would result in a deadlock, but that's better than data corruption.
|
||||||
|
driver: Mutex<FuturesUnordered<TaskFuture>>,
|
||||||
|
/// The queue of futures that need to be pushed into the `driver`.
|
||||||
|
///
|
||||||
|
/// This is has a separate mutex since `spawn` could be called from inside
|
||||||
|
/// a future, which would mean the driver's mutex is already locked.
|
||||||
|
spawn_queue: Mutex<Vec<TaskFuture>>,
|
||||||
|
/// This is used to track when a future calls `wake` while we are within
|
||||||
|
/// `hyper_executor::poll_next`.
|
||||||
|
is_woken: Arc<ExecWaker>,
|
||||||
|
}
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct WeakExec(Weak<hyper_executor>);
|
||||||
|
struct ExecWaker(AtomicBool);
|
||||||
|
/// An async task.
|
||||||
|
pub(crate) struct hyper_task {
|
||||||
|
future: BoxFuture<BoxAny>,
|
||||||
|
output: Option<BoxAny>,
|
||||||
|
userdata: UserDataPointer,
|
||||||
|
}
|
||||||
|
struct TaskFuture {
|
||||||
|
task: Option<Box<hyper_task>>,
|
||||||
|
}
|
||||||
|
/// An async context for a task that contains the related waker.
|
||||||
|
pub(crate) struct hyper_context<'a>(Context<'a>);
|
||||||
|
/// A waker that is saved and used to waken a pending task.
|
||||||
|
pub(crate) struct hyper_waker {
|
||||||
|
waker: std::task::Waker,
|
||||||
|
}
|
||||||
|
/// A descriptor for what type a `hyper_task` value is.
|
||||||
|
#[repr(C)]
|
||||||
|
pub(crate) enum hyper_task_return_type {
|
||||||
|
/// The value of this task is null (does not imply an error).
|
||||||
|
HYPER_TASK_EMPTY,
|
||||||
|
/// The value of this task is `hyper_error *`.
|
||||||
|
HYPER_TASK_ERROR,
|
||||||
|
/// The value of this task is `hyper_clientconn *`.
|
||||||
|
HYPER_TASK_CLIENTCONN,
|
||||||
|
/// The value of this task is `hyper_response *`.
|
||||||
|
HYPER_TASK_RESPONSE,
|
||||||
|
/// The value of this task is `hyper_buf *`.
|
||||||
|
HYPER_TASK_BUF,
|
||||||
|
}
|
||||||
|
pub(crate) unsafe trait AsTaskType {
|
||||||
|
fn as_task_type(&self) -> hyper_task_return_type;
|
||||||
|
}
|
||||||
|
pub(crate) trait IntoDynTaskType {
|
||||||
|
fn into_dyn_task_type(self) -> BoxAny;
|
||||||
|
}
|
||||||
|
impl hyper_executor {
|
||||||
|
fn new() -> Arc<hyper_executor> {
|
||||||
|
Arc::new(hyper_executor {
|
||||||
|
driver: Mutex::new(FuturesUnordered::new()),
|
||||||
|
spawn_queue: Mutex::new(Vec::new()),
|
||||||
|
is_woken: Arc::new(ExecWaker(AtomicBool::new(false))),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
pub(crate) fn downgrade(exec: &Arc<hyper_executor>) -> WeakExec {
|
||||||
|
WeakExec(Arc::downgrade(exec))
|
||||||
|
}
|
||||||
|
fn spawn(&self, task: Box<hyper_task>) {
|
||||||
|
self.spawn_queue.lock().unwrap().push(TaskFuture { task: Some(task) });
|
||||||
|
}
|
||||||
|
fn poll_next(&self) -> Option<Box<hyper_task>> {
|
||||||
|
self.drain_queue();
|
||||||
|
let waker = futures_util::task::waker_ref(&self.is_woken);
|
||||||
|
let mut cx = Context::from_waker(&waker);
|
||||||
|
loop {
|
||||||
|
match Pin::new(&mut *self.driver.lock().unwrap()).poll_next(&mut cx) {
|
||||||
|
Poll::Ready(val) => return val,
|
||||||
|
Poll::Pending => {
|
||||||
|
if self.drain_queue() {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if self.is_woken.0.swap(false, Ordering::SeqCst) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn drain_queue(&self) -> bool {
|
||||||
|
let mut queue = self.spawn_queue.lock().unwrap();
|
||||||
|
if queue.is_empty() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
let driver = self.driver.lock().unwrap();
|
||||||
|
for task in queue.drain(..) {
|
||||||
|
driver.push(task);
|
||||||
|
}
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl futures_util::task::ArcWake for ExecWaker {
|
||||||
|
fn wake_by_ref(me: &Arc<ExecWaker>) {
|
||||||
|
me.0.store(true, Ordering::SeqCst);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl WeakExec {
|
||||||
|
pub(crate) fn new() -> Self {
|
||||||
|
WeakExec(Weak::new())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl crate::rt::Executor<BoxFuture<()>> for WeakExec {
|
||||||
|
fn execute(&self, fut: BoxFuture<()>) {
|
||||||
|
if let Some(exec) = self.0.upgrade() {
|
||||||
|
exec.spawn(hyper_task::boxed(fut));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Creates a new task executor."] fn hyper_executor_new() -> * const
|
||||||
|
hyper_executor { Arc::into_raw(hyper_executor::new()) } ?= ptr::null()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Frees an executor and any incomplete tasks still part of it."] fn
|
||||||
|
hyper_executor_free(exec : * const hyper_executor) {
|
||||||
|
drop(non_null!(Arc::from_raw(exec) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Push a task onto the executor."] #[doc = ""] #[doc =
|
||||||
|
" The executor takes ownership of the task, it should not be accessed"] #[doc =
|
||||||
|
" again unless returned back to the user with `hyper_executor_poll`."] fn
|
||||||
|
hyper_executor_push(exec : * const hyper_executor, task : * mut hyper_task) ->
|
||||||
|
hyper_code { let exec = non_null!(&* exec ?= hyper_code::HYPERE_INVALID_ARG); let
|
||||||
|
task = non_null!(Box::from_raw(task) ?= hyper_code::HYPERE_INVALID_ARG); exec
|
||||||
|
.spawn(task); hyper_code::HYPERE_OK }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc =
|
||||||
|
" Polls the executor, trying to make progress on any tasks that have notified"] #[doc
|
||||||
|
= " that they are ready again."] #[doc = ""] #[doc =
|
||||||
|
" If ready, returns a task from the executor that has completed."] #[doc = ""] #[doc
|
||||||
|
= " If there are no ready tasks, this returns `NULL`."] fn hyper_executor_poll(exec :
|
||||||
|
* const hyper_executor) -> * mut hyper_task { let exec = non_null!(&* exec ?=
|
||||||
|
ptr::null_mut()); match exec.poll_next() { Some(task) => Box::into_raw(task), None =>
|
||||||
|
ptr::null_mut(), } } ?= ptr::null_mut()
|
||||||
|
}
|
||||||
|
impl hyper_task {
|
||||||
|
pub(crate) fn boxed<F>(fut: F) -> Box<hyper_task>
|
||||||
|
where
|
||||||
|
F: Future + Send + 'static,
|
||||||
|
F::Output: IntoDynTaskType + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
Box::new(hyper_task {
|
||||||
|
future: Box::pin(async move { fut.await.into_dyn_task_type() }),
|
||||||
|
output: None,
|
||||||
|
userdata: UserDataPointer(ptr::null_mut()),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
fn output_type(&self) -> hyper_task_return_type {
|
||||||
|
match self.output {
|
||||||
|
None => hyper_task_return_type::HYPER_TASK_EMPTY,
|
||||||
|
Some(ref val) => val.as_task_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl Future for TaskFuture {
|
||||||
|
type Output = Box<hyper_task>;
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||||
|
match Pin::new(&mut self.task.as_mut().unwrap().future).poll(cx) {
|
||||||
|
Poll::Ready(val) => {
|
||||||
|
let mut task = self.task.take().unwrap();
|
||||||
|
task.output = Some(val);
|
||||||
|
Poll::Ready(task)
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free a task."] fn hyper_task_free(task : * mut hyper_task) {
|
||||||
|
drop(non_null!(Box::from_raw(task) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Takes the output value of this task."] #[doc = ""] #[doc =
|
||||||
|
" This must only be called once polling the task on an executor has finished"] #[doc
|
||||||
|
= " this task."] #[doc = ""] #[doc =
|
||||||
|
" Use `hyper_task_type` to determine the type of the `void *` return value."] fn
|
||||||
|
hyper_task_value(task : * mut hyper_task) -> * mut c_void { let task = non_null!(&
|
||||||
|
mut * task ?= ptr::null_mut()); if let Some(val) = task.output.take() { let p =
|
||||||
|
Box::into_raw(val) as * mut c_void; if p == std::ptr::NonNull::< c_void >::dangling()
|
||||||
|
.as_ptr() { ptr::null_mut() } else { p } } else { ptr::null_mut() } } ?=
|
||||||
|
ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Query the return type of this task."] fn hyper_task_type(task : * mut
|
||||||
|
hyper_task) -> hyper_task_return_type { non_null!(&* task ?=
|
||||||
|
hyper_task_return_type::HYPER_TASK_EMPTY) .output_type() }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Set a user data pointer to be associated with this task."] #[doc = ""]
|
||||||
|
#[doc = " This value will be passed to task callbacks, and can be checked later"]
|
||||||
|
#[doc = " with `hyper_task_userdata`."] fn hyper_task_set_userdata(task : * mut
|
||||||
|
hyper_task, userdata : * mut c_void) { if task.is_null() { return; } unsafe { (*
|
||||||
|
task).userdata = UserDataPointer(userdata) }; }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Retrieve the userdata that has been set via `hyper_task_set_userdata`."] fn
|
||||||
|
hyper_task_userdata(task : * mut hyper_task) -> * mut c_void { non_null!(&* task ?=
|
||||||
|
ptr::null_mut()) .userdata.0 } ?= ptr::null_mut()
|
||||||
|
}
|
||||||
|
unsafe impl AsTaskType for () {
|
||||||
|
fn as_task_type(&self) -> hyper_task_return_type {
|
||||||
|
hyper_task_return_type::HYPER_TASK_EMPTY
|
||||||
|
}
|
||||||
|
}
|
||||||
|
unsafe impl AsTaskType for crate::Error {
|
||||||
|
fn as_task_type(&self) -> hyper_task_return_type {
|
||||||
|
hyper_task_return_type::HYPER_TASK_ERROR
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> IntoDynTaskType for T
|
||||||
|
where
|
||||||
|
T: AsTaskType + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn into_dyn_task_type(self) -> BoxAny {
|
||||||
|
Box::new(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> IntoDynTaskType for crate::Result<T>
|
||||||
|
where
|
||||||
|
T: IntoDynTaskType + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn into_dyn_task_type(self) -> BoxAny {
|
||||||
|
match self {
|
||||||
|
Ok(val) => val.into_dyn_task_type(),
|
||||||
|
Err(err) => Box::new(err),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T> IntoDynTaskType for Option<T>
|
||||||
|
where
|
||||||
|
T: IntoDynTaskType + Send + Sync + 'static,
|
||||||
|
{
|
||||||
|
fn into_dyn_task_type(self) -> BoxAny {
|
||||||
|
match self {
|
||||||
|
Some(val) => val.into_dyn_task_type(),
|
||||||
|
None => ().into_dyn_task_type(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl hyper_context<'_> {
|
||||||
|
pub(crate) fn wrap<'a, 'b>(cx: &'a mut Context<'b>) -> &'a mut hyper_context<'b> {
|
||||||
|
unsafe { std::mem::transmute::<&mut Context<'_>, &mut hyper_context<'_>>(cx) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Copies a waker out of the task context."] fn hyper_context_waker(cx : * mut
|
||||||
|
hyper_context <'_ >) -> * mut hyper_waker { let waker = non_null!(& mut * cx ?=
|
||||||
|
ptr::null_mut()) .0.waker().clone(); Box::into_raw(Box::new(hyper_waker { waker })) }
|
||||||
|
?= ptr::null_mut()
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Free a waker that hasn't been woken."] fn hyper_waker_free(waker : * mut
|
||||||
|
hyper_waker) { drop(non_null!(Box::from_raw(waker) ?= ())); }
|
||||||
|
}
|
||||||
|
ffi_fn! {
|
||||||
|
#[doc = " Wake up the task associated with a waker."] #[doc = ""] #[doc =
|
||||||
|
" NOTE: This consumes the waker. You should not use or free the waker afterwards."]
|
||||||
|
fn hyper_waker_wake(waker : * mut hyper_waker) { let waker =
|
||||||
|
non_null!(Box::from_raw(waker) ?= ()); waker.waker.wake(); }
|
||||||
|
}
|
||||||
154
hyper/src/headers.rs
Normal file
154
hyper/src/headers.rs
Normal file
|
|
@ -0,0 +1,154 @@
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use http::header::CONTENT_LENGTH;
|
||||||
|
use http::header::{HeaderValue, ValueIter};
|
||||||
|
use http::HeaderMap;
|
||||||
|
#[cfg(all(feature = "http2", feature = "client"))]
|
||||||
|
use http::Method;
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn connection_keep_alive(value: &HeaderValue) -> bool {
|
||||||
|
connection_has(value, "keep-alive")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn connection_close(value: &HeaderValue) -> bool {
|
||||||
|
connection_has(value, "close")
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
fn connection_has(value: &HeaderValue, needle: &str) -> bool {
|
||||||
|
if let Ok(s) = value.to_str() {
|
||||||
|
for val in s.split(',') {
|
||||||
|
if val.trim().eq_ignore_ascii_case(needle) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "http1", feature = "server"))]
|
||||||
|
pub(super) fn content_length_parse(value: &HeaderValue) -> Option<u64> {
|
||||||
|
from_digits(value.as_bytes())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn content_length_parse_all(headers: &HeaderMap) -> Option<u64> {
|
||||||
|
content_length_parse_all_values(headers.get_all(CONTENT_LENGTH).into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn content_length_parse_all_values(values: ValueIter<'_, HeaderValue>) -> Option<u64> {
|
||||||
|
// If multiple Content-Length headers were sent, everything can still
|
||||||
|
// be alright if they all contain the same value, and all parse
|
||||||
|
// correctly. If not, then it's an error.
|
||||||
|
|
||||||
|
let mut content_length: Option<u64> = None;
|
||||||
|
for h in values {
|
||||||
|
if let Ok(line) = h.to_str() {
|
||||||
|
for v in line.split(',') {
|
||||||
|
if let Some(n) = from_digits(v.trim().as_bytes()) {
|
||||||
|
if content_length.is_none() {
|
||||||
|
content_length = Some(n)
|
||||||
|
} else if content_length != Some(n) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return content_length
|
||||||
|
}
|
||||||
|
|
||||||
|
fn from_digits(bytes: &[u8]) -> Option<u64> {
|
||||||
|
// cannot use FromStr for u64, since it allows a signed prefix
|
||||||
|
let mut result = 0u64;
|
||||||
|
const RADIX: u64 = 10;
|
||||||
|
|
||||||
|
if bytes.is_empty() {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
for &b in bytes {
|
||||||
|
// can't use char::to_digit, since we haven't verified these bytes
|
||||||
|
// are utf-8.
|
||||||
|
match b {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(result)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(all(feature = "http2", feature = "client"))]
|
||||||
|
pub(super) fn method_has_defined_payload_semantics(method: &Method) -> bool {
|
||||||
|
match *method {
|
||||||
|
Method::GET | Method::HEAD | Method::DELETE | Method::CONNECT => false,
|
||||||
|
_ => true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(super) fn set_content_length_if_missing(headers: &mut HeaderMap, len: u64) {
|
||||||
|
headers
|
||||||
|
.entry(CONTENT_LENGTH)
|
||||||
|
.or_insert_with(|| HeaderValue::from(len));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn transfer_encoding_is_chunked(headers: &HeaderMap) -> bool {
|
||||||
|
is_chunked(headers.get_all(http::header::TRANSFER_ENCODING).into_iter())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn is_chunked(mut encodings: ValueIter<'_, HeaderValue>) -> bool {
|
||||||
|
// chunked must always be the last encoding, according to spec
|
||||||
|
if let Some(line) = encodings.next_back() {
|
||||||
|
return is_chunked_(line);
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn is_chunked_(value: &HeaderValue) -> bool {
|
||||||
|
// chunked must always be the last encoding, according to spec
|
||||||
|
if let Ok(s) = value.to_str() {
|
||||||
|
if let Some(encoding) = s.rsplit(',').next() {
|
||||||
|
return encoding.trim().eq_ignore_ascii_case("chunked");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(super) fn add_chunked(mut entry: http::header::OccupiedEntry<'_, HeaderValue>) {
|
||||||
|
const CHUNKED: &str = "chunked";
|
||||||
|
|
||||||
|
if let Some(line) = entry.iter_mut().next_back() {
|
||||||
|
// + 2 for ", "
|
||||||
|
let new_cap = line.as_bytes().len() + CHUNKED.len() + 2;
|
||||||
|
let mut buf = BytesMut::with_capacity(new_cap);
|
||||||
|
buf.extend_from_slice(line.as_bytes());
|
||||||
|
buf.extend_from_slice(b", ");
|
||||||
|
buf.extend_from_slice(CHUNKED.as_bytes());
|
||||||
|
|
||||||
|
*line = HeaderValue::from_maybe_shared(buf.freeze())
|
||||||
|
.expect("original header value plus ascii is valid");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
entry.insert(HeaderValue::from_static(CHUNKED));
|
||||||
|
}
|
||||||
90
hyper/src/lib.rs
Normal file
90
hyper/src/lib.rs
Normal file
|
|
@ -0,0 +1,90 @@
|
||||||
|
#![deny(missing_docs)]
|
||||||
|
#![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))]
|
||||||
|
#![cfg_attr(all(test, feature = "nightly"), feature(test))]
|
||||||
|
#![cfg_attr(docsrs, feature(doc_cfg))]
|
||||||
|
//! # hyper
|
||||||
|
//!
|
||||||
|
//! hyper is a **fast** and **correct** HTTP implementation written in and for Rust.
|
||||||
|
//!
|
||||||
|
//! ## Features
|
||||||
|
//!
|
||||||
|
//! - HTTP/1 and HTTP/2
|
||||||
|
//! - Asynchronous design
|
||||||
|
//! - Leading in performance
|
||||||
|
//! - Tested and **correct**
|
||||||
|
//! - Extensive production use
|
||||||
|
//! - [Client](client/index.html) and [Server](server/index.html) APIs
|
||||||
|
//!
|
||||||
|
//! If just starting out, **check out the [Guides](https://hyper.rs/guides)
|
||||||
|
//! first.**
|
||||||
|
//!
|
||||||
|
//! ## "Low-level"
|
||||||
|
//!
|
||||||
|
//! hyper is a lower-level HTTP library, meant to be a building block
|
||||||
|
//! for libraries and applications.
|
||||||
|
//!
|
||||||
|
//! If looking for just a convenient HTTP client, consider the
|
||||||
|
//! [reqwest](https://crates.io/crates/reqwest) crate.
|
||||||
|
//!
|
||||||
|
//! # Optional Features
|
||||||
|
//!
|
||||||
|
//! hyper uses a set of [feature flags] to reduce the amount of compiled code.
|
||||||
|
//! It is possible to just enable certain features over others. By default,
|
||||||
|
//! hyper does not enable any features but allows one to enable a subset for
|
||||||
|
//! their use case. Below is a list of the available feature flags. You may
|
||||||
|
//! also notice above each function, struct and trait there is listed one or
|
||||||
|
//! more feature flags that are required for that item to be used.
|
||||||
|
//!
|
||||||
|
//! If you are new to hyper it is possible to enable the `full` feature flag
|
||||||
|
//! which will enable all public APIs. Beware though that this will pull in
|
||||||
|
//! many extra dependencies that you may not need.
|
||||||
|
//!
|
||||||
|
//! The following optional features are available:
|
||||||
|
//!
|
||||||
|
//! - `http1`: Enables HTTP/1 support.
|
||||||
|
//! - `http2`: Enables HTTP/2 support.
|
||||||
|
//! - `client`: Enables the HTTP `client`.
|
||||||
|
//! - `server`: Enables the HTTP `server`.
|
||||||
|
//! - `runtime`: Enables convenient integration with `tokio`, providing
|
||||||
|
//! connectors and acceptors for TCP, and a default executor.
|
||||||
|
//! - `tcp`: Enables convenient implementations over TCP (using tokio).
|
||||||
|
//! - `stream`: Provides `futures::Stream` capabilities.
|
||||||
|
//!
|
||||||
|
//! [feature flags]: https://doc.rust-lang.org/cargo/reference/manifest.html#the-features-section
|
||||||
|
#[doc(hidden)]
|
||||||
|
pub use http;
|
||||||
|
#[cfg(all(test, feature = "nightly"))]
|
||||||
|
extern crate test;
|
||||||
|
pub use crate::http::{header, Method, Request, Response, StatusCode, Uri, Version};
|
||||||
|
#[doc(no_inline)]
|
||||||
|
pub use crate::http::HeaderMap;
|
||||||
|
pub use crate::body::Body;
|
||||||
|
pub use crate::error::{Error, Result};
|
||||||
|
#[macro_use]
|
||||||
|
mod cfg;
|
||||||
|
#[macro_use]
|
||||||
|
mod common;
|
||||||
|
pub mod body;
|
||||||
|
mod error;
|
||||||
|
pub(crate) mod ext;
|
||||||
|
#[cfg(test)]
|
||||||
|
mod mock;
|
||||||
|
pub(crate) mod rt;
|
||||||
|
pub mod service;
|
||||||
|
pub(crate) mod upgrade;
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
pub(crate) mod ffi;
|
||||||
|
cfg_proto! {
|
||||||
|
mod headers; mod proto;
|
||||||
|
}
|
||||||
|
cfg_feature! {
|
||||||
|
#![feature = "client"] pub mod client; #[cfg(any(feature = "http1", feature =
|
||||||
|
"http2"))] #[doc(no_inline)] pub use crate ::client::Client;
|
||||||
|
}
|
||||||
|
cfg_feature! {
|
||||||
|
#![feature = "server"] pub mod server; #[doc(no_inline)] pub use crate
|
||||||
|
::server::Server;
|
||||||
|
}
|
||||||
235
hyper/src/mock.rs
Normal file
235
hyper/src/mock.rs
Normal file
|
|
@ -0,0 +1,235 @@
|
||||||
|
// FIXME: re-implement tests with `async/await`
|
||||||
|
/*
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
use std::cmp;
|
||||||
|
use std::io::{self, Read, Write};
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
|
||||||
|
use bytes::Buf;
|
||||||
|
use futures::{Async, Poll};
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use futures::Future;
|
||||||
|
use futures::task::{self, Task};
|
||||||
|
use tokio_io::{AsyncRead, AsyncWrite};
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use crate::client::connect::{Connect, Connected, Destination};
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub struct Duplex {
|
||||||
|
inner: Arc<Mutex<DuplexInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
struct DuplexInner {
|
||||||
|
handle_read_task: Option<Task>,
|
||||||
|
read: AsyncIo<MockCursor>,
|
||||||
|
write: AsyncIo<MockCursor>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl Duplex {
|
||||||
|
pub(crate) fn channel() -> (Duplex, DuplexHandle) {
|
||||||
|
let mut inner = DuplexInner {
|
||||||
|
handle_read_task: None,
|
||||||
|
read: AsyncIo::new_buf(Vec::new(), 0),
|
||||||
|
write: AsyncIo::new_buf(Vec::new(), std::usize::MAX),
|
||||||
|
};
|
||||||
|
|
||||||
|
inner.read.park_tasks(true);
|
||||||
|
inner.write.park_tasks(true);
|
||||||
|
|
||||||
|
let inner = Arc::new(Mutex::new(inner));
|
||||||
|
|
||||||
|
let duplex = Duplex {
|
||||||
|
inner: inner.clone(),
|
||||||
|
};
|
||||||
|
let handle = DuplexHandle {
|
||||||
|
inner: inner,
|
||||||
|
};
|
||||||
|
|
||||||
|
(duplex, handle)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl Read for Duplex {
|
||||||
|
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
|
||||||
|
self.inner.lock().unwrap().read.read(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl Write for Duplex {
|
||||||
|
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
let ret = inner.write.write(buf);
|
||||||
|
if let Some(task) = inner.handle_read_task.take() {
|
||||||
|
trace!("waking DuplexHandle read");
|
||||||
|
task.notify();
|
||||||
|
}
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn flush(&mut self) -> io::Result<()> {
|
||||||
|
self.inner.lock().unwrap().write.flush()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl AsyncRead for Duplex {
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl AsyncWrite for Duplex {
|
||||||
|
fn shutdown(&mut self) -> Poll<(), io::Error> {
|
||||||
|
Ok(().into())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_buf<B: Buf>(&mut self, buf: &mut B) -> Poll<usize, io::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
if let Some(task) = inner.handle_read_task.take() {
|
||||||
|
task.notify();
|
||||||
|
}
|
||||||
|
inner.write.write_buf(buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub struct DuplexHandle {
|
||||||
|
inner: Arc<Mutex<DuplexInner>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl DuplexHandle {
|
||||||
|
pub fn read(&self, buf: &mut [u8]) -> Poll<usize, io::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
assert!(buf.len() >= inner.write.inner.len());
|
||||||
|
if inner.write.inner.is_empty() {
|
||||||
|
trace!("DuplexHandle read parking");
|
||||||
|
inner.handle_read_task = Some(task::current());
|
||||||
|
return Ok(Async::NotReady);
|
||||||
|
}
|
||||||
|
inner.write.read(buf).map(Async::Ready)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write(&self, bytes: &[u8]) -> Poll<usize, io::Error> {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
assert_eq!(inner.read.inner.pos, 0);
|
||||||
|
assert_eq!(inner.read.inner.vec.len(), 0, "write but read isn't empty");
|
||||||
|
inner
|
||||||
|
.read
|
||||||
|
.inner
|
||||||
|
.vec
|
||||||
|
.extend(bytes);
|
||||||
|
inner.read.block_in(bytes.len());
|
||||||
|
Ok(Async::Ready(bytes.len()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl Drop for DuplexHandle {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
trace!("mock duplex handle drop");
|
||||||
|
if !::std::thread::panicking() {
|
||||||
|
let mut inner = self.inner.lock().unwrap();
|
||||||
|
inner.read.close();
|
||||||
|
inner.write.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
type BoxedConnectFut = Box<dyn Future<Item=(Duplex, Connected), Error=io::Error> + Send>;
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct MockConnector {
|
||||||
|
mocks: Arc<Mutex<MockedConnections>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
struct MockedConnections(HashMap<String, Vec<BoxedConnectFut>>);
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl MockConnector {
|
||||||
|
pub fn new() -> MockConnector {
|
||||||
|
MockConnector {
|
||||||
|
mocks: Arc::new(Mutex::new(MockedConnections(HashMap::new()))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mock(&mut self, key: &str) -> DuplexHandle {
|
||||||
|
use futures::future;
|
||||||
|
self.mock_fut(key, future::ok::<_, ()>(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mock_fut<F>(&mut self, key: &str, fut: F) -> DuplexHandle
|
||||||
|
where
|
||||||
|
F: Future + Send + 'static,
|
||||||
|
{
|
||||||
|
self.mock_opts(key, Connected::new(), fut)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn mock_opts<F>(&mut self, key: &str, connected: Connected, fut: F) -> DuplexHandle
|
||||||
|
where
|
||||||
|
F: Future + Send + 'static,
|
||||||
|
{
|
||||||
|
let key = key.to_owned();
|
||||||
|
|
||||||
|
let (duplex, handle) = Duplex::channel();
|
||||||
|
|
||||||
|
let fut = Box::new(fut.then(move |_| {
|
||||||
|
trace!("MockConnector mocked fut ready");
|
||||||
|
Ok((duplex, connected))
|
||||||
|
}));
|
||||||
|
self.mocks.lock().unwrap().0.entry(key)
|
||||||
|
.or_insert(Vec::new())
|
||||||
|
.push(fut);
|
||||||
|
|
||||||
|
handle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl Connect for MockConnector {
|
||||||
|
type Transport = Duplex;
|
||||||
|
type Error = io::Error;
|
||||||
|
type Future = BoxedConnectFut;
|
||||||
|
|
||||||
|
fn connect(&self, dst: Destination) -> Self::Future {
|
||||||
|
trace!("mock connect: {:?}", dst);
|
||||||
|
let key = format!("{}://{}{}", dst.scheme(), dst.host(), if let Some(port) = dst.port() {
|
||||||
|
format!(":{}", port)
|
||||||
|
} else {
|
||||||
|
"".to_owned()
|
||||||
|
});
|
||||||
|
let mut mocks = self.mocks.lock().unwrap();
|
||||||
|
let mocks = mocks.0.get_mut(&key)
|
||||||
|
.expect(&format!("unknown mocks uri: {}", key));
|
||||||
|
assert!(!mocks.is_empty(), "no additional mocks for {}", key);
|
||||||
|
mocks.remove(0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl Drop for MockedConnections {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if !::std::thread::panicking() {
|
||||||
|
for (key, mocks) in self.0.iter() {
|
||||||
|
assert_eq!(
|
||||||
|
mocks.len(),
|
||||||
|
0,
|
||||||
|
"not all mocked connects for {:?} were used",
|
||||||
|
key,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
*/
|
||||||
1425
hyper/src/proto/h1/conn.rs
Normal file
1425
hyper/src/proto/h1/conn.rs
Normal file
File diff suppressed because it is too large
Load diff
731
hyper/src/proto/h1/decode.rs
Normal file
731
hyper/src/proto/h1/decode.rs
Normal file
|
|
@ -0,0 +1,731 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
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};
|
||||||
|
|
||||||
|
/// Decoders to handle different Transfer-Encodings.
|
||||||
|
///
|
||||||
|
/// If a message body does not include a Transfer-Encoding, it *should*
|
||||||
|
/// include a Content-Length header.
|
||||||
|
#[derive(Clone, PartialEq)]
|
||||||
|
pub(crate) struct Decoder {
|
||||||
|
kind: Kind,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
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),
|
||||||
|
/// A Reader used for responses that don't indicate a length or chunked.
|
||||||
|
///
|
||||||
|
/// The bool tracks when EOF is seen on the transport.
|
||||||
|
///
|
||||||
|
/// Note: This should only used for `Response`s. It is illegal for a
|
||||||
|
/// `Request` to be made with both `Content-Length` and
|
||||||
|
/// `Transfer-Encoding: chunked` missing, as explained from the spec:
|
||||||
|
///
|
||||||
|
/// > If a Transfer-Encoding header field is present in a response and
|
||||||
|
/// > the chunked transfer coding is not the final encoding, the
|
||||||
|
/// > message body length is determined by reading the connection until
|
||||||
|
/// > it is closed by the server. If a Transfer-Encoding header field
|
||||||
|
/// > is present in a request and the chunked transfer coding is not
|
||||||
|
/// > the final encoding, the message body length cannot be determined
|
||||||
|
/// > reliably; the server MUST respond with the 400 (Bad Request)
|
||||||
|
/// > status code and then close the connection.
|
||||||
|
Eof(bool),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone, Copy)]
|
||||||
|
enum ChunkedState {
|
||||||
|
Size,
|
||||||
|
SizeLws,
|
||||||
|
Extension,
|
||||||
|
SizeLf,
|
||||||
|
Body,
|
||||||
|
BodyCr,
|
||||||
|
BodyLf,
|
||||||
|
Trailer,
|
||||||
|
TrailerLf,
|
||||||
|
EndCr,
|
||||||
|
EndLf,
|
||||||
|
End,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Decoder {
|
||||||
|
// constructors
|
||||||
|
|
||||||
|
pub(crate) fn length(x: u64) -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
kind: Kind::Length(x),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn chunked() -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
kind: Kind::Chunked(ChunkedState::Size, 0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn eof() -> Decoder {
|
||||||
|
Decoder {
|
||||||
|
kind: Kind::Eof(false),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn new(len: DecodedLength) -> Self {
|
||||||
|
match len {
|
||||||
|
DecodedLength::CHUNKED => Decoder::chunked(),
|
||||||
|
DecodedLength::CLOSE_DELIMITED => Decoder::eof(),
|
||||||
|
length => Decoder::length(length.danger_len()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// methods
|
||||||
|
|
||||||
|
pub(crate) fn is_eof(&self) -> bool {
|
||||||
|
matches!(self.kind, Length(0) | Chunked(ChunkedState::End, _) | Eof(true))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn decode<R: MemRead>(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
body: &mut R,
|
||||||
|
) -> Poll<Result<Bytes, io::Error>> {
|
||||||
|
trace!("decode; state={:?}", self.kind);
|
||||||
|
match self.kind {
|
||||||
|
Length(ref mut remaining) => {
|
||||||
|
if *remaining == 0 {
|
||||||
|
Poll::Ready(Ok(Bytes::new()))
|
||||||
|
} else {
|
||||||
|
let to_read = *remaining as usize;
|
||||||
|
let buf = ready!(body.read_mem(cx, to_read))?;
|
||||||
|
let num = buf.as_ref().len() as u64;
|
||||||
|
if num > *remaining {
|
||||||
|
*remaining = 0;
|
||||||
|
} else if num == 0 {
|
||||||
|
return Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
IncompleteBody,
|
||||||
|
)));
|
||||||
|
} else {
|
||||||
|
*remaining -= num;
|
||||||
|
}
|
||||||
|
Poll::Ready(Ok(buf))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Chunked(ref mut state, ref mut size) => {
|
||||||
|
loop {
|
||||||
|
let mut buf = None;
|
||||||
|
// advances the chunked state
|
||||||
|
*state = ready!(state.step(cx, body, size, &mut buf))?;
|
||||||
|
if *state == ChunkedState::End {
|
||||||
|
trace!("end of chunked");
|
||||||
|
return Poll::Ready(Ok(Bytes::new()));
|
||||||
|
}
|
||||||
|
if let Some(buf) = buf {
|
||||||
|
return Poll::Ready(Ok(buf));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Eof(ref mut is_eof) => {
|
||||||
|
if *is_eof {
|
||||||
|
Poll::Ready(Ok(Bytes::new()))
|
||||||
|
} else {
|
||||||
|
// 8192 chosen because its about 2 packets, there probably
|
||||||
|
// won't be that much available, so don't have MemReaders
|
||||||
|
// allocate buffers to big
|
||||||
|
body.read_mem(cx, 8192).map_ok(|slice| {
|
||||||
|
*is_eof = slice.is_empty();
|
||||||
|
slice
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
async fn decode_fut<R: MemRead>(&mut self, body: &mut R) -> Result<Bytes, io::Error> {
|
||||||
|
futures_util::future::poll_fn(move |cx| self.decode(cx, body)).await
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for Decoder {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
fmt::Debug::fmt(&self.kind, f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! byte (
|
||||||
|
($rdr:ident, $cx:expr) => ({
|
||||||
|
let buf = ready!($rdr.read_mem($cx, 1))?;
|
||||||
|
if !buf.is_empty() {
|
||||||
|
buf[0]
|
||||||
|
} else {
|
||||||
|
return Poll::Ready(Err(io::Error::new(io::ErrorKind::UnexpectedEof,
|
||||||
|
"unexpected EOF during chunk size line")));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
impl ChunkedState {
|
||||||
|
fn step<R: MemRead>(
|
||||||
|
&self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
body: &mut R,
|
||||||
|
size: &mut u64,
|
||||||
|
buf: &mut Option<Bytes>,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
use self::ChunkedState::*;
|
||||||
|
match *self {
|
||||||
|
Size => ChunkedState::read_size(cx, body, size),
|
||||||
|
SizeLws => ChunkedState::read_size_lws(cx, body),
|
||||||
|
Extension => ChunkedState::read_extension(cx, body),
|
||||||
|
SizeLf => ChunkedState::read_size_lf(cx, body, *size),
|
||||||
|
Body => ChunkedState::read_body(cx, body, size, buf),
|
||||||
|
BodyCr => ChunkedState::read_body_cr(cx, body),
|
||||||
|
BodyLf => ChunkedState::read_body_lf(cx, body),
|
||||||
|
Trailer => ChunkedState::read_trailer(cx, body),
|
||||||
|
TrailerLf => ChunkedState::read_trailer_lf(cx, body),
|
||||||
|
EndCr => ChunkedState::read_end_cr(cx, body),
|
||||||
|
EndLf => ChunkedState::read_end_lf(cx, body),
|
||||||
|
End => Poll::Ready(Ok(ChunkedState::End)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_size<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
size: &mut u64,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
trace!("Read chunk hex size");
|
||||||
|
|
||||||
|
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));
|
||||||
|
}
|
||||||
|
b'\t' | b' ' => return Poll::Ready(Ok(ChunkedState::SizeLws)),
|
||||||
|
b';' => return Poll::Ready(Ok(ChunkedState::Extension)),
|
||||||
|
b'\r' => return Poll::Ready(Ok(ChunkedState::SizeLf)),
|
||||||
|
_ => {
|
||||||
|
return Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size line: Invalid Size",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(Ok(ChunkedState::Size))
|
||||||
|
}
|
||||||
|
fn read_size_lws<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
trace!("read_size_lws");
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
// LWS can follow the chunk size, but no more digits can come
|
||||||
|
b'\t' | b' ' => Poll::Ready(Ok(ChunkedState::SizeLws)),
|
||||||
|
b';' => Poll::Ready(Ok(ChunkedState::Extension)),
|
||||||
|
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
|
||||||
|
_ => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size linear white space",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_extension<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
trace!("read_extension");
|
||||||
|
// We don't care about extensions really at all. Just ignore them.
|
||||||
|
// They "end" at the next CRLF.
|
||||||
|
//
|
||||||
|
// However, some implementations may not check for the CR, so to save
|
||||||
|
// them from themselves, we reject extensions containing plain LF as
|
||||||
|
// well.
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\r' => Poll::Ready(Ok(ChunkedState::SizeLf)),
|
||||||
|
b'\n' => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidData,
|
||||||
|
"invalid chunk extension contains newline",
|
||||||
|
))),
|
||||||
|
_ => Poll::Ready(Ok(ChunkedState::Extension)), // no supported extensions
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_size_lf<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
size: u64,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
trace!("Chunk size is {:?}", size);
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\n' => {
|
||||||
|
if size == 0 {
|
||||||
|
Poll::Ready(Ok(ChunkedState::EndCr))
|
||||||
|
} else {
|
||||||
|
debug!("incoming chunked header: {0:#X} ({0} bytes)", size);
|
||||||
|
Poll::Ready(Ok(ChunkedState::Body))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk size LF",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_body<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
rem: &mut u64,
|
||||||
|
buf: &mut Option<Bytes>,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
trace!("Chunked read, remaining={:?}", rem);
|
||||||
|
|
||||||
|
// cap remaining bytes at the max capacity of usize
|
||||||
|
let rem_cap = match *rem {
|
||||||
|
r if r > usize::MAX as u64 => usize::MAX,
|
||||||
|
r => r as usize,
|
||||||
|
};
|
||||||
|
|
||||||
|
let to_read = rem_cap;
|
||||||
|
let slice = ready!(rdr.read_mem(cx, to_read))?;
|
||||||
|
let count = slice.len();
|
||||||
|
|
||||||
|
if count == 0 {
|
||||||
|
*rem = 0;
|
||||||
|
return Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::UnexpectedEof,
|
||||||
|
IncompleteBody,
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
*buf = Some(slice);
|
||||||
|
*rem -= count as u64;
|
||||||
|
|
||||||
|
if *rem > 0 {
|
||||||
|
Poll::Ready(Ok(ChunkedState::Body))
|
||||||
|
} else {
|
||||||
|
Poll::Ready(Ok(ChunkedState::BodyCr))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_body_cr<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\r' => Poll::Ready(Ok(ChunkedState::BodyLf)),
|
||||||
|
_ => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk body CR",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_body_lf<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\n' => Poll::Ready(Ok(ChunkedState::Size)),
|
||||||
|
_ => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk body LF",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_trailer<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
trace!("read_trailer");
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\r' => Poll::Ready(Ok(ChunkedState::TrailerLf)),
|
||||||
|
_ => Poll::Ready(Ok(ChunkedState::Trailer)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_trailer_lf<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\n' => Poll::Ready(Ok(ChunkedState::EndCr)),
|
||||||
|
_ => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid trailer end LF",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_end_cr<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\r' => Poll::Ready(Ok(ChunkedState::EndLf)),
|
||||||
|
_ => Poll::Ready(Ok(ChunkedState::Trailer)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn read_end_lf<R: MemRead>(
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
rdr: &mut R,
|
||||||
|
) -> Poll<Result<ChunkedState, io::Error>> {
|
||||||
|
match byte!(rdr, cx) {
|
||||||
|
b'\n' => Poll::Ready(Ok(ChunkedState::End)),
|
||||||
|
_ => Poll::Ready(Err(io::Error::new(
|
||||||
|
io::ErrorKind::InvalidInput,
|
||||||
|
"Invalid chunk end LF",
|
||||||
|
))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
struct IncompleteBody;
|
||||||
|
|
||||||
|
impl fmt::Display for IncompleteBody {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "end of file before message length reached")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StdError for IncompleteBody {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::io::{AsyncRead, ReadBuf};
|
||||||
|
|
||||||
|
impl<'a> MemRead for &'a [u8] {
|
||||||
|
fn read_mem(&mut self, _: &mut task::Context<'_>, len: usize) -> Poll<io::Result<Bytes>> {
|
||||||
|
let n = std::cmp::min(len, self.len());
|
||||||
|
if n > 0 {
|
||||||
|
let (a, b) = self.split_at(n);
|
||||||
|
let buf = Bytes::copy_from_slice(a);
|
||||||
|
*self = b;
|
||||||
|
Poll::Ready(Ok(buf))
|
||||||
|
} else {
|
||||||
|
Poll::Ready(Ok(Bytes::new()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> MemRead for &'a mut (dyn AsyncRead + Unpin) {
|
||||||
|
fn read_mem(&mut self, cx: &mut task::Context<'_>, len: usize) -> Poll<io::Result<Bytes>> {
|
||||||
|
let mut v = vec![0; len];
|
||||||
|
let mut buf = ReadBuf::new(&mut v);
|
||||||
|
ready!(Pin::new(self).poll_read(cx, &mut buf)?);
|
||||||
|
Poll::Ready(Ok(Bytes::copy_from_slice(&buf.filled())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
impl MemRead for Bytes {
|
||||||
|
fn read_mem(&mut self, _: &mut task::Context<'_>, len: usize) -> Poll<io::Result<Bytes>> {
|
||||||
|
let n = std::cmp::min(len, self.len());
|
||||||
|
let ret = self.split_to(n);
|
||||||
|
Poll::Ready(Ok(ret))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
use std::io;
|
||||||
|
use std::io::Write;
|
||||||
|
use super::Decoder;
|
||||||
|
use super::ChunkedState;
|
||||||
|
use futures::{Async, Poll};
|
||||||
|
use bytes::{BytesMut, Bytes};
|
||||||
|
use crate::mock::AsyncIo;
|
||||||
|
*/
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_chunk_size() {
|
||||||
|
use std::io::ErrorKind::{InvalidData, InvalidInput, UnexpectedEof};
|
||||||
|
|
||||||
|
async fn read(s: &str) -> u64 {
|
||||||
|
let mut state = ChunkedState::Size;
|
||||||
|
let rdr = &mut s.as_bytes();
|
||||||
|
let mut size = 0;
|
||||||
|
loop {
|
||||||
|
let result =
|
||||||
|
futures_util::future::poll_fn(|cx| state.step(cx, rdr, &mut size, &mut None))
|
||||||
|
.await;
|
||||||
|
let desc = format!("read_size failed for {:?}", s);
|
||||||
|
state = result.expect(desc.as_str());
|
||||||
|
if state == ChunkedState::Body || state == ChunkedState::EndCr {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
size
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn read_err(s: &str, expected_err: io::ErrorKind) {
|
||||||
|
let mut state = ChunkedState::Size;
|
||||||
|
let rdr = &mut s.as_bytes();
|
||||||
|
let mut size = 0;
|
||||||
|
loop {
|
||||||
|
let result =
|
||||||
|
futures_util::future::poll_fn(|cx| state.step(cx, rdr, &mut size, &mut None))
|
||||||
|
.await;
|
||||||
|
state = match result {
|
||||||
|
Ok(s) => s,
|
||||||
|
Err(e) => {
|
||||||
|
assert!(
|
||||||
|
expected_err == e.kind(),
|
||||||
|
"Reading {:?}, expected {:?}, but got {:?}",
|
||||||
|
s,
|
||||||
|
expected_err,
|
||||||
|
e.kind()
|
||||||
|
);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if state == ChunkedState::Body || state == ChunkedState::End {
|
||||||
|
panic!("Was Ok. Expected Err for {:?}", s);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_eq!(1, read("1\r\n").await);
|
||||||
|
assert_eq!(1, read("01\r\n").await);
|
||||||
|
assert_eq!(0, read("0\r\n").await);
|
||||||
|
assert_eq!(0, read("00\r\n").await);
|
||||||
|
assert_eq!(10, read("A\r\n").await);
|
||||||
|
assert_eq!(10, read("a\r\n").await);
|
||||||
|
assert_eq!(255, read("Ff\r\n").await);
|
||||||
|
assert_eq!(255, read("Ff \r\n").await);
|
||||||
|
// Missing LF or CRLF
|
||||||
|
read_err("F\rF", InvalidInput).await;
|
||||||
|
read_err("F", UnexpectedEof).await;
|
||||||
|
// Invalid hex digit
|
||||||
|
read_err("X\r\n", InvalidInput).await;
|
||||||
|
read_err("1X\r\n", InvalidInput).await;
|
||||||
|
read_err("-\r\n", InvalidInput).await;
|
||||||
|
read_err("-1\r\n", InvalidInput).await;
|
||||||
|
// Acceptable (if not fully valid) extensions do not influence the size
|
||||||
|
assert_eq!(1, read("1;extension\r\n").await);
|
||||||
|
assert_eq!(10, read("a;ext name=value\r\n").await);
|
||||||
|
assert_eq!(1, read("1;extension;extension2\r\n").await);
|
||||||
|
assert_eq!(1, read("1;;; ;\r\n").await);
|
||||||
|
assert_eq!(2, read("2; extension...\r\n").await);
|
||||||
|
assert_eq!(3, read("3 ; extension=123\r\n").await);
|
||||||
|
assert_eq!(3, read("3 ;\r\n").await);
|
||||||
|
assert_eq!(3, read("3 ; \r\n").await);
|
||||||
|
// Invalid extensions cause an error
|
||||||
|
read_err("1 invalid extension\r\n", InvalidInput).await;
|
||||||
|
read_err("1 A\r\n", InvalidInput).await;
|
||||||
|
read_err("1;no CRLF", UnexpectedEof).await;
|
||||||
|
read_err("1;reject\nnewlines\r\n", InvalidData).await;
|
||||||
|
// Overflow
|
||||||
|
read_err("f0000000000000003\r\n", InvalidData).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_sized_early_eof() {
|
||||||
|
let mut bytes = &b"foo bar"[..];
|
||||||
|
let mut decoder = Decoder::length(10);
|
||||||
|
assert_eq!(decoder.decode_fut(&mut bytes).await.unwrap().len(), 7);
|
||||||
|
let e = decoder.decode_fut(&mut bytes).await.unwrap_err();
|
||||||
|
assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_chunked_early_eof() {
|
||||||
|
let mut bytes = &b"\
|
||||||
|
9\r\n\
|
||||||
|
foo bar\
|
||||||
|
"[..];
|
||||||
|
let mut decoder = Decoder::chunked();
|
||||||
|
assert_eq!(decoder.decode_fut(&mut bytes).await.unwrap().len(), 7);
|
||||||
|
let e = decoder.decode_fut(&mut bytes).await.unwrap_err();
|
||||||
|
assert_eq!(e.kind(), io::ErrorKind::UnexpectedEof);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_chunked_single_read() {
|
||||||
|
let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\n"[..];
|
||||||
|
let buf = Decoder::chunked()
|
||||||
|
.decode_fut(&mut mock_buf)
|
||||||
|
.await
|
||||||
|
.expect("decode");
|
||||||
|
assert_eq!(16, buf.len());
|
||||||
|
let result = String::from_utf8(buf.as_ref().to_vec()).expect("decode String");
|
||||||
|
assert_eq!("1234567890abcdef", &result);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[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"[..];
|
||||||
|
let mut decoder = Decoder::chunked();
|
||||||
|
decoder.decode_fut(&mut mock_buf).await.expect("decode");
|
||||||
|
let e = decoder.decode_fut(&mut mock_buf).await.unwrap_err();
|
||||||
|
assert_eq!(e.kind(), io::ErrorKind::InvalidInput);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_chunked_after_eof() {
|
||||||
|
let mut mock_buf = &b"10\r\n1234567890abcdef\r\n0\r\n\r\n"[..];
|
||||||
|
let mut decoder = Decoder::chunked();
|
||||||
|
|
||||||
|
// normal read
|
||||||
|
let buf = decoder.decode_fut(&mut mock_buf).await.unwrap();
|
||||||
|
assert_eq!(16, buf.len());
|
||||||
|
let result = String::from_utf8(buf.as_ref().to_vec()).expect("decode String");
|
||||||
|
assert_eq!("1234567890abcdef", &result);
|
||||||
|
|
||||||
|
// eof read
|
||||||
|
let buf = decoder.decode_fut(&mut mock_buf).await.expect("decode");
|
||||||
|
assert_eq!(0, buf.len());
|
||||||
|
|
||||||
|
// ensure read after eof also returns eof
|
||||||
|
let buf = decoder.decode_fut(&mut mock_buf).await.expect("decode");
|
||||||
|
assert_eq!(0, buf.len());
|
||||||
|
}
|
||||||
|
|
||||||
|
// perform an async read using a custom buffer size and causing a blocking
|
||||||
|
// read at the specified byte
|
||||||
|
async fn read_async(mut decoder: Decoder, content: &[u8], block_at: usize) -> String {
|
||||||
|
let mut outs = Vec::new();
|
||||||
|
|
||||||
|
let mut ins = if block_at == 0 {
|
||||||
|
tokio_test::io::Builder::new()
|
||||||
|
.wait(Duration::from_millis(10))
|
||||||
|
.read(content)
|
||||||
|
.build()
|
||||||
|
} else {
|
||||||
|
tokio_test::io::Builder::new()
|
||||||
|
.read(&content[..block_at])
|
||||||
|
.wait(Duration::from_millis(10))
|
||||||
|
.read(&content[block_at..])
|
||||||
|
.build()
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut ins = &mut ins as &mut (dyn AsyncRead + Unpin);
|
||||||
|
|
||||||
|
loop {
|
||||||
|
let buf = decoder
|
||||||
|
.decode_fut(&mut ins)
|
||||||
|
.await
|
||||||
|
.expect("unexpected decode error");
|
||||||
|
if buf.is_empty() {
|
||||||
|
break; // eof
|
||||||
|
}
|
||||||
|
outs.extend(buf.as_ref());
|
||||||
|
}
|
||||||
|
|
||||||
|
String::from_utf8(outs).expect("decode String")
|
||||||
|
}
|
||||||
|
|
||||||
|
// iterate over the different ways that this async read could go.
|
||||||
|
// tests blocking a read at each byte along the content - The shotgun approach
|
||||||
|
async fn all_async_cases(content: &str, expected: &str, decoder: Decoder) {
|
||||||
|
let content_len = content.len();
|
||||||
|
for block_at in 0..content_len {
|
||||||
|
let actual = read_async(decoder.clone(), content.as_bytes(), block_at).await;
|
||||||
|
assert_eq!(expected, &actual) //, "Failed async. Blocking at {}", block_at);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_length_async() {
|
||||||
|
let content = "foobar";
|
||||||
|
all_async_cases(content, content, Decoder::length(content.len() as u64)).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_chunked_async() {
|
||||||
|
let content = "3\r\nfoo\r\n3\r\nbar\r\n0\r\n\r\n";
|
||||||
|
let expected = "foobar";
|
||||||
|
all_async_cases(content, expected, Decoder::chunked()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn test_read_eof_async() {
|
||||||
|
let content = "foobar";
|
||||||
|
all_async_cases(content, content, Decoder::eof()).await;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_decode_chunked_1kb(b: &mut test::Bencher) {
|
||||||
|
let rt = new_runtime();
|
||||||
|
|
||||||
|
const LEN: usize = 1024;
|
||||||
|
let mut vec = Vec::new();
|
||||||
|
vec.extend(format!("{:x}\r\n", LEN).as_bytes());
|
||||||
|
vec.extend(&[0; LEN][..]);
|
||||||
|
vec.extend(b"\r\n");
|
||||||
|
let content = Bytes::from(vec);
|
||||||
|
|
||||||
|
b.bytes = LEN as u64;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut decoder = Decoder::chunked();
|
||||||
|
rt.block_on(async {
|
||||||
|
let mut raw = content.clone();
|
||||||
|
let chunk = decoder.decode_fut(&mut raw).await.unwrap();
|
||||||
|
assert_eq!(chunk.len(), LEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
#[bench]
|
||||||
|
fn bench_decode_length_1kb(b: &mut test::Bencher) {
|
||||||
|
let rt = new_runtime();
|
||||||
|
|
||||||
|
const LEN: usize = 1024;
|
||||||
|
let content = Bytes::from(&[0; LEN][..]);
|
||||||
|
b.bytes = LEN as u64;
|
||||||
|
|
||||||
|
b.iter(|| {
|
||||||
|
let mut decoder = Decoder::length(LEN as u64);
|
||||||
|
rt.block_on(async {
|
||||||
|
let mut raw = content.clone();
|
||||||
|
let chunk = decoder.decode_fut(&mut raw).await.unwrap();
|
||||||
|
assert_eq!(chunk.len(), LEN);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "nightly")]
|
||||||
|
fn new_runtime() -> tokio::runtime::Runtime {
|
||||||
|
tokio::runtime::Builder::new_current_thread()
|
||||||
|
.enable_all()
|
||||||
|
.build()
|
||||||
|
.expect("rt build")
|
||||||
|
}
|
||||||
|
}
|
||||||
750
hyper/src/proto/h1/dispatch.rs
Normal file
750
hyper/src/proto/h1/dispatch.rs
Normal file
|
|
@ -0,0 +1,750 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
|
use bytes::{Buf, Bytes};
|
||||||
|
use http::Request;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
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::upgrade::OnUpgrade;
|
||||||
|
|
||||||
|
pub(crate) struct Dispatcher<D, Bs: HttpBody, I, T> {
|
||||||
|
conn: Conn<I, Bs::Data, T>,
|
||||||
|
dispatch: D,
|
||||||
|
body_tx: Option<crate::body::Sender>,
|
||||||
|
body_rx: Pin<Box<Option<Bs>>>,
|
||||||
|
is_closing: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Dispatch {
|
||||||
|
type PollItem;
|
||||||
|
type PollBody;
|
||||||
|
type PollError;
|
||||||
|
type RecvItem;
|
||||||
|
fn poll_msg(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<(Self::PollItem, Self::PollBody), Self::PollError>>>;
|
||||||
|
fn recv_msg(&mut self, msg: crate::Result<(Self::RecvItem, Body)>) -> crate::Result<()>;
|
||||||
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), ()>>;
|
||||||
|
fn should_poll(&self) -> bool;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_server! {
|
||||||
|
use crate::service::HttpService;
|
||||||
|
|
||||||
|
pub(crate) struct Server<S: HttpService<B>, B> {
|
||||||
|
in_flight: Pin<Box<Option<S::Future>>>,
|
||||||
|
pub(crate) service: S,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_client! {
|
||||||
|
pin_project_lite::pin_project! {
|
||||||
|
pub(crate) struct Client<B> {
|
||||||
|
callback: Option<crate::client::dispatch::Callback<Request<B>, http::Response<Body>>>,
|
||||||
|
#[pin]
|
||||||
|
rx: ClientRx<B>,
|
||||||
|
rx_closed: bool,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ClientRx<B> = crate::client::dispatch::Receiver<Request<B>, http::Response<Body>>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Bs, I, T> Dispatcher<D, Bs, I, T>
|
||||||
|
where
|
||||||
|
D: Dispatch<
|
||||||
|
PollItem = MessageHead<T::Outgoing>,
|
||||||
|
PollBody = Bs,
|
||||||
|
RecvItem = MessageHead<T::Incoming>,
|
||||||
|
> + Unpin,
|
||||||
|
D::PollError: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
T: Http1Transaction + Unpin,
|
||||||
|
Bs: HttpBody + 'static,
|
||||||
|
Bs::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(dispatch: D, conn: Conn<I, Bs::Data, T>) -> Self {
|
||||||
|
Dispatcher {
|
||||||
|
conn,
|
||||||
|
dispatch,
|
||||||
|
body_tx: None,
|
||||||
|
body_rx: Box::pin(None),
|
||||||
|
is_closing: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(crate) fn disable_keep_alive(&mut self) {
|
||||||
|
self.conn.disable_keep_alive();
|
||||||
|
if self.conn.is_write_closed() {
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_inner(self) -> (I, Bytes, D) {
|
||||||
|
let (io, buf) = self.conn.into_inner();
|
||||||
|
(io, buf, self.dispatch)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Run this dispatcher until HTTP says this connection is done,
|
||||||
|
/// but don't call `AsyncWrite::shutdown` on the underlying IO.
|
||||||
|
///
|
||||||
|
/// 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<crate::Result<()>>
|
||||||
|
where
|
||||||
|
Self: Unpin,
|
||||||
|
{
|
||||||
|
Pin::new(self).poll_catch(cx, false).map_ok(|ds| {
|
||||||
|
if let Dispatched::Upgrade(pending) = ds {
|
||||||
|
pending.manual();
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_catch(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
should_shutdown: bool,
|
||||||
|
) -> Poll<crate::Result<Dispatched>> {
|
||||||
|
Poll::Ready(ready!(self.poll_inner(cx, should_shutdown)).or_else(|e| {
|
||||||
|
// An error means we're shutting down either way.
|
||||||
|
// We just try to give the error to the user,
|
||||||
|
// and close the connection with an Ok. If we
|
||||||
|
// cannot give it to the user, then return the Err.
|
||||||
|
self.dispatch.recv_msg(Err(e))?;
|
||||||
|
Ok(Dispatched::Shutdown)
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_inner(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
should_shutdown: bool,
|
||||||
|
) -> Poll<crate::Result<Dispatched>> {
|
||||||
|
T::update_date();
|
||||||
|
|
||||||
|
ready!(self.poll_loop(cx))?;
|
||||||
|
|
||||||
|
if self.is_done() {
|
||||||
|
if let Some(pending) = self.conn.pending_upgrade() {
|
||||||
|
self.conn.take_error()?;
|
||||||
|
return Poll::Ready(Ok(Dispatched::Upgrade(pending)));
|
||||||
|
} else if should_shutdown {
|
||||||
|
ready!(self.conn.poll_shutdown(cx)).map_err(crate::Error::new_shutdown)?;
|
||||||
|
}
|
||||||
|
self.conn.take_error()?;
|
||||||
|
Poll::Ready(Ok(Dispatched::Shutdown))
|
||||||
|
} else {
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_loop(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
// Limit the looping on this connection, in case it is ready far too
|
||||||
|
// often, so that other futures don't starve.
|
||||||
|
//
|
||||||
|
// 16 was chosen arbitrarily, as that is number of pipelined requests
|
||||||
|
// benchmarks often use. Perhaps it should be a config option instead.
|
||||||
|
for _ in 0..16 {
|
||||||
|
let _ = self.poll_read(cx)?;
|
||||||
|
let _ = self.poll_write(cx)?;
|
||||||
|
let _ = self.poll_flush(cx)?;
|
||||||
|
|
||||||
|
// This could happen if reading paused before blocking on IO,
|
||||||
|
// such as getting to the end of a framed message, but then
|
||||||
|
// writing/flushing set the state back to Init. In that case,
|
||||||
|
// if the read buffer still had bytes, we'd want to try poll_read
|
||||||
|
// again, or else we wouldn't ever be woken up again.
|
||||||
|
//
|
||||||
|
// Using this instead of task::current() and notify() inside
|
||||||
|
// the Conn is noticeably faster in pipelined benchmarks.
|
||||||
|
if !self.conn.wants_read_again() {
|
||||||
|
//break;
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("poll_loop yielding (self = {:p})", self);
|
||||||
|
|
||||||
|
task::yield_now(cx).map(|never| match never {})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_read(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
loop {
|
||||||
|
if self.is_closing {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
} else if self.conn.can_read_head() {
|
||||||
|
ready!(self.poll_read_head(cx))?;
|
||||||
|
} else if let Some(mut body) = self.body_tx.take() {
|
||||||
|
if self.conn.can_read_body() {
|
||||||
|
match body.poll_ready(cx) {
|
||||||
|
Poll::Ready(Ok(())) => (),
|
||||||
|
Poll::Pending => {
|
||||||
|
self.body_tx = Some(body);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(_canceled)) => {
|
||||||
|
// user doesn't care about the body
|
||||||
|
// so we should stop reading
|
||||||
|
trace!("body receiver dropped before eof, draining or closing");
|
||||||
|
self.conn.poll_drain_or_close_read(cx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match self.conn.poll_read_body(cx) {
|
||||||
|
Poll::Ready(Some(Ok(chunk))) => match body.try_send_data(chunk) {
|
||||||
|
Ok(()) => {
|
||||||
|
self.body_tx = Some(body);
|
||||||
|
}
|
||||||
|
Err(_canceled) => {
|
||||||
|
if self.conn.can_read_body() {
|
||||||
|
trace!("body receiver dropped before eof, closing");
|
||||||
|
self.conn.close_read();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
// just drop, the body will close automatically
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
self.body_tx = Some(body);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
Poll::Ready(Some(Err(e))) => {
|
||||||
|
body.send_error(crate::Error::new_body(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// just drop, the body will close automatically
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return self.conn.poll_read_keep_alive(cx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_read_head(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
// can dispatch receive, or does it still care about, an incoming message?
|
||||||
|
match ready!(self.dispatch.poll_ready(cx)) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(()) => {
|
||||||
|
trace!("dispatch no longer receiving messages");
|
||||||
|
self.close();
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// dispatch is ready for a message, try to read one
|
||||||
|
match ready!(self.conn.poll_read_head(cx)) {
|
||||||
|
Some(Ok((mut head, body_len, wants))) => {
|
||||||
|
let body = match body_len {
|
||||||
|
DecodedLength::ZERO => Body::empty(),
|
||||||
|
other => {
|
||||||
|
let (tx, rx) = Body::new_channel(other, wants.contains(Wants::EXPECT));
|
||||||
|
self.body_tx = Some(tx);
|
||||||
|
rx
|
||||||
|
}
|
||||||
|
};
|
||||||
|
if wants.contains(Wants::UPGRADE) {
|
||||||
|
let upgrade = self.conn.on_upgrade();
|
||||||
|
debug_assert!(!upgrade.is_none(), "empty upgrade");
|
||||||
|
debug_assert!(head.extensions.get::<OnUpgrade>().is_none(), "OnUpgrade already set");
|
||||||
|
head.extensions.insert(upgrade);
|
||||||
|
}
|
||||||
|
self.dispatch.recv_msg(Ok((head, body)))?;
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
Some(Err(err)) => {
|
||||||
|
debug!("read_head error: {}", err);
|
||||||
|
self.dispatch.recv_msg(Err(err))?;
|
||||||
|
// if here, the dispatcher gave the user the error
|
||||||
|
// somewhere else. we still need to shutdown, but
|
||||||
|
// not as a second error.
|
||||||
|
self.close();
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// read eof, the write side will have been closed too unless
|
||||||
|
// allow_read_close was set to true, in which case just do
|
||||||
|
// nothing...
|
||||||
|
debug_assert!(self.conn.is_read_closed());
|
||||||
|
if self.conn.is_write_closed() {
|
||||||
|
self.close();
|
||||||
|
}
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_write(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
loop {
|
||||||
|
if self.is_closing {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
} else if self.body_rx.is_none()
|
||||||
|
&& self.conn.can_write_head()
|
||||||
|
&& self.dispatch.should_poll()
|
||||||
|
{
|
||||||
|
if let Some(msg) = ready!(Pin::new(&mut self.dispatch).poll_msg(cx)) {
|
||||||
|
let (head, mut body) = msg.map_err(crate::Error::new_user_service)?;
|
||||||
|
|
||||||
|
// Check if the body knows its full data immediately.
|
||||||
|
//
|
||||||
|
// If so, we can skip a bit of bookkeeping that streaming
|
||||||
|
// bodies need to do.
|
||||||
|
if let Some(full) = crate::body::take_full_data(&mut body) {
|
||||||
|
self.conn.write_full_msg(head, full);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_type = if body.is_end_stream() {
|
||||||
|
self.body_rx.set(None);
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
let btype = body
|
||||||
|
.size_hint()
|
||||||
|
.exact()
|
||||||
|
.map(BodyLength::Known)
|
||||||
|
.or_else(|| Some(BodyLength::Unknown));
|
||||||
|
self.body_rx.set(Some(body));
|
||||||
|
btype
|
||||||
|
};
|
||||||
|
self.conn.write_head(head, body_type);
|
||||||
|
} else {
|
||||||
|
self.close();
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
} else if !self.conn.can_buffer_body() {
|
||||||
|
ready!(self.poll_flush(cx))?;
|
||||||
|
} else {
|
||||||
|
// A new scope is needed :(
|
||||||
|
if let (Some(mut body), clear_body) =
|
||||||
|
OptGuard::new(self.body_rx.as_mut()).guard_mut()
|
||||||
|
{
|
||||||
|
debug_assert!(!*clear_body, "opt guard defaults to keeping body");
|
||||||
|
if !self.conn.can_write_body() {
|
||||||
|
trace!(
|
||||||
|
"no more write body allowed, user body is_end_stream = {}",
|
||||||
|
body.is_end_stream(),
|
||||||
|
);
|
||||||
|
*clear_body = true;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
let item = ready!(body.as_mut().poll_data(cx));
|
||||||
|
if let Some(item) = item {
|
||||||
|
let chunk = item.map_err(|e| {
|
||||||
|
*clear_body = true;
|
||||||
|
crate::Error::new_user_body(e)
|
||||||
|
})?;
|
||||||
|
let eos = body.is_end_stream();
|
||||||
|
if eos {
|
||||||
|
*clear_body = true;
|
||||||
|
if chunk.remaining() == 0 {
|
||||||
|
trace!("discarding empty chunk");
|
||||||
|
self.conn.end_body()?;
|
||||||
|
} else {
|
||||||
|
self.conn.write_body_and_end(chunk);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if chunk.remaining() == 0 {
|
||||||
|
trace!("discarding empty chunk");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
self.conn.write_body(chunk);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
*clear_body = true;
|
||||||
|
self.conn.end_body()?;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(&mut self, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
self.conn.poll_flush(cx).map_err(|err| {
|
||||||
|
debug!("error writing: {}", err);
|
||||||
|
crate::Error::new_body_write(err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) {
|
||||||
|
self.is_closing = true;
|
||||||
|
self.conn.close_read();
|
||||||
|
self.conn.close_write();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_done(&self) -> bool {
|
||||||
|
if self.is_closing {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
let read_done = self.conn.is_read_closed();
|
||||||
|
|
||||||
|
if !T::should_read_first() && read_done {
|
||||||
|
// a client that cannot read may was well be done.
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
let write_done = self.conn.is_write_closed()
|
||||||
|
|| (!self.dispatch.should_poll() && self.body_rx.is_none());
|
||||||
|
read_done && write_done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<D, Bs, I, T> Future for Dispatcher<D, Bs, I, T>
|
||||||
|
where
|
||||||
|
D: Dispatch<
|
||||||
|
PollItem = MessageHead<T::Outgoing>,
|
||||||
|
PollBody = Bs,
|
||||||
|
RecvItem = MessageHead<T::Incoming>,
|
||||||
|
> + Unpin,
|
||||||
|
D::PollError: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
T: Http1Transaction + Unpin,
|
||||||
|
Bs: HttpBody + 'static,
|
||||||
|
Bs::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<Dispatched>;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.poll_catch(cx, true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl OptGuard =====
|
||||||
|
|
||||||
|
/// A drop guard to allow a mutable borrow of an Option while being able to
|
||||||
|
/// set whether the `Option` should be cleared on drop.
|
||||||
|
struct OptGuard<'a, T>(Pin<&'a mut Option<T>>, bool);
|
||||||
|
|
||||||
|
impl<'a, T> OptGuard<'a, T> {
|
||||||
|
fn new(pin: Pin<&'a mut Option<T>>) -> Self {
|
||||||
|
OptGuard(pin, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn guard_mut(&mut self) -> (Option<Pin<&mut T>>, &mut bool) {
|
||||||
|
(self.0.as_mut().as_pin_mut(), &mut self.1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a, T> Drop for OptGuard<'a, T> {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.1 {
|
||||||
|
self.0.set(None);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Server =====
|
||||||
|
|
||||||
|
cfg_server! {
|
||||||
|
impl<S, B> Server<S, B>
|
||||||
|
where
|
||||||
|
S: HttpService<B>,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(service: S) -> Server<S, B> {
|
||||||
|
Server {
|
||||||
|
in_flight: Box::pin(None),
|
||||||
|
service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn into_service(self) -> S {
|
||||||
|
self.service
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Service is never pinned
|
||||||
|
impl<S: HttpService<B>, B> Unpin for Server<S, B> {}
|
||||||
|
|
||||||
|
impl<S, Bs> Dispatch for Server<S, Body>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = Bs>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
Bs: HttpBody,
|
||||||
|
{
|
||||||
|
type PollItem = MessageHead<http::StatusCode>;
|
||||||
|
type PollBody = Bs;
|
||||||
|
type PollError = S::Error;
|
||||||
|
type RecvItem = RequestHead;
|
||||||
|
|
||||||
|
fn poll_msg(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<(Self::PollItem, Self::PollBody), Self::PollError>>> {
|
||||||
|
let mut this = self.as_mut();
|
||||||
|
let ret = if let Some(ref mut fut) = this.in_flight.as_mut().as_pin_mut() {
|
||||||
|
let resp = ready!(fut.as_mut().poll(cx)?);
|
||||||
|
let (parts, body) = resp.into_parts();
|
||||||
|
let head = MessageHead {
|
||||||
|
version: parts.version,
|
||||||
|
subject: parts.status,
|
||||||
|
headers: parts.headers,
|
||||||
|
extensions: parts.extensions,
|
||||||
|
};
|
||||||
|
Poll::Ready(Some(Ok((head, body))))
|
||||||
|
} else {
|
||||||
|
unreachable!("poll_msg shouldn't be called if no inflight");
|
||||||
|
};
|
||||||
|
|
||||||
|
// Since in_flight finished, remove it
|
||||||
|
this.in_flight.set(None);
|
||||||
|
ret
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv_msg(&mut self, msg: crate::Result<(Self::RecvItem, Body)>) -> crate::Result<()> {
|
||||||
|
let (msg, body) = msg?;
|
||||||
|
let mut req = Request::new(body);
|
||||||
|
*req.method_mut() = msg.subject.0;
|
||||||
|
*req.uri_mut() = msg.subject.1;
|
||||||
|
*req.headers_mut() = msg.headers;
|
||||||
|
*req.version_mut() = msg.version;
|
||||||
|
*req.extensions_mut() = msg.extensions;
|
||||||
|
let fut = self.service.call(req);
|
||||||
|
self.in_flight.set(Some(fut));
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), ()>> {
|
||||||
|
if self.in_flight.is_some() {
|
||||||
|
Poll::Pending
|
||||||
|
} else {
|
||||||
|
self.service.poll_ready(cx).map_err(|_e| {
|
||||||
|
// FIXME: return error value.
|
||||||
|
trace!("service closed");
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_poll(&self) -> bool {
|
||||||
|
self.in_flight.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Client =====
|
||||||
|
|
||||||
|
cfg_client! {
|
||||||
|
impl<B> Client<B> {
|
||||||
|
pub(crate) fn new(rx: ClientRx<B>) -> Client<B> {
|
||||||
|
Client {
|
||||||
|
callback: None,
|
||||||
|
rx,
|
||||||
|
rx_closed: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> Dispatch for Client<B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
type PollItem = RequestHead;
|
||||||
|
type PollBody = B;
|
||||||
|
type PollError = crate::common::Never;
|
||||||
|
type RecvItem = crate::proto::ResponseHead;
|
||||||
|
|
||||||
|
fn poll_msg(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<(Self::PollItem, Self::PollBody), crate::common::Never>>> {
|
||||||
|
let mut this = self.as_mut();
|
||||||
|
debug_assert!(!this.rx_closed);
|
||||||
|
match this.rx.poll_recv(cx) {
|
||||||
|
Poll::Ready(Some((req, mut cb))) => {
|
||||||
|
// check that future hasn't been canceled already
|
||||||
|
match cb.poll_canceled(cx) {
|
||||||
|
Poll::Ready(()) => {
|
||||||
|
trace!("request canceled");
|
||||||
|
Poll::Ready(None)
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
let (parts, body) = req.into_parts();
|
||||||
|
let head = RequestHead {
|
||||||
|
version: parts.version,
|
||||||
|
subject: crate::proto::RequestLine(parts.method, parts.uri),
|
||||||
|
headers: parts.headers,
|
||||||
|
extensions: parts.extensions,
|
||||||
|
};
|
||||||
|
this.callback = Some(cb);
|
||||||
|
Poll::Ready(Some(Ok((head, body))))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
// user has dropped sender handle
|
||||||
|
trace!("client tx closed");
|
||||||
|
this.rx_closed = true;
|
||||||
|
Poll::Ready(None)
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Pending,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn recv_msg(&mut self, msg: crate::Result<(Self::RecvItem, Body)>) -> crate::Result<()> {
|
||||||
|
match msg {
|
||||||
|
Ok((msg, body)) => {
|
||||||
|
if let Some(cb) = self.callback.take() {
|
||||||
|
let res = msg.into_response(body);
|
||||||
|
cb.send(Ok(res));
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
// Getting here is likely a bug! An error should have happened
|
||||||
|
// in Conn::require_empty_read() before ever parsing a
|
||||||
|
// full message!
|
||||||
|
Err(crate::Error::new_unexpected_message())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
if let Some(cb) = self.callback.take() {
|
||||||
|
cb.send(Err((err, None)));
|
||||||
|
Ok(())
|
||||||
|
} else if !self.rx_closed {
|
||||||
|
self.rx.close();
|
||||||
|
if let Some((req, cb)) = self.rx.try_recv() {
|
||||||
|
trace!("canceling queued request with connection error: {}", err);
|
||||||
|
// in this case, the message was never even started, so it's safe to tell
|
||||||
|
// the user that the request was completely canceled
|
||||||
|
cb.send(Err((crate::Error::new_canceled().with(err), Some(req))));
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), ()>> {
|
||||||
|
match self.callback {
|
||||||
|
Some(ref mut cb) => match cb.poll_canceled(cx) {
|
||||||
|
Poll::Ready(()) => {
|
||||||
|
trace!("callback receiver has dropped");
|
||||||
|
Poll::Ready(Err(()))
|
||||||
|
}
|
||||||
|
Poll::Pending => Poll::Ready(Ok(())),
|
||||||
|
},
|
||||||
|
None => Poll::Ready(Err(())),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_poll(&self) -> bool {
|
||||||
|
self.callback.is_none()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use crate::proto::h1::ClientTransaction;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn client_read_bytes_before_writing_request() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
tokio_test::task::spawn(()).enter(|cx, _| {
|
||||||
|
let (io, mut handle) = tokio_test::io::Builder::new().build_with_handle();
|
||||||
|
|
||||||
|
// Block at 0 for now, but we will release this response before
|
||||||
|
// the request is ready to write later...
|
||||||
|
let (mut tx, rx) = crate::client::dispatch::channel();
|
||||||
|
let conn = Conn::<_, bytes::Bytes, ClientTransaction>::new(io);
|
||||||
|
let mut dispatcher = Dispatcher::new(Client::new(rx), conn);
|
||||||
|
|
||||||
|
// First poll is needed to allow tx to send...
|
||||||
|
assert!(Pin::new(&mut dispatcher).poll(cx).is_pending());
|
||||||
|
|
||||||
|
// Unblock our IO, which has a response before we've sent request!
|
||||||
|
//
|
||||||
|
handle.read(b"HTTP/1.1 200 OK\r\n\r\n");
|
||||||
|
|
||||||
|
let mut res_rx = tx
|
||||||
|
.try_send(crate::Request::new(crate::Body::empty()))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
tokio_test::assert_ready_ok!(Pin::new(&mut dispatcher).poll(cx));
|
||||||
|
let err = tokio_test::assert_ready_ok!(Pin::new(&mut res_rx).poll(cx))
|
||||||
|
.expect_err("callback should send error");
|
||||||
|
|
||||||
|
match (err.0.kind(), err.1) {
|
||||||
|
(&crate::error::Kind::Canceled, Some(_)) => (),
|
||||||
|
other => panic!("expected Canceled, got {:?}", other),
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn client_flushing_is_not_ready_for_next_request() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let (io, _handle) = tokio_test::io::Builder::new()
|
||||||
|
.write(b"POST / HTTP/1.1\r\ncontent-length: 4\r\n\r\n")
|
||||||
|
.read(b"HTTP/1.1 200 OK\r\ncontent-length: 0\r\n\r\n")
|
||||||
|
.wait(std::time::Duration::from_secs(2))
|
||||||
|
.build_with_handle();
|
||||||
|
|
||||||
|
let (mut tx, rx) = crate::client::dispatch::channel();
|
||||||
|
let mut conn = Conn::<_, bytes::Bytes, ClientTransaction>::new(io);
|
||||||
|
conn.set_write_strategy_queue();
|
||||||
|
|
||||||
|
let dispatcher = Dispatcher::new(Client::new(rx), conn);
|
||||||
|
let _dispatcher = tokio::spawn(async move { dispatcher.await });
|
||||||
|
|
||||||
|
let req = crate::Request::builder()
|
||||||
|
.method("POST")
|
||||||
|
.body(crate::Body::from("reee"))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
let res = tx.try_send(req).unwrap().await.expect("response");
|
||||||
|
drop(res);
|
||||||
|
|
||||||
|
assert!(!tx.is_ready());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[tokio::test]
|
||||||
|
async fn body_empty_chunks_ignored() {
|
||||||
|
let _ = pretty_env_logger::try_init();
|
||||||
|
|
||||||
|
let io = tokio_test::io::Builder::new()
|
||||||
|
// no reading or writing, just be blocked for the test...
|
||||||
|
.wait(Duration::from_secs(5))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
let (mut tx, rx) = crate::client::dispatch::channel();
|
||||||
|
let conn = Conn::<_, bytes::Bytes, ClientTransaction>::new(io);
|
||||||
|
let mut dispatcher = tokio_test::task::spawn(Dispatcher::new(Client::new(rx), conn));
|
||||||
|
|
||||||
|
// First poll is needed to allow tx to send...
|
||||||
|
assert!(dispatcher.poll().is_pending());
|
||||||
|
|
||||||
|
let body = {
|
||||||
|
let (mut tx, body) = crate::Body::channel();
|
||||||
|
tx.try_send_data("".into()).unwrap();
|
||||||
|
body
|
||||||
|
};
|
||||||
|
|
||||||
|
let _res_rx = tx.try_send(crate::Request::new(body)).unwrap();
|
||||||
|
|
||||||
|
// Ensure conn.write_body wasn't called with the empty chunk.
|
||||||
|
// If it is, it will trigger an assertion.
|
||||||
|
assert!(dispatcher.poll().is_pending());
|
||||||
|
}
|
||||||
|
}
|
||||||
439
hyper/src/proto/h1/encode.rs
Normal file
439
hyper/src/proto/h1/encode.rs
Normal file
|
|
@ -0,0 +1,439 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::io::IoSlice;
|
||||||
|
|
||||||
|
use bytes::buf::{Chain, Take};
|
||||||
|
use bytes::Buf;
|
||||||
|
use tracing::trace;
|
||||||
|
|
||||||
|
use super::io::WriteBuf;
|
||||||
|
|
||||||
|
type StaticBuf = &'static [u8];
|
||||||
|
|
||||||
|
/// Encoders to handle different Transfer-Encodings.
|
||||||
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
|
pub(crate) struct Encoder {
|
||||||
|
kind: Kind,
|
||||||
|
is_last: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct EncodedBuf<B> {
|
||||||
|
kind: BufKind<B>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct NotEof(u64);
|
||||||
|
|
||||||
|
#[derive(Debug, PartialEq, Clone)]
|
||||||
|
enum Kind {
|
||||||
|
/// An Encoder for when Transfer-Encoding includes `chunked`.
|
||||||
|
Chunked,
|
||||||
|
/// An Encoder for when Content-Length is set.
|
||||||
|
///
|
||||||
|
/// Enforces that the body is not longer than the Content-Length header.
|
||||||
|
Length(u64),
|
||||||
|
/// An Encoder for when neither Content-Length nor Chunked encoding is set.
|
||||||
|
///
|
||||||
|
/// This is mostly only used with HTTP/1.0 with a length. This kind requires
|
||||||
|
/// the connection to be closed when the body is finished.
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
CloseDelimited,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
enum BufKind<B> {
|
||||||
|
Exact(B),
|
||||||
|
Limited(Take<B>),
|
||||||
|
Chunked(Chain<Chain<ChunkSize, B>, StaticBuf>),
|
||||||
|
ChunkedEnd(StaticBuf),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Encoder {
|
||||||
|
fn new(kind: Kind) -> Encoder {
|
||||||
|
Encoder {
|
||||||
|
kind,
|
||||||
|
is_last: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) fn chunked() -> Encoder {
|
||||||
|
Encoder::new(Kind::Chunked)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn length(len: u64) -> Encoder {
|
||||||
|
Encoder::new(Kind::Length(len))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(crate) fn close_delimited() -> Encoder {
|
||||||
|
Encoder::new(Kind::CloseDelimited)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_eof(&self) -> bool {
|
||||||
|
matches!(self.kind, Kind::Length(0))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(crate) fn set_last(mut self, is_last: bool) -> Self {
|
||||||
|
self.is_last = is_last;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_last(&self) -> bool {
|
||||||
|
self.is_last
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn is_close_delimited(&self) -> bool {
|
||||||
|
match self.kind {
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::CloseDelimited => true,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn end<B>(&self) -> Result<Option<EncodedBuf<B>>, NotEof> {
|
||||||
|
match self.kind {
|
||||||
|
Kind::Length(0) => Ok(None),
|
||||||
|
Kind::Chunked => Ok(Some(EncodedBuf {
|
||||||
|
kind: BufKind::ChunkedEnd(b"0\r\n\r\n"),
|
||||||
|
})),
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::CloseDelimited => Ok(None),
|
||||||
|
Kind::Length(n) => Err(NotEof(n)),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn encode<B>(&mut self, msg: B) -> EncodedBuf<B>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
let len = msg.remaining();
|
||||||
|
debug_assert!(len > 0, "encode() called with empty buf");
|
||||||
|
|
||||||
|
let kind = match self.kind {
|
||||||
|
Kind::Chunked => {
|
||||||
|
trace!("encoding chunked {}B", len);
|
||||||
|
let buf = ChunkSize::new(len)
|
||||||
|
.chain(msg)
|
||||||
|
.chain(b"\r\n" as &'static [u8]);
|
||||||
|
BufKind::Chunked(buf)
|
||||||
|
}
|
||||||
|
Kind::Length(ref mut remaining) => {
|
||||||
|
trace!("sized write, len = {}", len);
|
||||||
|
if len as u64 > *remaining {
|
||||||
|
let limit = *remaining as usize;
|
||||||
|
*remaining = 0;
|
||||||
|
BufKind::Limited(msg.take(limit))
|
||||||
|
} else {
|
||||||
|
*remaining -= len as u64;
|
||||||
|
BufKind::Exact(msg)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::CloseDelimited => {
|
||||||
|
trace!("close delimited write {}B", len);
|
||||||
|
BufKind::Exact(msg)
|
||||||
|
}
|
||||||
|
};
|
||||||
|
EncodedBuf { kind }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn encode_and_end<B>(&self, msg: B, dst: &mut WriteBuf<EncodedBuf<B>>) -> bool
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
let len = msg.remaining();
|
||||||
|
debug_assert!(len > 0, "encode() called with empty buf");
|
||||||
|
|
||||||
|
match self.kind {
|
||||||
|
Kind::Chunked => {
|
||||||
|
trace!("encoding chunked {}B", len);
|
||||||
|
let buf = ChunkSize::new(len)
|
||||||
|
.chain(msg)
|
||||||
|
.chain(b"\r\n0\r\n\r\n" as &'static [u8]);
|
||||||
|
dst.buffer(buf);
|
||||||
|
!self.is_last
|
||||||
|
}
|
||||||
|
Kind::Length(remaining) => {
|
||||||
|
use std::cmp::Ordering;
|
||||||
|
|
||||||
|
trace!("sized write, len = {}", len);
|
||||||
|
match (len as u64).cmp(&remaining) {
|
||||||
|
Ordering::Equal => {
|
||||||
|
dst.buffer(msg);
|
||||||
|
!self.is_last
|
||||||
|
}
|
||||||
|
Ordering::Greater => {
|
||||||
|
dst.buffer(msg.take(remaining as usize));
|
||||||
|
!self.is_last
|
||||||
|
}
|
||||||
|
Ordering::Less => {
|
||||||
|
dst.buffer(msg);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
Kind::CloseDelimited => {
|
||||||
|
trace!("close delimited write {}B", len);
|
||||||
|
dst.buffer(msg);
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Encodes the full body, without verifying the remaining length matches.
|
||||||
|
///
|
||||||
|
/// This is used in conjunction with HttpBody::__hyper_full_data(), which
|
||||||
|
/// means we can trust that the buf has the correct size (the buf itself
|
||||||
|
/// was checked to make the headers).
|
||||||
|
pub(super) fn danger_full_buf<B>(self, msg: B, dst: &mut WriteBuf<EncodedBuf<B>>)
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
debug_assert!(msg.remaining() > 0, "encode() called with empty buf");
|
||||||
|
debug_assert!(
|
||||||
|
match self.kind {
|
||||||
|
Kind::Length(len) => len == msg.remaining() as u64,
|
||||||
|
_ => true,
|
||||||
|
},
|
||||||
|
"danger_full_buf length mismatches"
|
||||||
|
);
|
||||||
|
|
||||||
|
match self.kind {
|
||||||
|
Kind::Chunked => {
|
||||||
|
let len = msg.remaining();
|
||||||
|
trace!("encoding chunked {}B", len);
|
||||||
|
let buf = ChunkSize::new(len)
|
||||||
|
.chain(msg)
|
||||||
|
.chain(b"\r\n0\r\n\r\n" as &'static [u8]);
|
||||||
|
dst.buffer(buf);
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
dst.buffer(msg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> Buf for EncodedBuf<B>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
#[inline]
|
||||||
|
fn remaining(&self) -> usize {
|
||||||
|
match self.kind {
|
||||||
|
BufKind::Exact(ref b) => b.remaining(),
|
||||||
|
BufKind::Limited(ref b) => b.remaining(),
|
||||||
|
BufKind::Chunked(ref b) => b.remaining(),
|
||||||
|
BufKind::ChunkedEnd(ref b) => b.remaining(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn chunk(&self) -> &[u8] {
|
||||||
|
match self.kind {
|
||||||
|
BufKind::Exact(ref b) => b.chunk(),
|
||||||
|
BufKind::Limited(ref b) => b.chunk(),
|
||||||
|
BufKind::Chunked(ref b) => b.chunk(),
|
||||||
|
BufKind::ChunkedEnd(ref b) => b.chunk(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn advance(&mut self, cnt: usize) {
|
||||||
|
match self.kind {
|
||||||
|
BufKind::Exact(ref mut b) => b.advance(cnt),
|
||||||
|
BufKind::Limited(ref mut b) => b.advance(cnt),
|
||||||
|
BufKind::Chunked(ref mut b) => b.advance(cnt),
|
||||||
|
BufKind::ChunkedEnd(ref mut b) => b.advance(cnt),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn chunks_vectored<'t>(&'t self, dst: &mut [IoSlice<'t>]) -> usize {
|
||||||
|
match self.kind {
|
||||||
|
BufKind::Exact(ref b) => b.chunks_vectored(dst),
|
||||||
|
BufKind::Limited(ref b) => b.chunks_vectored(dst),
|
||||||
|
BufKind::Chunked(ref b) => b.chunks_vectored(dst),
|
||||||
|
BufKind::ChunkedEnd(ref b) => b.chunks_vectored(dst),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "32")]
|
||||||
|
const USIZE_BYTES: usize = 4;
|
||||||
|
|
||||||
|
#[cfg(target_pointer_width = "64")]
|
||||||
|
const USIZE_BYTES: usize = 8;
|
||||||
|
|
||||||
|
// each byte will become 2 hex
|
||||||
|
const CHUNK_SIZE_MAX_BYTES: usize = USIZE_BYTES * 2;
|
||||||
|
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
struct ChunkSize {
|
||||||
|
bytes: [u8; CHUNK_SIZE_MAX_BYTES + 2],
|
||||||
|
pos: u8,
|
||||||
|
len: u8,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ChunkSize {
|
||||||
|
fn new(len: usize) -> ChunkSize {
|
||||||
|
use std::fmt::Write;
|
||||||
|
let mut size = ChunkSize {
|
||||||
|
bytes: [0; CHUNK_SIZE_MAX_BYTES + 2],
|
||||||
|
pos: 0,
|
||||||
|
len: 0,
|
||||||
|
};
|
||||||
|
write!(&mut size, "{:X}\r\n", len).expect("CHUNK_SIZE_MAX_BYTES should fit any usize");
|
||||||
|
size
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Buf for ChunkSize {
|
||||||
|
#[inline]
|
||||||
|
fn remaining(&self) -> usize {
|
||||||
|
(self.len - self.pos).into()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn chunk(&self) -> &[u8] {
|
||||||
|
&self.bytes[self.pos.into()..self.len.into()]
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn advance(&mut self, cnt: usize) {
|
||||||
|
assert!(cnt <= self.remaining());
|
||||||
|
self.pos += cnt as u8; // just asserted cnt fits in u8
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for ChunkSize {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("ChunkSize")
|
||||||
|
.field("bytes", &&self.bytes[..self.len.into()])
|
||||||
|
.field("pos", &self.pos)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Write for ChunkSize {
|
||||||
|
fn write_str(&mut self, num: &str) -> fmt::Result {
|
||||||
|
use std::io::Write;
|
||||||
|
(&mut self.bytes[self.len.into()..])
|
||||||
|
.write_all(num.as_bytes())
|
||||||
|
.expect("&mut [u8].write() cannot error");
|
||||||
|
self.len += num.len() as u8; // safe because bytes is never bigger than 256
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Buf> From<B> for EncodedBuf<B> {
|
||||||
|
fn from(buf: B) -> Self {
|
||||||
|
EncodedBuf {
|
||||||
|
kind: BufKind::Exact(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Buf> From<Take<B>> for EncodedBuf<B> {
|
||||||
|
fn from(buf: Take<B>) -> Self {
|
||||||
|
EncodedBuf {
|
||||||
|
kind: BufKind::Limited(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Buf> From<Chain<Chain<ChunkSize, B>, StaticBuf>> for EncodedBuf<B> {
|
||||||
|
fn from(buf: Chain<Chain<ChunkSize, B>, StaticBuf>) -> Self {
|
||||||
|
EncodedBuf {
|
||||||
|
kind: BufKind::Chunked(buf),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for NotEof {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
write!(f, "early end, expected {} more bytes", self.0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::error::Error for NotEof {}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bytes::BufMut;
|
||||||
|
|
||||||
|
use super::super::io::Cursor;
|
||||||
|
use super::Encoder;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn chunked() {
|
||||||
|
let mut encoder = Encoder::chunked();
|
||||||
|
let mut dst = Vec::new();
|
||||||
|
|
||||||
|
let msg1 = b"foo bar".as_ref();
|
||||||
|
let buf1 = encoder.encode(msg1);
|
||||||
|
dst.put(buf1);
|
||||||
|
assert_eq!(dst, b"7\r\nfoo bar\r\n");
|
||||||
|
|
||||||
|
let msg2 = b"baz quux herp".as_ref();
|
||||||
|
let buf2 = encoder.encode(msg2);
|
||||||
|
dst.put(buf2);
|
||||||
|
|
||||||
|
assert_eq!(dst, b"7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n");
|
||||||
|
|
||||||
|
let end = encoder.end::<Cursor<Vec<u8>>>().unwrap().unwrap();
|
||||||
|
dst.put(end);
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
dst,
|
||||||
|
b"7\r\nfoo bar\r\nD\r\nbaz quux herp\r\n0\r\n\r\n".as_ref()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn length() {
|
||||||
|
let max_len = 8;
|
||||||
|
let mut encoder = Encoder::length(max_len as u64);
|
||||||
|
let mut dst = Vec::new();
|
||||||
|
|
||||||
|
let msg1 = b"foo bar".as_ref();
|
||||||
|
let buf1 = encoder.encode(msg1);
|
||||||
|
dst.put(buf1);
|
||||||
|
|
||||||
|
assert_eq!(dst, b"foo bar");
|
||||||
|
assert!(!encoder.is_eof());
|
||||||
|
encoder.end::<()>().unwrap_err();
|
||||||
|
|
||||||
|
let msg2 = b"baz".as_ref();
|
||||||
|
let buf2 = encoder.encode(msg2);
|
||||||
|
dst.put(buf2);
|
||||||
|
|
||||||
|
assert_eq!(dst.len(), max_len);
|
||||||
|
assert_eq!(dst, b"foo barb");
|
||||||
|
assert!(encoder.is_eof());
|
||||||
|
assert!(encoder.end::<()>().unwrap().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn eof() {
|
||||||
|
let mut encoder = Encoder::close_delimited();
|
||||||
|
let mut dst = Vec::new();
|
||||||
|
|
||||||
|
let msg1 = b"foo bar".as_ref();
|
||||||
|
let buf1 = encoder.encode(msg1);
|
||||||
|
dst.put(buf1);
|
||||||
|
|
||||||
|
assert_eq!(dst, b"foo bar");
|
||||||
|
assert!(!encoder.is_eof());
|
||||||
|
encoder.end::<()>().unwrap();
|
||||||
|
|
||||||
|
let msg2 = b"baz".as_ref();
|
||||||
|
let buf2 = encoder.encode(msg2);
|
||||||
|
dst.put(buf2);
|
||||||
|
|
||||||
|
assert_eq!(dst, b"foo barbaz");
|
||||||
|
assert!(!encoder.is_eof());
|
||||||
|
encoder.end::<()>().unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
1002
hyper/src/proto/h1/io.rs
Normal file
1002
hyper/src/proto/h1/io.rs
Normal file
File diff suppressed because it is too large
Load diff
122
hyper/src/proto/h1/mod.rs
Normal file
122
hyper/src/proto/h1/mod.rs
Normal file
|
|
@ -0,0 +1,122 @@
|
||||||
|
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||||
|
use std::{pin::Pin, time::Duration};
|
||||||
|
|
||||||
|
use bytes::BytesMut;
|
||||||
|
use http::{HeaderMap, Method};
|
||||||
|
use httparse::ParserConfig;
|
||||||
|
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||||
|
use tokio::time::Sleep;
|
||||||
|
|
||||||
|
use crate::body::DecodedLength;
|
||||||
|
use crate::proto::{BodyLength, MessageHead};
|
||||||
|
|
||||||
|
pub(crate) use self::conn::Conn;
|
||||||
|
pub(crate) use self::decode::Decoder;
|
||||||
|
pub(crate) use self::dispatch::Dispatcher;
|
||||||
|
pub(crate) use self::encode::{EncodedBuf, Encoder};
|
||||||
|
//TODO: move out of h1::io
|
||||||
|
pub(crate) use self::io::MINIMUM_MAX_BUFFER_SIZE;
|
||||||
|
|
||||||
|
mod conn;
|
||||||
|
mod decode;
|
||||||
|
pub(crate) mod dispatch;
|
||||||
|
mod encode;
|
||||||
|
mod io;
|
||||||
|
mod role;
|
||||||
|
|
||||||
|
cfg_client! {
|
||||||
|
pub(crate) type ClientTransaction = role::Client;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_server! {
|
||||||
|
pub(crate) type ServerTransaction = role::Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) trait Http1Transaction {
|
||||||
|
type Incoming;
|
||||||
|
type Outgoing: Default;
|
||||||
|
const LOG: &'static str;
|
||||||
|
fn parse(bytes: &mut BytesMut, ctx: ParseContext<'_>) -> ParseResult<Self::Incoming>;
|
||||||
|
fn encode(enc: Encode<'_, Self::Outgoing>, dst: &mut Vec<u8>) -> crate::Result<Encoder>;
|
||||||
|
|
||||||
|
fn on_error(err: &crate::Error) -> Option<MessageHead<Self::Outgoing>>;
|
||||||
|
|
||||||
|
fn is_client() -> bool {
|
||||||
|
!Self::is_server()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_server() -> bool {
|
||||||
|
!Self::is_client()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_error_on_parse_eof() -> bool {
|
||||||
|
Self::is_client()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_read_first() -> bool {
|
||||||
|
Self::is_server()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_date() {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Result newtype for Http1Transaction::parse.
|
||||||
|
pub(crate) type ParseResult<T> = Result<Option<ParsedMessage<T>>, crate::error::Parse>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(crate) struct ParsedMessage<T> {
|
||||||
|
head: MessageHead<T>,
|
||||||
|
decode: DecodedLength,
|
||||||
|
expect_continue: bool,
|
||||||
|
keep_alive: bool,
|
||||||
|
wants_upgrade: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) struct ParseContext<'a> {
|
||||||
|
cached_headers: &'a mut Option<HeaderMap>,
|
||||||
|
req_method: &'a mut Option<Method>,
|
||||||
|
h1_parser_config: ParserConfig,
|
||||||
|
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||||
|
h1_header_read_timeout: Option<Duration>,
|
||||||
|
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||||
|
h1_header_read_timeout_fut: &'a mut Option<Pin<Box<Sleep>>>,
|
||||||
|
#[cfg(all(feature = "server", feature = "runtime"))]
|
||||||
|
h1_header_read_timeout_running: &'a mut bool,
|
||||||
|
preserve_header_case: bool,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
preserve_header_order: bool,
|
||||||
|
h09_responses: bool,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
on_informational: &'a mut Option<crate::ffi::OnInformational>,
|
||||||
|
#[cfg(feature = "ffi")]
|
||||||
|
raw_headers: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Passed to Http1Transaction::encode
|
||||||
|
pub(crate) struct Encode<'a, T> {
|
||||||
|
head: &'a mut MessageHead<T>,
|
||||||
|
body: Option<BodyLength>,
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
keep_alive: bool,
|
||||||
|
req_method: &'a mut Option<Method>,
|
||||||
|
title_case_headers: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Extra flags that a request "wants", like expect-continue or upgrades.
|
||||||
|
#[derive(Clone, Copy, Debug)]
|
||||||
|
struct Wants(u8);
|
||||||
|
|
||||||
|
impl Wants {
|
||||||
|
const EMPTY: Wants = Wants(0b00);
|
||||||
|
const EXPECT: Wants = Wants(0b01);
|
||||||
|
const UPGRADE: Wants = Wants(0b10);
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
fn add(self, other: Wants) -> Wants {
|
||||||
|
Wants(self.0 | other.0)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn contains(&self, other: Wants) -> bool {
|
||||||
|
(self.0 & other.0) == other.0
|
||||||
|
}
|
||||||
|
}
|
||||||
2847
hyper/src/proto/h1/role.rs
Normal file
2847
hyper/src/proto/h1/role.rs
Normal file
File diff suppressed because it is too large
Load diff
450
hyper/src/proto/h2/client.rs
Normal file
450
hyper/src/proto/h2/client.rs
Normal file
|
|
@ -0,0 +1,450 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use futures_channel::{mpsc, oneshot};
|
||||||
|
use futures_util::future::{self, Either, FutureExt as _, TryFutureExt as _};
|
||||||
|
use futures_util::stream::StreamExt as _;
|
||||||
|
use h2::client::{Builder, SendRequest};
|
||||||
|
use h2::SendStream;
|
||||||
|
use http::{Method, StatusCode};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
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::ext::Protocol;
|
||||||
|
use crate::headers;
|
||||||
|
use crate::proto::h2::UpgradedSendStream;
|
||||||
|
use crate::proto::Dispatched;
|
||||||
|
use crate::upgrade::Upgraded;
|
||||||
|
use crate::{Body, Request, Response};
|
||||||
|
use h2::client::ResponseFuture;
|
||||||
|
|
||||||
|
type ClientRx<B> = crate::client::dispatch::Receiver<Request<B>, Response<Body>>;
|
||||||
|
|
||||||
|
///// 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<Never>;
|
||||||
|
|
||||||
|
///// 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<Never>;
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// for performance.
|
||||||
|
const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024 * 5; // 5mb
|
||||||
|
const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024 * 2; // 2mb
|
||||||
|
const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
|
||||||
|
const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 1024; // 1mb
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
pub(crate) adaptive_window: bool,
|
||||||
|
pub(crate) initial_conn_window_size: u32,
|
||||||
|
pub(crate) initial_stream_window_size: u32,
|
||||||
|
pub(crate) max_frame_size: u32,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(crate) keep_alive_interval: Option<Duration>,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(crate) keep_alive_timeout: Duration,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(crate) keep_alive_while_idle: bool,
|
||||||
|
pub(crate) max_concurrent_reset_streams: Option<usize>,
|
||||||
|
pub(crate) max_send_buffer_size: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Config {
|
||||||
|
Config {
|
||||||
|
adaptive_window: false,
|
||||||
|
initial_conn_window_size: DEFAULT_CONN_WINDOW,
|
||||||
|
initial_stream_window_size: DEFAULT_STREAM_WINDOW,
|
||||||
|
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_interval: None,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_timeout: Duration::from_secs(20),
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_while_idle: false,
|
||||||
|
max_concurrent_reset_streams: None,
|
||||||
|
max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_builder(config: &Config) -> Builder {
|
||||||
|
let mut builder = Builder::default();
|
||||||
|
builder
|
||||||
|
.initial_window_size(config.initial_stream_window_size)
|
||||||
|
.initial_connection_window_size(config.initial_conn_window_size)
|
||||||
|
.max_frame_size(config.max_frame_size)
|
||||||
|
.max_send_buffer_size(config.max_send_buffer_size)
|
||||||
|
.enable_push(false);
|
||||||
|
if let Some(max) = config.max_concurrent_reset_streams {
|
||||||
|
builder.max_concurrent_reset_streams(max);
|
||||||
|
}
|
||||||
|
builder
|
||||||
|
}
|
||||||
|
|
||||||
|
fn new_ping_config(config: &Config) -> ping::Config {
|
||||||
|
ping::Config {
|
||||||
|
bdp_initial_window: if config.adaptive_window {
|
||||||
|
Some(config.initial_stream_window_size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
},
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_interval: config.keep_alive_interval,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_timeout: config.keep_alive_timeout,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_while_idle: config.keep_alive_while_idle,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) async fn handshake<T, B>(
|
||||||
|
io: T,
|
||||||
|
req_rx: ClientRx<B>,
|
||||||
|
config: &Config,
|
||||||
|
exec: Exec,
|
||||||
|
) -> crate::Result<ClientTask<B>>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Send + Unpin + 'static,
|
||||||
|
B: HttpBody,
|
||||||
|
B::Data: Send + 'static,
|
||||||
|
{
|
||||||
|
let (h2_tx, mut conn) = new_builder(config)
|
||||||
|
.handshake::<_, SendBuf<B::Data>>(io)
|
||||||
|
.await
|
||||||
|
.map_err(crate::Error::new_h2)?;
|
||||||
|
|
||||||
|
// An mpsc channel is used entirely to detect when the
|
||||||
|
// 'Client' has been dropped. This is to get around a bug
|
||||||
|
// in h2 where dropping all SendRequests won't notify a
|
||||||
|
// parked Connection.
|
||||||
|
let (conn_drop_ref, rx) = mpsc::channel(1);
|
||||||
|
let (cancel_tx, conn_eof) = oneshot::channel();
|
||||||
|
|
||||||
|
let conn_drop_rx = rx.into_future().map(|(item, _rx)| {
|
||||||
|
if let Some(never) = item {
|
||||||
|
match never {}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let ping_config = new_ping_config(&config);
|
||||||
|
|
||||||
|
let (conn, ping) = if ping_config.is_enabled() {
|
||||||
|
let pp = conn.ping_pong().expect("conn.ping_pong");
|
||||||
|
let (recorder, mut ponger) = ping::channel(pp, ping_config);
|
||||||
|
|
||||||
|
let conn = future::poll_fn(move |cx| {
|
||||||
|
match ponger.poll(cx) {
|
||||||
|
Poll::Ready(ping::Ponged::SizeUpdate(wnd)) => {
|
||||||
|
conn.set_target_window_size(wnd);
|
||||||
|
conn.set_initial_window_size(wnd)?;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
Poll::Ready(ping::Ponged::KeepAliveTimedOut) => {
|
||||||
|
debug!("connection keep-alive timed out");
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
Poll::Pending => {}
|
||||||
|
}
|
||||||
|
|
||||||
|
Pin::new(&mut conn).poll(cx)
|
||||||
|
});
|
||||||
|
(Either::Left(conn), recorder)
|
||||||
|
} else {
|
||||||
|
(Either::Right(conn), ping::disabled())
|
||||||
|
};
|
||||||
|
let conn = conn.map_err(|e| debug!("connection error: {}", e));
|
||||||
|
|
||||||
|
exec.execute(conn_task(conn, conn_drop_rx, cancel_tx));
|
||||||
|
|
||||||
|
Ok(ClientTask {
|
||||||
|
ping,
|
||||||
|
conn_drop_ref,
|
||||||
|
conn_eof,
|
||||||
|
executor: exec,
|
||||||
|
h2_tx,
|
||||||
|
req_rx,
|
||||||
|
fut_ctx: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn conn_task<C, D>(conn: C, drop_rx: D, cancel_tx: oneshot::Sender<Never>)
|
||||||
|
where
|
||||||
|
C: Future + Unpin,
|
||||||
|
D: Future<Output = ()> + Unpin,
|
||||||
|
{
|
||||||
|
match future::select(conn, drop_rx).await {
|
||||||
|
Either::Left(_) => {
|
||||||
|
// ok or err, the `conn` has finished
|
||||||
|
}
|
||||||
|
Either::Right(((), conn)) => {
|
||||||
|
// mpsc has been dropped, hopefully polling
|
||||||
|
// the connection some more should start shutdown
|
||||||
|
// and then close
|
||||||
|
trace!("send_request dropped, starting conn shutdown");
|
||||||
|
drop(cancel_tx);
|
||||||
|
let _ = conn.await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct FutCtx<B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
is_connect: bool,
|
||||||
|
eos: bool,
|
||||||
|
fut: ResponseFuture,
|
||||||
|
body_tx: SendStream<SendBuf<B::Data>>,
|
||||||
|
body: B,
|
||||||
|
cb: Callback<Request<B>, Response<Body>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: HttpBody> Unpin for FutCtx<B> {}
|
||||||
|
|
||||||
|
pub(crate) struct ClientTask<B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
ping: ping::Recorder,
|
||||||
|
conn_drop_ref: ConnDropRef,
|
||||||
|
conn_eof: ConnEof,
|
||||||
|
executor: Exec,
|
||||||
|
h2_tx: SendRequest<SendBuf<B::Data>>,
|
||||||
|
req_rx: ClientRx<B>,
|
||||||
|
fut_ctx: Option<FutCtx<B>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> ClientTask<B>
|
||||||
|
where
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
{
|
||||||
|
pub(crate) fn is_extended_connect_protocol_enabled(&self) -> bool {
|
||||||
|
self.h2_tx.is_extended_connect_protocol_enabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> ClientTask<B>
|
||||||
|
where
|
||||||
|
B: HttpBody + Send + 'static,
|
||||||
|
B::Data: Send,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
fn poll_pipe(&mut self, f: FutCtx<B>, cx: &mut task::Context<'_>) {
|
||||||
|
let ping = self.ping.clone();
|
||||||
|
let send_stream = if !f.is_connect {
|
||||||
|
if !f.eos {
|
||||||
|
let mut pipe = Box::pin(PipeToSendStream::new(f.body, f.body_tx)).map(|res| {
|
||||||
|
if let Err(e) = res {
|
||||||
|
debug!("client request body error: {}", e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// eagerly see if the body pipe is ready and
|
||||||
|
// can thus skip allocating in the executor
|
||||||
|
match Pin::new(&mut pipe).poll(cx) {
|
||||||
|
Poll::Ready(_) => (),
|
||||||
|
Poll::Pending => {
|
||||||
|
let conn_drop_ref = self.conn_drop_ref.clone();
|
||||||
|
// keep the ping recorder's knowledge of an
|
||||||
|
// "open stream" alive while this body is
|
||||||
|
// still sending...
|
||||||
|
let ping = ping.clone();
|
||||||
|
let pipe = pipe.map(move |x| {
|
||||||
|
drop(conn_drop_ref);
|
||||||
|
drop(ping);
|
||||||
|
x
|
||||||
|
});
|
||||||
|
// Clear send task
|
||||||
|
self.executor.execute(pipe);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
None
|
||||||
|
} else {
|
||||||
|
Some(f.body_tx)
|
||||||
|
};
|
||||||
|
|
||||||
|
let fut = f.fut.map(move |result| match result {
|
||||||
|
Ok(res) => {
|
||||||
|
// record that we got the response headers
|
||||||
|
ping.record_non_data();
|
||||||
|
|
||||||
|
let content_length = headers::content_length_parse_all(res.headers());
|
||||||
|
if let (Some(mut send_stream), StatusCode::OK) = (send_stream, res.status()) {
|
||||||
|
if content_length.map_or(false, |len| len != 0) {
|
||||||
|
warn!("h2 connect response with non-zero body not supported");
|
||||||
|
|
||||||
|
send_stream.send_reset(h2::Reason::INTERNAL_ERROR);
|
||||||
|
return Err((
|
||||||
|
crate::Error::new_h2(h2::Reason::INTERNAL_ERROR.into()),
|
||||||
|
None,
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let (parts, recv_stream) = res.into_parts();
|
||||||
|
let mut res = Response::from_parts(parts, Body::empty());
|
||||||
|
|
||||||
|
let (pending, on_upgrade) = crate::upgrade::pending();
|
||||||
|
let io = H2Upgraded {
|
||||||
|
ping,
|
||||||
|
send_stream: unsafe { UpgradedSendStream::new(send_stream) },
|
||||||
|
recv_stream,
|
||||||
|
buf: Bytes::new(),
|
||||||
|
};
|
||||||
|
let upgraded = Upgraded::new(io, Bytes::new());
|
||||||
|
|
||||||
|
pending.fulfill(upgraded);
|
||||||
|
res.extensions_mut().insert(on_upgrade);
|
||||||
|
|
||||||
|
Ok(res)
|
||||||
|
} else {
|
||||||
|
let res = res.map(|stream| {
|
||||||
|
let ping = ping.for_stream(&stream);
|
||||||
|
crate::Body::h2(stream, content_length.into(), ping)
|
||||||
|
});
|
||||||
|
Ok(res)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
ping.ensure_not_timed_out().map_err(|e| (e, None))?;
|
||||||
|
|
||||||
|
debug!("client response error: {}", err);
|
||||||
|
Err((crate::Error::new_h2(err), None))
|
||||||
|
}
|
||||||
|
});
|
||||||
|
self.executor.execute(f.cb.send_when(fut));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> Future for ClientTask<B>
|
||||||
|
where
|
||||||
|
B: HttpBody + Send + 'static,
|
||||||
|
B::Data: Send,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<Dispatched>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
loop {
|
||||||
|
match ready!(self.h2_tx.poll_ready(cx)) {
|
||||||
|
Ok(()) => (),
|
||||||
|
Err(err) => {
|
||||||
|
self.ping.ensure_not_timed_out()?;
|
||||||
|
return if err.reason() == Some(::h2::Reason::NO_ERROR) {
|
||||||
|
trace!("connection gracefully shutdown");
|
||||||
|
Poll::Ready(Ok(Dispatched::Shutdown))
|
||||||
|
} else {
|
||||||
|
Poll::Ready(Err(crate::Error::new_h2(err)))
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
match self.fut_ctx.take() {
|
||||||
|
// If we were waiting on pending open
|
||||||
|
// continue where we left off.
|
||||||
|
Some(f) => {
|
||||||
|
self.poll_pipe(f, cx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
None => (),
|
||||||
|
}
|
||||||
|
|
||||||
|
match self.req_rx.poll_recv(cx) {
|
||||||
|
Poll::Ready(Some((req, cb))) => {
|
||||||
|
// check that future hasn't been canceled already
|
||||||
|
if cb.is_canceled() {
|
||||||
|
trace!("request callback is canceled");
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let (head, body) = req.into_parts();
|
||||||
|
let mut req = ::http::Request::from_parts(head, ());
|
||||||
|
super::strip_connection_headers(req.headers_mut(), true);
|
||||||
|
if let Some(len) = body.size_hint().exact() {
|
||||||
|
if len != 0 || headers::method_has_defined_payload_semantics(req.method()) {
|
||||||
|
headers::set_content_length_if_missing(req.headers_mut(), len);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let is_connect = req.method() == Method::CONNECT;
|
||||||
|
let eos = body.is_end_stream();
|
||||||
|
|
||||||
|
if is_connect {
|
||||||
|
if headers::content_length_parse_all(req.headers())
|
||||||
|
.map_or(false, |len| len != 0)
|
||||||
|
{
|
||||||
|
warn!("h2 connect request with non-zero body not supported");
|
||||||
|
cb.send(Err((
|
||||||
|
crate::Error::new_h2(h2::Reason::INTERNAL_ERROR.into()),
|
||||||
|
None,
|
||||||
|
)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(protocol) = req.extensions_mut().remove::<Protocol>() {
|
||||||
|
req.extensions_mut().insert(protocol.into_inner());
|
||||||
|
}
|
||||||
|
|
||||||
|
let (fut, body_tx) = match self.h2_tx.send_request(req, !is_connect && eos) {
|
||||||
|
Ok(ok) => ok,
|
||||||
|
Err(err) => {
|
||||||
|
debug!("client send request error: {}", err);
|
||||||
|
cb.send(Err((crate::Error::new_h2(err), None)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let f = FutCtx {
|
||||||
|
is_connect,
|
||||||
|
eos,
|
||||||
|
fut,
|
||||||
|
body_tx,
|
||||||
|
body,
|
||||||
|
cb,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Check poll_ready() again.
|
||||||
|
// If the call to send_request() resulted in the new stream being pending open
|
||||||
|
// we have to wait for the open to complete before accepting new requests.
|
||||||
|
match self.h2_tx.poll_ready(cx) {
|
||||||
|
Poll::Pending => {
|
||||||
|
// Save Context
|
||||||
|
self.fut_ctx = Some(f);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
Poll::Ready(Ok(())) => (),
|
||||||
|
Poll::Ready(Err(err)) => {
|
||||||
|
f.cb.send(Err((crate::Error::new_h2(err), None)));
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.poll_pipe(f, cx);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(None) => {
|
||||||
|
trace!("client::dispatch::Sender dropped");
|
||||||
|
return Poll::Ready(Ok(Dispatched::Shutdown));
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Pending => match ready!(Pin::new(&mut self.conn_eof).poll(cx)) {
|
||||||
|
Ok(never) => match never {},
|
||||||
|
Err(_conn_is_eof) => {
|
||||||
|
trace!("connection task is closed, closing dispatch task");
|
||||||
|
return Poll::Ready(Ok(Dispatched::Shutdown));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
471
hyper/src/proto/h2/mod.rs
Normal file
471
hyper/src/proto/h2/mod.rs
Normal file
|
|
@ -0,0 +1,471 @@
|
||||||
|
use bytes::{Buf, Bytes};
|
||||||
|
use h2::{Reason, RecvStream, SendStream};
|
||||||
|
use http::header::{HeaderName, CONNECTION, TE, TRAILER, TRANSFER_ENCODING, UPGRADE};
|
||||||
|
use http::HeaderMap;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::io::{self, Cursor, IoSlice};
|
||||||
|
use std::mem;
|
||||||
|
use std::task::Context;
|
||||||
|
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;
|
||||||
|
|
||||||
|
cfg_client! {
|
||||||
|
pub(crate) mod client;
|
||||||
|
pub(crate) use self::client::ClientTask;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_server! {
|
||||||
|
pub(crate) mod server;
|
||||||
|
pub(crate) use self::server::Server;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Default initial stream window size defined in HTTP2 spec.
|
||||||
|
pub(crate) const SPEC_WINDOW_SIZE: u32 = 65_535;
|
||||||
|
|
||||||
|
fn strip_connection_headers(headers: &mut HeaderMap, is_request: bool) {
|
||||||
|
// List of connection headers from:
|
||||||
|
// https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
|
||||||
|
//
|
||||||
|
// TE headers are allowed in HTTP/2 requests as long as the value is "trailers", so they're
|
||||||
|
// tested separately.
|
||||||
|
let connection_headers = [
|
||||||
|
HeaderName::from_lowercase(b"keep-alive").unwrap(),
|
||||||
|
HeaderName::from_lowercase(b"proxy-connection").unwrap(),
|
||||||
|
TRAILER,
|
||||||
|
TRANSFER_ENCODING,
|
||||||
|
UPGRADE,
|
||||||
|
];
|
||||||
|
|
||||||
|
for header in connection_headers.iter() {
|
||||||
|
if headers.remove(header).is_some() {
|
||||||
|
warn!("Connection header illegal in HTTP/2: {}", header.as_str());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if is_request {
|
||||||
|
if headers
|
||||||
|
.get(TE)
|
||||||
|
.map(|te_header| te_header != "trailers")
|
||||||
|
.unwrap_or(false)
|
||||||
|
{
|
||||||
|
warn!("TE headers not set to \"trailers\" are illegal in HTTP/2 requests");
|
||||||
|
headers.remove(TE);
|
||||||
|
}
|
||||||
|
} else if headers.remove(TE).is_some() {
|
||||||
|
warn!("TE headers illegal in HTTP/2 responses");
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(header) = headers.remove(CONNECTION) {
|
||||||
|
warn!(
|
||||||
|
"Connection header illegal in HTTP/2: {}",
|
||||||
|
CONNECTION.as_str()
|
||||||
|
);
|
||||||
|
let header_contents = header.to_str().unwrap();
|
||||||
|
|
||||||
|
// A `Connection` header may have a comma-separated list of names of other headers that
|
||||||
|
// are meant for only this specific connection.
|
||||||
|
//
|
||||||
|
// Iterate these names and remove them as headers. Connection-specific headers are
|
||||||
|
// forbidden in HTTP2, as that information has been moved into frame types of the h2
|
||||||
|
// protocol.
|
||||||
|
for name in header_contents.split(',') {
|
||||||
|
let name = name.trim();
|
||||||
|
headers.remove(name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// body adapters used by both Client and Server
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
struct PipeToSendStream<S>
|
||||||
|
where
|
||||||
|
S: HttpBody,
|
||||||
|
{
|
||||||
|
body_tx: SendStream<SendBuf<S::Data>>,
|
||||||
|
data_done: bool,
|
||||||
|
#[pin]
|
||||||
|
stream: S,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> PipeToSendStream<S>
|
||||||
|
where
|
||||||
|
S: HttpBody,
|
||||||
|
{
|
||||||
|
fn new(stream: S, tx: SendStream<SendBuf<S::Data>>) -> PipeToSendStream<S> {
|
||||||
|
PipeToSendStream {
|
||||||
|
body_tx: tx,
|
||||||
|
data_done: false,
|
||||||
|
stream,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<S> Future for PipeToSendStream<S>
|
||||||
|
where
|
||||||
|
S: HttpBody,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<()>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut me = self.project();
|
||||||
|
loop {
|
||||||
|
if !*me.data_done {
|
||||||
|
// we don't have the next chunk of data yet, so just reserve 1 byte to make
|
||||||
|
// sure there's some capacity available. h2 will handle the capacity management
|
||||||
|
// for the actual body chunk.
|
||||||
|
me.body_tx.reserve_capacity(1);
|
||||||
|
|
||||||
|
if me.body_tx.capacity() == 0 {
|
||||||
|
loop {
|
||||||
|
match ready!(me.body_tx.poll_capacity(cx)) {
|
||||||
|
Some(Ok(0)) => {}
|
||||||
|
Some(Ok(_)) => break,
|
||||||
|
Some(Err(e)) => {
|
||||||
|
return Poll::Ready(Err(crate::Error::new_body_write(e)))
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// None means the stream is no longer in a
|
||||||
|
// streaming state, we either finished it
|
||||||
|
// somehow, or the remote reset us.
|
||||||
|
return Poll::Ready(Err(crate::Error::new_body_write(
|
||||||
|
"send stream capacity unexpectedly closed",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if let Poll::Ready(reason) = me
|
||||||
|
.body_tx
|
||||||
|
.poll_reset(cx)
|
||||||
|
.map_err(crate::Error::new_body_write)?
|
||||||
|
{
|
||||||
|
debug!("stream received RST_STREAM: {:?}", reason);
|
||||||
|
return Poll::Ready(Err(crate::Error::new_body_write(::h2::Error::from(
|
||||||
|
reason,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
match ready!(me.stream.as_mut().poll_data(cx)) {
|
||||||
|
Some(Ok(chunk)) => {
|
||||||
|
let is_eos = me.stream.is_end_stream();
|
||||||
|
trace!(
|
||||||
|
"send body chunk: {} bytes, eos={}",
|
||||||
|
chunk.remaining(),
|
||||||
|
is_eos,
|
||||||
|
);
|
||||||
|
|
||||||
|
let buf = SendBuf::Buf(chunk);
|
||||||
|
me.body_tx
|
||||||
|
.send_data(buf, is_eos)
|
||||||
|
.map_err(crate::Error::new_body_write)?;
|
||||||
|
|
||||||
|
if is_eos {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Some(Err(e)) => return Poll::Ready(Err(me.body_tx.on_user_err(e))),
|
||||||
|
None => {
|
||||||
|
me.body_tx.reserve_capacity(0);
|
||||||
|
let is_eos = me.stream.is_end_stream();
|
||||||
|
if is_eos {
|
||||||
|
return Poll::Ready(me.body_tx.send_eos_frame());
|
||||||
|
} else {
|
||||||
|
*me.data_done = true;
|
||||||
|
// loop again to poll_trailers
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let Poll::Ready(reason) = me
|
||||||
|
.body_tx
|
||||||
|
.poll_reset(cx)
|
||||||
|
.map_err(crate::Error::new_body_write)?
|
||||||
|
{
|
||||||
|
debug!("stream received RST_STREAM: {:?}", reason);
|
||||||
|
return Poll::Ready(Err(crate::Error::new_body_write(::h2::Error::from(
|
||||||
|
reason,
|
||||||
|
))));
|
||||||
|
}
|
||||||
|
|
||||||
|
match ready!(me.stream.poll_trailers(cx)) {
|
||||||
|
Ok(Some(trailers)) => {
|
||||||
|
me.body_tx
|
||||||
|
.send_trailers(trailers)
|
||||||
|
.map_err(crate::Error::new_body_write)?;
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
Ok(None) => {
|
||||||
|
// There were no trailers, so send an empty DATA frame...
|
||||||
|
return Poll::Ready(me.body_tx.send_eos_frame());
|
||||||
|
}
|
||||||
|
Err(e) => return Poll::Ready(Err(me.body_tx.on_user_err(e))),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
trait SendStreamExt {
|
||||||
|
fn on_user_err<E>(&mut self, err: E) -> crate::Error
|
||||||
|
where
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync>>;
|
||||||
|
fn send_eos_frame(&mut self) -> crate::Result<()>;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Buf> SendStreamExt for SendStream<SendBuf<B>> {
|
||||||
|
fn on_user_err<E>(&mut self, err: E) -> crate::Error
|
||||||
|
where
|
||||||
|
E: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||||
|
{
|
||||||
|
let err = crate::Error::new_user_body(err);
|
||||||
|
debug!("send body user stream error: {}", err);
|
||||||
|
self.send_reset(err.h2_reason());
|
||||||
|
err
|
||||||
|
}
|
||||||
|
|
||||||
|
fn send_eos_frame(&mut self) -> crate::Result<()> {
|
||||||
|
trace!("send body eos");
|
||||||
|
self.send_data(SendBuf::None, true)
|
||||||
|
.map_err(crate::Error::new_body_write)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(usize)]
|
||||||
|
enum SendBuf<B> {
|
||||||
|
Buf(B),
|
||||||
|
Cursor(Cursor<Box<[u8]>>),
|
||||||
|
None,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B: Buf> Buf for SendBuf<B> {
|
||||||
|
#[inline]
|
||||||
|
fn remaining(&self) -> usize {
|
||||||
|
match *self {
|
||||||
|
Self::Buf(ref b) => b.remaining(),
|
||||||
|
Self::Cursor(ref c) => Buf::remaining(c),
|
||||||
|
Self::None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn chunk(&self) -> &[u8] {
|
||||||
|
match *self {
|
||||||
|
Self::Buf(ref b) => b.chunk(),
|
||||||
|
Self::Cursor(ref c) => c.chunk(),
|
||||||
|
Self::None => &[],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn advance(&mut self, cnt: usize) {
|
||||||
|
match *self {
|
||||||
|
Self::Buf(ref mut b) => b.advance(cnt),
|
||||||
|
Self::Cursor(ref mut c) => c.advance(cnt),
|
||||||
|
Self::None => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chunks_vectored<'a>(&'a self, dst: &mut [IoSlice<'a>]) -> usize {
|
||||||
|
match *self {
|
||||||
|
Self::Buf(ref b) => b.chunks_vectored(dst),
|
||||||
|
Self::Cursor(ref c) => c.chunks_vectored(dst),
|
||||||
|
Self::None => 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct H2Upgraded<B>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
ping: Recorder,
|
||||||
|
send_stream: UpgradedSendStream<B>,
|
||||||
|
recv_stream: RecvStream,
|
||||||
|
buf: Bytes,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> AsyncRead for H2Upgraded<B>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
fn poll_read(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
read_buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<Result<(), io::Error>> {
|
||||||
|
if self.buf.is_empty() {
|
||||||
|
self.buf = loop {
|
||||||
|
match ready!(self.recv_stream.poll_data(cx)) {
|
||||||
|
None => return Poll::Ready(Ok(())),
|
||||||
|
Some(Ok(buf)) if buf.is_empty() && !self.recv_stream.is_end_stream() => {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
Some(Ok(buf)) => {
|
||||||
|
self.ping.record_data(buf.len());
|
||||||
|
break buf;
|
||||||
|
}
|
||||||
|
Some(Err(e)) => {
|
||||||
|
return Poll::Ready(match e.reason() {
|
||||||
|
Some(Reason::NO_ERROR) | Some(Reason::CANCEL) => Ok(()),
|
||||||
|
Some(Reason::STREAM_CLOSED) => {
|
||||||
|
Err(io::Error::new(io::ErrorKind::BrokenPipe, e))
|
||||||
|
}
|
||||||
|
_ => Err(h2_to_io_error(e)),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let cnt = std::cmp::min(self.buf.len(), read_buf.remaining());
|
||||||
|
read_buf.put_slice(&self.buf[..cnt]);
|
||||||
|
self.buf.advance(cnt);
|
||||||
|
let _ = self.recv_stream.flow_control().release_capacity(cnt);
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<B> AsyncWrite for H2Upgraded<B>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
fn poll_write(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<Result<usize, io::Error>> {
|
||||||
|
if buf.is_empty() {
|
||||||
|
return Poll::Ready(Ok(0));
|
||||||
|
}
|
||||||
|
self.send_stream.reserve_capacity(buf.len());
|
||||||
|
|
||||||
|
// We ignore all errors returned by `poll_capacity` and `write`, as we
|
||||||
|
// will get the correct from `poll_reset` anyway.
|
||||||
|
let cnt = match ready!(self.send_stream.poll_capacity(cx)) {
|
||||||
|
None => Some(0),
|
||||||
|
Some(Ok(cnt)) => self
|
||||||
|
.send_stream
|
||||||
|
.write(&buf[..cnt], false)
|
||||||
|
.ok()
|
||||||
|
.map(|()| cnt),
|
||||||
|
Some(Err(_)) => None,
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(cnt) = cnt {
|
||||||
|
return Poll::Ready(Ok(cnt));
|
||||||
|
}
|
||||||
|
|
||||||
|
Poll::Ready(Err(h2_to_io_error(
|
||||||
|
match ready!(self.send_stream.poll_reset(cx)) {
|
||||||
|
Ok(Reason::NO_ERROR) | Ok(Reason::CANCEL) | Ok(Reason::STREAM_CLOSED) => {
|
||||||
|
return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
|
||||||
|
}
|
||||||
|
Ok(reason) => reason.into(),
|
||||||
|
Err(e) => e,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_shutdown(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut Context<'_>,
|
||||||
|
) -> Poll<Result<(), io::Error>> {
|
||||||
|
if self.send_stream.write(&[], true).is_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::CANCEL) | Ok(Reason::STREAM_CLOSED) => {
|
||||||
|
return Poll::Ready(Err(io::ErrorKind::BrokenPipe.into()))
|
||||||
|
}
|
||||||
|
Ok(reason) => reason.into(),
|
||||||
|
Err(e) => e,
|
||||||
|
},
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn h2_to_io_error(e: h2::Error) -> io::Error {
|
||||||
|
if e.is_io() {
|
||||||
|
e.into_io().unwrap()
|
||||||
|
} else {
|
||||||
|
io::Error::new(io::ErrorKind::Other, e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct UpgradedSendStream<B>(SendStream<SendBuf<Neutered<B>>>);
|
||||||
|
|
||||||
|
impl<B> UpgradedSendStream<B>
|
||||||
|
where
|
||||||
|
B: Buf,
|
||||||
|
{
|
||||||
|
unsafe fn new(inner: SendStream<SendBuf<B>>) -> Self {
|
||||||
|
assert_eq!(mem::size_of::<B>(), mem::size_of::<Neutered<B>>());
|
||||||
|
Self(mem::transmute(inner))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn reserve_capacity(&mut self, cnt: usize) {
|
||||||
|
unsafe { self.as_inner_unchecked().reserve_capacity(cnt) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_capacity(&mut self, cx: &mut Context<'_>) -> Poll<Option<Result<usize, h2::Error>>> {
|
||||||
|
unsafe { self.as_inner_unchecked().poll_capacity(cx) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_reset(&mut self, cx: &mut Context<'_>) -> Poll<Result<h2::Reason, h2::Error>> {
|
||||||
|
unsafe { self.as_inner_unchecked().poll_reset(cx) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write(&mut self, buf: &[u8], end_of_stream: bool) -> Result<(), io::Error> {
|
||||||
|
let send_buf = SendBuf::Cursor(Cursor::new(buf.into()));
|
||||||
|
unsafe {
|
||||||
|
self.as_inner_unchecked()
|
||||||
|
.send_data(send_buf, end_of_stream)
|
||||||
|
.map_err(h2_to_io_error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
unsafe fn as_inner_unchecked(&mut self) -> &mut SendStream<SendBuf<B>> {
|
||||||
|
&mut *(&mut self.0 as *mut _ as *mut _)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[repr(transparent)]
|
||||||
|
struct Neutered<B> {
|
||||||
|
_inner: B,
|
||||||
|
impossible: Impossible,
|
||||||
|
}
|
||||||
|
|
||||||
|
enum Impossible {}
|
||||||
|
|
||||||
|
unsafe impl<B> Send for Neutered<B> {}
|
||||||
|
|
||||||
|
impl<B> Buf for Neutered<B> {
|
||||||
|
fn remaining(&self) -> usize {
|
||||||
|
match self.impossible {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn chunk(&self) -> &[u8] {
|
||||||
|
match self.impossible {}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn advance(&mut self, _cnt: usize) {
|
||||||
|
match self.impossible {}
|
||||||
|
}
|
||||||
|
}
|
||||||
555
hyper/src/proto/h2/ping.rs
Normal file
555
hyper/src/proto/h2/ping.rs
Normal file
|
|
@ -0,0 +1,555 @@
|
||||||
|
/// HTTP2 Ping usage
|
||||||
|
///
|
||||||
|
/// hyper uses HTTP2 pings for two purposes:
|
||||||
|
///
|
||||||
|
/// 1. Adaptive flow control using BDP
|
||||||
|
/// 2. Connection keep-alive
|
||||||
|
///
|
||||||
|
/// Both cases are optional.
|
||||||
|
///
|
||||||
|
/// # BDP Algorithm
|
||||||
|
///
|
||||||
|
/// 1. When receiving a DATA frame, if a BDP ping isn't outstanding:
|
||||||
|
/// 1a. Record current time.
|
||||||
|
/// 1b. Send a BDP ping.
|
||||||
|
/// 2. Increment the number of received bytes.
|
||||||
|
/// 3. When the BDP ping ack is received:
|
||||||
|
/// 3a. Record duration from sent time.
|
||||||
|
/// 3b. Merge RTT with a running average.
|
||||||
|
/// 3c. Calculate bdp as bytes/rtt.
|
||||||
|
/// 3d. If bdp is over 2/3 max, set new max to bdp and update windows.
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::fmt;
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::future::Future;
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::pin::Pin;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::task::{self, Poll};
|
||||||
|
use std::time::Duration;
|
||||||
|
#[cfg(not(feature = "runtime"))]
|
||||||
|
use std::time::Instant;
|
||||||
|
|
||||||
|
use h2::{Ping, PingPong};
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use tokio::time::{Instant, Sleep};
|
||||||
|
use tracing::{debug, trace};
|
||||||
|
|
||||||
|
type WindowSize = u32;
|
||||||
|
|
||||||
|
pub(super) fn disabled() -> Recorder {
|
||||||
|
Recorder { shared: None }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn channel(ping_pong: PingPong, config: Config) -> (Recorder, Ponger) {
|
||||||
|
debug_assert!(
|
||||||
|
config.is_enabled(),
|
||||||
|
"ping channel requires bdp or keep-alive config",
|
||||||
|
);
|
||||||
|
|
||||||
|
let bdp = config.bdp_initial_window.map(|wnd| Bdp {
|
||||||
|
bdp: wnd,
|
||||||
|
max_bandwidth: 0.0,
|
||||||
|
rtt: 0.0,
|
||||||
|
ping_delay: Duration::from_millis(100),
|
||||||
|
stable_count: 0,
|
||||||
|
});
|
||||||
|
|
||||||
|
let (bytes, next_bdp_at) = if bdp.is_some() {
|
||||||
|
(Some(0), Some(Instant::now()))
|
||||||
|
} else {
|
||||||
|
(None, None)
|
||||||
|
};
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
let keep_alive = config.keep_alive_interval.map(|interval| KeepAlive {
|
||||||
|
interval,
|
||||||
|
timeout: config.keep_alive_timeout,
|
||||||
|
while_idle: config.keep_alive_while_idle,
|
||||||
|
timer: Box::pin(tokio::time::sleep(interval)),
|
||||||
|
state: KeepAliveState::Init,
|
||||||
|
});
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
let last_read_at = keep_alive.as_ref().map(|_| Instant::now());
|
||||||
|
|
||||||
|
let shared = Arc::new(Mutex::new(Shared {
|
||||||
|
bytes,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
last_read_at,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
is_keep_alive_timed_out: false,
|
||||||
|
ping_pong,
|
||||||
|
ping_sent_at: None,
|
||||||
|
next_bdp_at,
|
||||||
|
}));
|
||||||
|
|
||||||
|
(
|
||||||
|
Recorder {
|
||||||
|
shared: Some(shared.clone()),
|
||||||
|
},
|
||||||
|
Ponger {
|
||||||
|
bdp,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive,
|
||||||
|
shared,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(super) struct Config {
|
||||||
|
pub(super) bdp_initial_window: Option<WindowSize>,
|
||||||
|
/// If no frames are received in this amount of time, a PING frame is sent.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(super) keep_alive_interval: Option<Duration>,
|
||||||
|
/// After sending a keepalive PING, the connection will be closed if
|
||||||
|
/// a pong is not received in this amount of time.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(super) keep_alive_timeout: Duration,
|
||||||
|
/// If true, sends pings even when there are no active streams.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(super) keep_alive_while_idle: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub(crate) struct Recorder {
|
||||||
|
shared: Option<Arc<Mutex<Shared>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) struct Ponger {
|
||||||
|
bdp: Option<Bdp>,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive: Option<KeepAlive>,
|
||||||
|
shared: Arc<Mutex<Shared>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Shared {
|
||||||
|
ping_pong: PingPong,
|
||||||
|
ping_sent_at: Option<Instant>,
|
||||||
|
|
||||||
|
// bdp
|
||||||
|
/// If `Some`, bdp is enabled, and this tracks how many bytes have been
|
||||||
|
/// read during the current sample.
|
||||||
|
bytes: Option<usize>,
|
||||||
|
/// We delay a variable amount of time between BDP pings. This allows us
|
||||||
|
/// to send less pings as the bandwidth stabilizes.
|
||||||
|
next_bdp_at: Option<Instant>,
|
||||||
|
|
||||||
|
// keep-alive
|
||||||
|
/// If `Some`, keep-alive is enabled, and the Instant is how long ago
|
||||||
|
/// the connection read the last frame.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
last_read_at: Option<Instant>,
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
is_keep_alive_timed_out: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Bdp {
|
||||||
|
/// Current BDP in bytes
|
||||||
|
bdp: u32,
|
||||||
|
/// Largest bandwidth we've seen so far.
|
||||||
|
max_bandwidth: f64,
|
||||||
|
/// Round trip time in seconds
|
||||||
|
rtt: f64,
|
||||||
|
/// Delay the next ping by this amount.
|
||||||
|
///
|
||||||
|
/// This will change depending on how stable the current bandwidth is.
|
||||||
|
ping_delay: Duration,
|
||||||
|
/// The count of ping round trips where BDP has stayed the same.
|
||||||
|
stable_count: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
struct KeepAlive {
|
||||||
|
/// If no frames are received in this amount of time, a PING frame is sent.
|
||||||
|
interval: Duration,
|
||||||
|
/// After sending a keepalive PING, the connection will be closed if
|
||||||
|
/// a pong is not received in this amount of time.
|
||||||
|
timeout: Duration,
|
||||||
|
/// If true, sends pings even when there are no active streams.
|
||||||
|
while_idle: bool,
|
||||||
|
|
||||||
|
state: KeepAliveState,
|
||||||
|
timer: Pin<Box<Sleep>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
enum KeepAliveState {
|
||||||
|
Init,
|
||||||
|
Scheduled,
|
||||||
|
PingSent,
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) enum Ponged {
|
||||||
|
SizeUpdate(WindowSize),
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
KeepAliveTimedOut,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub(super) struct KeepAliveTimedOut;
|
||||||
|
|
||||||
|
// ===== impl Config =====
|
||||||
|
|
||||||
|
impl Config {
|
||||||
|
pub(super) fn is_enabled(&self) -> bool {
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
{
|
||||||
|
self.bdp_initial_window.is_some() || self.keep_alive_interval.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(feature = "runtime"))]
|
||||||
|
{
|
||||||
|
self.bdp_initial_window.is_some()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Recorder =====
|
||||||
|
|
||||||
|
impl Recorder {
|
||||||
|
pub(crate) fn record_data(&self, len: usize) {
|
||||||
|
let shared = if let Some(ref shared) = self.shared {
|
||||||
|
shared
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut locked = shared.lock().unwrap();
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
locked.update_last_read_at();
|
||||||
|
|
||||||
|
// are we ready to send another bdp ping?
|
||||||
|
// if not, we don't need to record bytes either
|
||||||
|
|
||||||
|
if let Some(ref next_bdp_at) = locked.next_bdp_at {
|
||||||
|
if Instant::now() < *next_bdp_at {
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
locked.next_bdp_at = None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(ref mut bytes) = locked.bytes {
|
||||||
|
*bytes += len;
|
||||||
|
} else {
|
||||||
|
// no need to send bdp ping if bdp is disabled
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !locked.is_ping_sent() {
|
||||||
|
locked.send_ping();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn record_non_data(&self) {
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
{
|
||||||
|
let shared = if let Some(ref shared) = self.shared {
|
||||||
|
shared
|
||||||
|
} else {
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
let mut locked = shared.lock().unwrap();
|
||||||
|
|
||||||
|
locked.update_last_read_at();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// If the incoming stream is already closed, convert self into
|
||||||
|
/// a disabled reporter.
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(super) fn for_stream(self, stream: &h2::RecvStream) -> Self {
|
||||||
|
if stream.is_end_stream() {
|
||||||
|
disabled()
|
||||||
|
} else {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn ensure_not_timed_out(&self) -> crate::Result<()> {
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
{
|
||||||
|
if let Some(ref shared) = self.shared {
|
||||||
|
let locked = shared.lock().unwrap();
|
||||||
|
if locked.is_keep_alive_timed_out {
|
||||||
|
return Err(KeepAliveTimedOut.crate_error());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// else
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Ponger =====
|
||||||
|
|
||||||
|
impl Ponger {
|
||||||
|
pub(super) fn poll(&mut self, cx: &mut task::Context<'_>) -> Poll<Ponged> {
|
||||||
|
let now = Instant::now();
|
||||||
|
let mut locked = self.shared.lock().unwrap();
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
let is_idle = self.is_idle();
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
{
|
||||||
|
if let Some(ref mut ka) = self.keep_alive {
|
||||||
|
ka.schedule(is_idle, &locked);
|
||||||
|
ka.maybe_ping(cx, &mut locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if !locked.is_ping_sent() {
|
||||||
|
// XXX: this doesn't register a waker...?
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
|
||||||
|
match locked.ping_pong.poll_pong(cx) {
|
||||||
|
Poll::Ready(Ok(_pong)) => {
|
||||||
|
let start = locked
|
||||||
|
.ping_sent_at
|
||||||
|
.expect("pong received implies ping_sent_at");
|
||||||
|
locked.ping_sent_at = None;
|
||||||
|
let rtt = now - start;
|
||||||
|
trace!("recv pong");
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
{
|
||||||
|
if let Some(ref mut ka) = self.keep_alive {
|
||||||
|
locked.update_last_read_at();
|
||||||
|
ka.schedule(is_idle, &locked);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
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))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
debug!("pong error: {}", e);
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
{
|
||||||
|
if let Some(ref mut ka) = self.keep_alive {
|
||||||
|
if let Err(KeepAliveTimedOut) = ka.maybe_timeout(cx) {
|
||||||
|
self.keep_alive = None;
|
||||||
|
locked.is_keep_alive_timed_out = true;
|
||||||
|
return Poll::Ready(Ponged::KeepAliveTimedOut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// XXX: this doesn't register a waker...?
|
||||||
|
Poll::Pending
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
fn is_idle(&self) -> bool {
|
||||||
|
Arc::strong_count(&self.shared) <= 2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Shared =====
|
||||||
|
|
||||||
|
impl Shared {
|
||||||
|
fn send_ping(&mut self) {
|
||||||
|
match self.ping_pong.send_ping(Ping::opaque()) {
|
||||||
|
Ok(()) => {
|
||||||
|
self.ping_sent_at = Some(Instant::now());
|
||||||
|
trace!("sent ping");
|
||||||
|
}
|
||||||
|
Err(err) => {
|
||||||
|
debug!("error sending ping: {}", err);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_ping_sent(&self) -> bool {
|
||||||
|
self.ping_sent_at.is_some()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
fn update_last_read_at(&mut self) {
|
||||||
|
if self.last_read_at.is_some() {
|
||||||
|
self.last_read_at = Some(Instant::now());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
fn last_read_at(&self) -> Instant {
|
||||||
|
self.last_read_at.expect("keep_alive expects last_read_at")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl Bdp =====
|
||||||
|
|
||||||
|
/// Any higher than this likely will be hitting the TCP flow control.
|
||||||
|
const BDP_LIMIT: usize = 1024 * 1024 * 16;
|
||||||
|
|
||||||
|
impl Bdp {
|
||||||
|
fn calculate(&mut self, bytes: usize, rtt: Duration) -> Option<WindowSize> {
|
||||||
|
// No need to do any math if we're at the limit.
|
||||||
|
if self.bdp as usize == BDP_LIMIT {
|
||||||
|
self.stabilize_delay();
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
// average the rtt
|
||||||
|
let rtt = seconds(rtt);
|
||||||
|
if self.rtt == 0.0 {
|
||||||
|
// First sample means rtt is first rtt.
|
||||||
|
self.rtt = rtt;
|
||||||
|
} else {
|
||||||
|
// Weigh this rtt as 1/8 for a moving average.
|
||||||
|
self.rtt += (rtt - self.rtt) * 0.125;
|
||||||
|
}
|
||||||
|
|
||||||
|
// calculate the current bandwidth
|
||||||
|
let bw = (bytes as f64) / (self.rtt * 1.5);
|
||||||
|
trace!("current bandwidth = {:.1}B/s", bw);
|
||||||
|
|
||||||
|
if bw < self.max_bandwidth {
|
||||||
|
// not a faster bandwidth, so don't update
|
||||||
|
self.stabilize_delay();
|
||||||
|
return None;
|
||||||
|
} else {
|
||||||
|
self.max_bandwidth = bw;
|
||||||
|
}
|
||||||
|
|
||||||
|
// if the current `bytes` sample is at least 2/3 the previous
|
||||||
|
// bdp, increase to double the current sample.
|
||||||
|
if bytes >= self.bdp as usize * 2 / 3 {
|
||||||
|
self.bdp = (bytes * 2).min(BDP_LIMIT) as WindowSize;
|
||||||
|
trace!("BDP increased to {}", self.bdp);
|
||||||
|
|
||||||
|
self.stable_count = 0;
|
||||||
|
self.ping_delay /= 2;
|
||||||
|
Some(self.bdp)
|
||||||
|
} else {
|
||||||
|
self.stabilize_delay();
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stabilize_delay(&mut self) {
|
||||||
|
if self.ping_delay < Duration::from_secs(10) {
|
||||||
|
self.stable_count += 1;
|
||||||
|
|
||||||
|
if self.stable_count >= 2 {
|
||||||
|
self.ping_delay *= 4;
|
||||||
|
self.stable_count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn seconds(dur: Duration) -> f64 {
|
||||||
|
const NANOS_PER_SEC: f64 = 1_000_000_000.0;
|
||||||
|
let secs = dur.as_secs() as f64;
|
||||||
|
secs + (dur.subsec_nanos() as f64) / NANOS_PER_SEC
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl KeepAlive =====
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl KeepAlive {
|
||||||
|
fn schedule(&mut self, is_idle: bool, shared: &Shared) {
|
||||||
|
match self.state {
|
||||||
|
KeepAliveState::Init => {
|
||||||
|
if !self.while_idle && is_idle {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = KeepAliveState::Scheduled;
|
||||||
|
let interval = shared.last_read_at() + self.interval;
|
||||||
|
self.timer.as_mut().reset(interval);
|
||||||
|
}
|
||||||
|
KeepAliveState::PingSent => {
|
||||||
|
if shared.is_ping_sent() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.state = KeepAliveState::Scheduled;
|
||||||
|
let interval = shared.last_read_at() + self.interval;
|
||||||
|
self.timer.as_mut().reset(interval);
|
||||||
|
}
|
||||||
|
KeepAliveState::Scheduled => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_ping(&mut self, cx: &mut task::Context<'_>, shared: &mut Shared) {
|
||||||
|
match self.state {
|
||||||
|
KeepAliveState::Scheduled => {
|
||||||
|
if Pin::new(&mut self.timer).poll(cx).is_pending() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// check if we've received a frame while we were scheduled
|
||||||
|
if shared.last_read_at() + self.interval > self.timer.deadline() {
|
||||||
|
self.state = KeepAliveState::Init;
|
||||||
|
cx.waker().wake_by_ref(); // schedule us again
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
trace!("keep-alive interval ({:?}) reached", self.interval);
|
||||||
|
shared.send_ping();
|
||||||
|
self.state = KeepAliveState::PingSent;
|
||||||
|
let timeout = Instant::now() + self.timeout;
|
||||||
|
self.timer.as_mut().reset(timeout);
|
||||||
|
}
|
||||||
|
KeepAliveState::Init | KeepAliveState::PingSent => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn maybe_timeout(&mut self, cx: &mut task::Context<'_>) -> Result<(), KeepAliveTimedOut> {
|
||||||
|
match self.state {
|
||||||
|
KeepAliveState::PingSent => {
|
||||||
|
if Pin::new(&mut self.timer).poll(cx).is_pending() {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
trace!("keep-alive timeout ({:?}) reached", self.timeout);
|
||||||
|
Err(KeepAliveTimedOut)
|
||||||
|
}
|
||||||
|
KeepAliveState::Init | KeepAliveState::Scheduled => Ok(()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== impl KeepAliveTimedOut =====
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl KeepAliveTimedOut {
|
||||||
|
pub(super) fn crate_error(self) -> crate::Error {
|
||||||
|
crate::Error::new(crate::error::Kind::Http2).with(self)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl fmt::Display for KeepAliveTimedOut {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.write_str("keep-alive timed out")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
impl std::error::Error for KeepAliveTimedOut {
|
||||||
|
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||||
|
Some(&crate::error::TimedOut)
|
||||||
|
}
|
||||||
|
}
|
||||||
548
hyper/src/proto/h2/server.rs
Normal file
548
hyper/src/proto/h2/server.rs
Normal file
|
|
@ -0,0 +1,548 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::marker::Unpin;
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use bytes::Bytes;
|
||||||
|
use h2::server::{Connection, Handshake, SendResponse};
|
||||||
|
use h2::{Reason, RecvStream};
|
||||||
|
use http::{Method, Request};
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tracing::{debug, trace, warn};
|
||||||
|
|
||||||
|
use super::{ping, PipeToSendStream, SendBuf};
|
||||||
|
use crate::body::HttpBody;
|
||||||
|
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;
|
||||||
|
use crate::proto::h2::{H2Upgraded, UpgradedSendStream};
|
||||||
|
use crate::proto::Dispatched;
|
||||||
|
use crate::service::HttpService;
|
||||||
|
|
||||||
|
use crate::upgrade::{OnUpgrade, Pending, Upgraded};
|
||||||
|
use crate::{Body, Response};
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// for performance.
|
||||||
|
//
|
||||||
|
// At the same time, a server more often has multiple clients connected, and
|
||||||
|
// so is more likely to use more resources than a client would.
|
||||||
|
const DEFAULT_CONN_WINDOW: u32 = 1024 * 1024; // 1mb
|
||||||
|
const DEFAULT_STREAM_WINDOW: u32 = 1024 * 1024; // 1mb
|
||||||
|
const DEFAULT_MAX_FRAME_SIZE: u32 = 1024 * 16; // 16kb
|
||||||
|
const DEFAULT_MAX_SEND_BUF_SIZE: usize = 1024 * 400; // 400kb
|
||||||
|
// 16 MB "sane default" taken from golang http2
|
||||||
|
const DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE: u32 = 16 << 20;
|
||||||
|
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
pub(crate) struct Config {
|
||||||
|
pub(crate) adaptive_window: bool,
|
||||||
|
pub(crate) initial_conn_window_size: u32,
|
||||||
|
pub(crate) initial_stream_window_size: u32,
|
||||||
|
pub(crate) max_frame_size: u32,
|
||||||
|
pub(crate) enable_connect_protocol: bool,
|
||||||
|
pub(crate) max_concurrent_streams: Option<u32>,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(crate) keep_alive_interval: Option<Duration>,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
pub(crate) keep_alive_timeout: Duration,
|
||||||
|
pub(crate) max_send_buffer_size: usize,
|
||||||
|
pub(crate) max_header_list_size: u32,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Config {
|
||||||
|
fn default() -> Config {
|
||||||
|
Config {
|
||||||
|
adaptive_window: false,
|
||||||
|
initial_conn_window_size: DEFAULT_CONN_WINDOW,
|
||||||
|
initial_stream_window_size: DEFAULT_STREAM_WINDOW,
|
||||||
|
max_frame_size: DEFAULT_MAX_FRAME_SIZE,
|
||||||
|
enable_connect_protocol: false,
|
||||||
|
max_concurrent_streams: None,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_interval: None,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_timeout: Duration::from_secs(20),
|
||||||
|
max_send_buffer_size: DEFAULT_MAX_SEND_BUF_SIZE,
|
||||||
|
max_header_list_size: DEFAULT_SETTINGS_MAX_HEADER_LIST_SIZE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
pub(crate) struct Server<T, S, B, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body>,
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
exec: E,
|
||||||
|
service: S,
|
||||||
|
state: State<T, B>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum State<T, B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
Handshaking {
|
||||||
|
ping_config: ping::Config,
|
||||||
|
hs: Handshake<T, SendBuf<B::Data>>,
|
||||||
|
},
|
||||||
|
Serving(Serving<T, B>),
|
||||||
|
Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
struct Serving<T, B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
ping: Option<(ping::Recorder, ping::Ponger)>,
|
||||||
|
conn: Connection<T, SendBuf<B::Data>>,
|
||||||
|
closing: Option<crate::Error>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, B, E> Server<T, S, B, E>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
pub(crate) fn new(io: T, service: S, config: &Config, exec: E) -> Server<T, S, B, E> {
|
||||||
|
let mut builder = h2::server::Builder::default();
|
||||||
|
builder
|
||||||
|
.initial_window_size(config.initial_stream_window_size)
|
||||||
|
.initial_connection_window_size(config.initial_conn_window_size)
|
||||||
|
.max_frame_size(config.max_frame_size)
|
||||||
|
.max_header_list_size(config.max_header_list_size)
|
||||||
|
.max_send_buffer_size(config.max_send_buffer_size);
|
||||||
|
if let Some(max) = config.max_concurrent_streams {
|
||||||
|
builder.max_concurrent_streams(max);
|
||||||
|
}
|
||||||
|
if config.enable_connect_protocol {
|
||||||
|
builder.enable_connect_protocol();
|
||||||
|
}
|
||||||
|
let handshake = builder.handshake(io);
|
||||||
|
|
||||||
|
let bdp = if config.adaptive_window {
|
||||||
|
Some(config.initial_stream_window_size)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
|
let ping_config = ping::Config {
|
||||||
|
bdp_initial_window: bdp,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_interval: config.keep_alive_interval,
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_timeout: config.keep_alive_timeout,
|
||||||
|
// If keep-alive is enabled for servers, always enabled while
|
||||||
|
// idle, so it can more aggressively close dead connections.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
keep_alive_while_idle: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
Server {
|
||||||
|
exec,
|
||||||
|
state: State::Handshaking {
|
||||||
|
ping_config,
|
||||||
|
hs: handshake,
|
||||||
|
},
|
||||||
|
service,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn graceful_shutdown(&mut self) {
|
||||||
|
trace!("graceful_shutdown");
|
||||||
|
match self.state {
|
||||||
|
State::Handshaking { .. } => {
|
||||||
|
// fall-through, to replace state with Closed
|
||||||
|
}
|
||||||
|
State::Serving(ref mut srv) => {
|
||||||
|
if srv.closing.is_none() {
|
||||||
|
srv.conn.graceful_shutdown();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
State::Closed => {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.state = State::Closed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, S, B, E> Future for Server<T, S, B, E>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<Dispatched>;
|
||||||
|
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let me = &mut *self;
|
||||||
|
loop {
|
||||||
|
let next = match me.state {
|
||||||
|
State::Handshaking {
|
||||||
|
ref mut hs,
|
||||||
|
ref ping_config,
|
||||||
|
} => {
|
||||||
|
let mut conn = ready!(Pin::new(hs).poll(cx).map_err(crate::Error::new_h2))?;
|
||||||
|
let ping = if ping_config.is_enabled() {
|
||||||
|
let pp = conn.ping_pong().expect("conn.ping_pong");
|
||||||
|
Some(ping::channel(pp, ping_config.clone()))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
State::Serving(Serving {
|
||||||
|
ping,
|
||||||
|
conn,
|
||||||
|
closing: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
State::Serving(ref mut srv) => {
|
||||||
|
ready!(srv.poll_server(cx, &mut me.service, &mut me.exec))?;
|
||||||
|
return Poll::Ready(Ok(Dispatched::Shutdown));
|
||||||
|
}
|
||||||
|
State::Closed => {
|
||||||
|
// graceful_shutdown was called before handshaking finished,
|
||||||
|
// nothing to do here...
|
||||||
|
return Poll::Ready(Ok(Dispatched::Shutdown));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
me.state = next;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, B> Serving<T, B>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
{
|
||||||
|
fn poll_server<S, E>(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
service: &mut S,
|
||||||
|
exec: &mut E,
|
||||||
|
) -> Poll<crate::Result<()>>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
if self.closing.is_none() {
|
||||||
|
loop {
|
||||||
|
self.poll_ping(cx);
|
||||||
|
|
||||||
|
// Check that the service is ready to accept a new request.
|
||||||
|
//
|
||||||
|
// - If not, just drive the connection some.
|
||||||
|
// - If ready, try to accept a new request from the connection.
|
||||||
|
match service.poll_ready(cx) {
|
||||||
|
Poll::Ready(Ok(())) => (),
|
||||||
|
Poll::Pending => {
|
||||||
|
// use `poll_closed` instead of `poll_accept`,
|
||||||
|
// in order to avoid accepting a request.
|
||||||
|
ready!(self.conn.poll_closed(cx).map_err(crate::Error::new_h2))?;
|
||||||
|
trace!("incoming connection complete");
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(err)) => {
|
||||||
|
let err = crate::Error::new_user_service(err);
|
||||||
|
debug!("service closed: {}", err);
|
||||||
|
|
||||||
|
let reason = err.h2_reason();
|
||||||
|
if reason == Reason::NO_ERROR {
|
||||||
|
// NO_ERROR is only used for graceful shutdowns...
|
||||||
|
trace!("interpreting NO_ERROR user error as graceful_shutdown");
|
||||||
|
self.conn.graceful_shutdown();
|
||||||
|
} else {
|
||||||
|
trace!("abruptly shutting down with {:?}", reason);
|
||||||
|
self.conn.abrupt_shutdown(reason);
|
||||||
|
}
|
||||||
|
self.closing = Some(err);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// When the service is ready, accepts an incoming request.
|
||||||
|
match ready!(self.conn.poll_accept(cx)) {
|
||||||
|
Some(Ok((req, mut respond))) => {
|
||||||
|
trace!("incoming request");
|
||||||
|
let content_length = headers::content_length_parse_all(req.headers());
|
||||||
|
let ping = self
|
||||||
|
.ping
|
||||||
|
.as_ref()
|
||||||
|
.map(|ping| ping.0.clone())
|
||||||
|
.unwrap_or_else(ping::disabled);
|
||||||
|
|
||||||
|
// Record the headers received
|
||||||
|
ping.record_non_data();
|
||||||
|
|
||||||
|
let is_connect = req.method() == Method::CONNECT;
|
||||||
|
let (mut parts, stream) = req.into_parts();
|
||||||
|
let (mut req, connect_parts) = if !is_connect {
|
||||||
|
(
|
||||||
|
Request::from_parts(
|
||||||
|
parts,
|
||||||
|
crate::Body::h2(stream, content_length.into(), ping),
|
||||||
|
),
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
if content_length.map_or(false, |len| len != 0) {
|
||||||
|
warn!("h2 connect request with non-zero body not supported");
|
||||||
|
respond.send_reset(h2::Reason::INTERNAL_ERROR);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
let (pending, upgrade) = crate::upgrade::pending();
|
||||||
|
debug_assert!(parts.extensions.get::<OnUpgrade>().is_none());
|
||||||
|
parts.extensions.insert(upgrade);
|
||||||
|
(
|
||||||
|
Request::from_parts(parts, crate::Body::empty()),
|
||||||
|
Some(ConnectParts {
|
||||||
|
pending,
|
||||||
|
ping,
|
||||||
|
recv_stream: stream,
|
||||||
|
}),
|
||||||
|
)
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(protocol) = req.extensions_mut().remove::<h2::ext::Protocol>() {
|
||||||
|
req.extensions_mut().insert(Protocol::from_inner(protocol));
|
||||||
|
}
|
||||||
|
|
||||||
|
let fut = H2Stream::new(service.call(req), connect_parts, respond);
|
||||||
|
exec.execute_h2stream(fut);
|
||||||
|
}
|
||||||
|
Some(Err(e)) => {
|
||||||
|
return Poll::Ready(Err(crate::Error::new_h2(e)));
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
// no more incoming streams...
|
||||||
|
if let Some((ref ping, _)) = self.ping {
|
||||||
|
ping.ensure_not_timed_out()?;
|
||||||
|
}
|
||||||
|
|
||||||
|
trace!("incoming connection complete");
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
debug_assert!(
|
||||||
|
self.closing.is_some(),
|
||||||
|
"poll_server broke loop without closing"
|
||||||
|
);
|
||||||
|
|
||||||
|
ready!(self.conn.poll_closed(cx).map_err(crate::Error::new_h2))?;
|
||||||
|
|
||||||
|
Poll::Ready(Err(self.closing.take().expect("polled after error")))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_ping(&mut self, cx: &mut task::Context<'_>) {
|
||||||
|
if let Some((_, ref mut estimator)) = self.ping {
|
||||||
|
match estimator.poll(cx) {
|
||||||
|
Poll::Ready(ping::Ponged::SizeUpdate(wnd)) => {
|
||||||
|
self.conn.set_target_window_size(wnd);
|
||||||
|
let _ = self.conn.set_initial_window_size(wnd);
|
||||||
|
}
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
Poll::Ready(ping::Ponged::KeepAliveTimedOut) => {
|
||||||
|
debug!("keep-alive timed out, closing connection");
|
||||||
|
self.conn.abrupt_shutdown(h2::Reason::NO_ERROR);
|
||||||
|
}
|
||||||
|
Poll::Pending => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct H2Stream<F, B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
reply: SendResponse<SendBuf<B::Data>>,
|
||||||
|
#[pin]
|
||||||
|
state: H2StreamState<F, B>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[project = H2StreamStateProj]
|
||||||
|
enum H2StreamState<F, B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
Service {
|
||||||
|
#[pin]
|
||||||
|
fut: F,
|
||||||
|
connect_parts: Option<ConnectParts>,
|
||||||
|
},
|
||||||
|
Body {
|
||||||
|
#[pin]
|
||||||
|
pipe: PipeToSendStream<B>,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct ConnectParts {
|
||||||
|
pending: Pending,
|
||||||
|
ping: Recorder,
|
||||||
|
recv_stream: RecvStream,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B> H2Stream<F, B>
|
||||||
|
where
|
||||||
|
B: HttpBody,
|
||||||
|
{
|
||||||
|
fn new(
|
||||||
|
fut: F,
|
||||||
|
connect_parts: Option<ConnectParts>,
|
||||||
|
respond: SendResponse<SendBuf<B::Data>>,
|
||||||
|
) -> H2Stream<F, B> {
|
||||||
|
H2Stream {
|
||||||
|
reply: respond,
|
||||||
|
state: H2StreamState::Service { fut, connect_parts },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
macro_rules! reply {
|
||||||
|
($me:expr, $res:expr, $eos:expr) => {{
|
||||||
|
match $me.reply.send_response($res, $eos) {
|
||||||
|
Ok(tx) => tx,
|
||||||
|
Err(e) => {
|
||||||
|
debug!("send response error: {}", e);
|
||||||
|
$me.reply.send_reset(Reason::INTERNAL_ERROR);
|
||||||
|
return Poll::Ready(Err(crate::Error::new_h2(e)));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}};
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B, E> H2Stream<F, B>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<Response<B>, E>>,
|
||||||
|
B: HttpBody,
|
||||||
|
B::Data: 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
fn poll2(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<crate::Result<()>> {
|
||||||
|
let mut me = self.project();
|
||||||
|
loop {
|
||||||
|
let next = match me.state.as_mut().project() {
|
||||||
|
H2StreamStateProj::Service {
|
||||||
|
fut: h,
|
||||||
|
connect_parts,
|
||||||
|
} => {
|
||||||
|
let res = match h.poll(cx) {
|
||||||
|
Poll::Ready(Ok(r)) => r,
|
||||||
|
Poll::Pending => {
|
||||||
|
// Response is not yet ready, so we want to check if the client has sent a
|
||||||
|
// RST_STREAM frame which would cancel the current request.
|
||||||
|
if let Poll::Ready(reason) =
|
||||||
|
me.reply.poll_reset(cx).map_err(crate::Error::new_h2)?
|
||||||
|
{
|
||||||
|
debug!("stream received RST_STREAM: {:?}", reason);
|
||||||
|
return Poll::Ready(Err(crate::Error::new_h2(reason.into())));
|
||||||
|
}
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
Poll::Ready(Err(e)) => {
|
||||||
|
let err = crate::Error::new_user_service(e);
|
||||||
|
warn!("http2 service errored: {}", err);
|
||||||
|
me.reply.send_reset(err.h2_reason());
|
||||||
|
return Poll::Ready(Err(err));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
let (head, body) = res.into_parts();
|
||||||
|
let mut res = ::http::Response::from_parts(head, ());
|
||||||
|
super::strip_connection_headers(res.headers_mut(), false);
|
||||||
|
|
||||||
|
// set Date header if it isn't already set...
|
||||||
|
res.headers_mut()
|
||||||
|
.entry(::http::header::DATE)
|
||||||
|
.or_insert_with(date::update_and_header_value);
|
||||||
|
|
||||||
|
if let Some(connect_parts) = connect_parts.take() {
|
||||||
|
if res.status().is_success() {
|
||||||
|
if headers::content_length_parse_all(res.headers())
|
||||||
|
.map_or(false, |len| len != 0)
|
||||||
|
{
|
||||||
|
warn!("h2 successful response to CONNECT request with body not supported");
|
||||||
|
me.reply.send_reset(h2::Reason::INTERNAL_ERROR);
|
||||||
|
return Poll::Ready(Err(crate::Error::new_user_header()));
|
||||||
|
}
|
||||||
|
let send_stream = reply!(me, res, false);
|
||||||
|
connect_parts.pending.fulfill(Upgraded::new(
|
||||||
|
H2Upgraded {
|
||||||
|
ping: connect_parts.ping,
|
||||||
|
recv_stream: connect_parts.recv_stream,
|
||||||
|
send_stream: unsafe { UpgradedSendStream::new(send_stream) },
|
||||||
|
buf: Bytes::new(),
|
||||||
|
},
|
||||||
|
Bytes::new(),
|
||||||
|
));
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
if !body.is_end_stream() {
|
||||||
|
// automatically set Content-Length from body...
|
||||||
|
if let Some(len) = body.size_hint().exact() {
|
||||||
|
headers::set_content_length_if_missing(res.headers_mut(), len);
|
||||||
|
}
|
||||||
|
|
||||||
|
let body_tx = reply!(me, res, false);
|
||||||
|
H2StreamState::Body {
|
||||||
|
pipe: PipeToSendStream::new(body, body_tx),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
reply!(me, res, true);
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
H2StreamStateProj::Body { pipe } => {
|
||||||
|
return pipe.poll(cx);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
me.state.set(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<F, B, E> Future for H2Stream<F, B>
|
||||||
|
where
|
||||||
|
F: Future<Output = Result<Response<B>, E>>,
|
||||||
|
B: HttpBody,
|
||||||
|
B::Data: 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Output = ();
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
self.poll2(cx).map(|res| {
|
||||||
|
if let Err(e) = res {
|
||||||
|
debug!("stream error: {}", e);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
71
hyper/src/proto/mod.rs
Normal file
71
hyper/src/proto/mod.rs
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
//! Pieces pertaining to the HTTP message protocol.
|
||||||
|
|
||||||
|
cfg_feature! {
|
||||||
|
#![feature = "http1"]
|
||||||
|
|
||||||
|
pub(crate) mod h1;
|
||||||
|
|
||||||
|
pub(crate) use self::h1::Conn;
|
||||||
|
|
||||||
|
#[cfg(feature = "client")]
|
||||||
|
pub(crate) use self::h1::dispatch;
|
||||||
|
#[cfg(feature = "server")]
|
||||||
|
pub(crate) use self::h1::ServerTransaction;
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) mod h2;
|
||||||
|
|
||||||
|
/// An Incoming Message head. Includes request/status line, and headers.
|
||||||
|
#[derive(Debug, Default)]
|
||||||
|
pub(crate) struct MessageHead<S> {
|
||||||
|
/// HTTP version of the message.
|
||||||
|
pub(crate) version: http::Version,
|
||||||
|
/// Subject (request line or status line) of Incoming message.
|
||||||
|
pub(crate) subject: S,
|
||||||
|
/// Headers of the Incoming message.
|
||||||
|
pub(crate) headers: http::HeaderMap,
|
||||||
|
/// Extensions.
|
||||||
|
extensions: http::Extensions,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An incoming request message.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) type RequestHead = MessageHead<RequestLine>;
|
||||||
|
|
||||||
|
#[derive(Debug, Default, PartialEq)]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) struct RequestLine(pub(crate) http::Method, pub(crate) http::Uri);
|
||||||
|
|
||||||
|
/// An incoming response message.
|
||||||
|
#[cfg(all(feature = "http1", feature = "client"))]
|
||||||
|
pub(crate) type ResponseHead = MessageHead<http::StatusCode>;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) enum BodyLength {
|
||||||
|
/// Content-Length
|
||||||
|
Known(u64),
|
||||||
|
/// Transfer-Encoding: chunked (if h1)
|
||||||
|
Unknown,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Status of when a Disaptcher future completes.
|
||||||
|
pub(crate) enum Dispatched {
|
||||||
|
/// Dispatcher completely shutdown connection.
|
||||||
|
Shutdown,
|
||||||
|
/// Dispatcher has pending upgrade, and so did not shutdown.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Upgrade(crate::upgrade::Pending),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl MessageHead<http::StatusCode> {
|
||||||
|
fn into_response<B>(self, body: B) -> http::Response<B> {
|
||||||
|
let mut res = http::Response::new(body);
|
||||||
|
*res.status_mut() = self.subject;
|
||||||
|
*res.headers_mut() = self.headers;
|
||||||
|
*res.version_mut() = self.version;
|
||||||
|
*res.extensions_mut() = self.extensions;
|
||||||
|
res
|
||||||
|
}
|
||||||
|
}
|
||||||
12
hyper/src/rt.rs
Normal file
12
hyper/src/rt.rs
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
//! Runtime components
|
||||||
|
//!
|
||||||
|
//! By default, hyper includes the [tokio](https://tokio.rs) runtime.
|
||||||
|
//!
|
||||||
|
//! If the `runtime` feature is disabled, the types in this module can be used
|
||||||
|
//! to plug in other runtimes.
|
||||||
|
|
||||||
|
/// An executor of futures.
|
||||||
|
pub trait Executor<Fut> {
|
||||||
|
/// Place the future into the executor to be run.
|
||||||
|
fn execute(&self, fut: Fut);
|
||||||
|
}
|
||||||
96
hyper/src/server/accept.rs
Normal file
96
hyper/src/server/accept.rs
Normal file
|
|
@ -0,0 +1,96 @@
|
||||||
|
//! The `Accept` trait and supporting types.
|
||||||
|
//!
|
||||||
|
//! This module contains:
|
||||||
|
//!
|
||||||
|
//! - The [`Accept`](Accept) trait used to asynchronously accept incoming
|
||||||
|
//! connections.
|
||||||
|
//! - Utilities like `poll_fn` to ease creating a custom `Accept`.
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
use futures_core::Stream;
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use crate::common::{
|
||||||
|
task::{self, Poll},
|
||||||
|
Pin,
|
||||||
|
};
|
||||||
|
/// Asynchronously accept incoming connections.
|
||||||
|
pub trait Accept {
|
||||||
|
/// The connection type that can be accepted.
|
||||||
|
type Conn;
|
||||||
|
/// The error type that can occur when accepting a connection.
|
||||||
|
type Error;
|
||||||
|
/// Poll to accept the next connection.
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Conn, Self::Error>>>;
|
||||||
|
}
|
||||||
|
/// Create an `Accept` with a polling function.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// use std::task::Poll;
|
||||||
|
/// use hyper::server::{accept, Server};
|
||||||
|
///
|
||||||
|
/// # let mock_conn = ();
|
||||||
|
/// // If we created some mocked connection...
|
||||||
|
/// let mut conn = Some(mock_conn);
|
||||||
|
///
|
||||||
|
/// // And accept just the mocked conn once...
|
||||||
|
/// let once = accept::poll_fn(move |cx| {
|
||||||
|
/// Poll::Ready(conn.take().map(Ok::<_, ()>))
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// let builder = Server::builder(once);
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn poll_fn<F, IO, E>(func: F) -> impl Accept<Conn = IO, Error = E>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut task::Context<'_>) -> Poll<Option<Result<IO, E>>>,
|
||||||
|
{
|
||||||
|
struct PollFn<F>(F);
|
||||||
|
impl<F> Unpin for PollFn<F> {}
|
||||||
|
impl<F, IO, E> Accept for PollFn<F>
|
||||||
|
where
|
||||||
|
F: FnMut(&mut task::Context<'_>) -> Poll<Option<Result<IO, E>>>,
|
||||||
|
{
|
||||||
|
type Conn = IO;
|
||||||
|
type Error = E;
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
(self.get_mut().0)(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PollFn(func)
|
||||||
|
}
|
||||||
|
/// Adapt a `Stream` of incoming connections into an `Accept`.
|
||||||
|
///
|
||||||
|
/// # Optional
|
||||||
|
///
|
||||||
|
/// This function requires enabling the `stream` feature in your
|
||||||
|
/// `Cargo.toml`.
|
||||||
|
#[cfg(feature = "stream")]
|
||||||
|
pub fn from_stream<S, IO, E>(stream: S) -> impl Accept<Conn = IO, Error = E>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<IO, E>>,
|
||||||
|
{
|
||||||
|
pin_project! {
|
||||||
|
struct FromStream < S > { #[pin] stream : S, }
|
||||||
|
}
|
||||||
|
impl<S, IO, E> Accept for FromStream<S>
|
||||||
|
where
|
||||||
|
S: Stream<Item = Result<IO, E>>,
|
||||||
|
{
|
||||||
|
type Conn = IO;
|
||||||
|
type Error = E;
|
||||||
|
fn poll_accept(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
self.project().stream.poll_next(cx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
FromStream { stream }
|
||||||
|
}
|
||||||
961
hyper/src/server/conn.rs
Normal file
961
hyper/src/server/conn.rs
Normal file
|
|
@ -0,0 +1,961 @@
|
||||||
|
//! Lower-level Server connection API.
|
||||||
|
//!
|
||||||
|
//! The types in this module are to provide a lower-level API based around a
|
||||||
|
//! single connection. Accepting a connection and binding it with a service
|
||||||
|
//! are not handled at this level. This module provides the building blocks to
|
||||||
|
//! customize those things externally.
|
||||||
|
//!
|
||||||
|
//! If you don't have need to manage connections yourself, consider using the
|
||||||
|
//! higher-level [Server](super) API.
|
||||||
|
//!
|
||||||
|
//! ## Example
|
||||||
|
//! A simple example that uses the `Http` struct to talk HTTP over a Tokio TCP stream
|
||||||
|
//! ```no_run
|
||||||
|
//! # #[cfg(all(feature = "http1", feature = "runtime"))]
|
||||||
|
//! # mod rt {
|
||||||
|
//! use http::{Request, Response, StatusCode};
|
||||||
|
//! use hyper::{server::conn::Http, service::service_fn, Body};
|
||||||
|
//! use std::{net::SocketAddr, convert::Infallible};
|
||||||
|
//! use tokio::net::TcpListener;
|
||||||
|
//!
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
|
||||||
|
//! let addr: SocketAddr = ([127, 0, 0, 1], 8080).into();
|
||||||
|
//!
|
||||||
|
//! let mut tcp_listener = TcpListener::bind(addr).await?;
|
||||||
|
//! loop {
|
||||||
|
//! let (tcp_stream, _) = tcp_listener.accept().await?;
|
||||||
|
//! tokio::task::spawn(async move {
|
||||||
|
//! if let Err(http_err) = Http::new()
|
||||||
|
//! .http1_only(true)
|
||||||
|
//! .http1_keep_alive(true)
|
||||||
|
//! .serve_connection(tcp_stream, service_fn(hello))
|
||||||
|
//! .await {
|
||||||
|
//! eprintln!("Error while serving HTTP connection: {}", http_err);
|
||||||
|
//! }
|
||||||
|
//! });
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn hello(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
//! Ok(Response::new(Body::from("Hello World!")))
|
||||||
|
//! }
|
||||||
|
//! # }
|
||||||
|
//! ```
|
||||||
|
#[cfg(
|
||||||
|
all(
|
||||||
|
any(feature = "http1", feature = "http2"),
|
||||||
|
not(all(feature = "http1", feature = "http2"))
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
use std::marker::PhantomData;
|
||||||
|
#[cfg(all(any(feature = "http1", feature = "http2"), feature = "runtime"))]
|
||||||
|
use std::time::Duration;
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
use crate::common::io::Rewind;
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
use crate::error::{Kind, Parse};
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
use crate::upgrade::Upgraded;
|
||||||
|
cfg_feature! {
|
||||||
|
#![any(feature = "http1", feature = "http2")] use std::error::Error as StdError; use
|
||||||
|
std::fmt; use bytes::Bytes; use pin_project_lite::pin_project; use tokio::io:: {
|
||||||
|
AsyncRead, AsyncWrite }; use tracing::trace; 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; pub (super) use
|
||||||
|
self::upgrades::UpgradeableConnection;
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
pub use super::tcp::{AddrIncoming, AddrStream};
|
||||||
|
/// A lower-level configuration of the HTTP protocol.
|
||||||
|
///
|
||||||
|
/// This structure is used to configure options for an HTTP server connection.
|
||||||
|
///
|
||||||
|
/// If you don't have need to manage connections yourself, consider using the
|
||||||
|
/// higher-level [Server](super) API.
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
pub(crate) struct Http<E = Exec> {
|
||||||
|
pub(crate) exec: E,
|
||||||
|
h1_half_close: bool,
|
||||||
|
h1_keep_alive: bool,
|
||||||
|
h1_title_case_headers: bool,
|
||||||
|
h1_preserve_header_case: bool,
|
||||||
|
#[cfg(all(feature = "http1", feature = "runtime"))]
|
||||||
|
h1_header_read_timeout: Option<Duration>,
|
||||||
|
h1_writev: Option<bool>,
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
h2_builder: proto::h2::server::Config,
|
||||||
|
mode: ConnectionMode,
|
||||||
|
max_buf_size: Option<usize>,
|
||||||
|
pipeline_flush: bool,
|
||||||
|
}
|
||||||
|
/// The internal mode of HTTP protocol which indicates the behavior when a parse error occurs.
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[derive(Clone, Debug, PartialEq)]
|
||||||
|
enum ConnectionMode {
|
||||||
|
/// Always use HTTP/1 and do not upgrade when a parse error occurs.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
H1Only,
|
||||||
|
/// Always use HTTP/2.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
H2Only,
|
||||||
|
/// Use HTTP/1 and try to upgrade to h2 when a parse error occurs.
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
Fallback,
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pin_project! {
|
||||||
|
#[doc = " A future binding a connection with a Service."] #[doc = ""] #[doc =
|
||||||
|
" Polling this future will drive HTTP forward."] #[must_use =
|
||||||
|
"futures do nothing unless polled"] #[cfg_attr(docsrs, doc(cfg(any(feature = "http1",
|
||||||
|
feature = "http2"))))] pub struct Connection < T, S, E = Exec > where S : HttpService
|
||||||
|
< Body >, { pub (super) conn : Option < ProtoServer < T, S::ResBody, S, E >>,
|
||||||
|
fallback : Fallback < E >, }
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
type Http1Dispatcher<T, B, S> = proto::h1::Dispatcher<
|
||||||
|
proto::h1::dispatch::Server<S, Body>,
|
||||||
|
B,
|
||||||
|
T,
|
||||||
|
proto::ServerTransaction,
|
||||||
|
>;
|
||||||
|
#[cfg(all(not(feature = "http1"), feature = "http2"))]
|
||||||
|
type Http1Dispatcher<T, B, S> = (Never, PhantomData<(T, Box<Pin<B>>, Box<Pin<S>>)>);
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
type Http2Server<T, B, S, E> = proto::h2::Server<Rewind<T>, S, B, E>;
|
||||||
|
#[cfg(all(not(feature = "http2"), feature = "http1"))]
|
||||||
|
type Http2Server<T, B, S, E> = (
|
||||||
|
Never,
|
||||||
|
PhantomData<(T, Box<Pin<S>>, Box<Pin<B>>, Box<Pin<E>>)>,
|
||||||
|
);
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
pin_project! {
|
||||||
|
#[project = ProtoServerProj] pub (super) enum ProtoServer < T, B, S, E = Exec > where
|
||||||
|
S : HttpService < Body >, B : HttpBody, { H1 { #[pin] h1 : Http1Dispatcher < T, B, S
|
||||||
|
>, }, H2 { #[pin] h2 : Http2Server < T, B, S, E >, }, }
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
enum Fallback<E> {
|
||||||
|
ToHttp2(proto::h2::server::Config, E),
|
||||||
|
Http1Only,
|
||||||
|
}
|
||||||
|
#[cfg(
|
||||||
|
all(
|
||||||
|
any(feature = "http1", feature = "http2"),
|
||||||
|
not(all(feature = "http1", feature = "http2"))
|
||||||
|
)
|
||||||
|
)]
|
||||||
|
type Fallback<E> = PhantomData<E>;
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
impl<E> Fallback<E> {
|
||||||
|
fn to_h2(&self) -> bool {
|
||||||
|
match *self {
|
||||||
|
Fallback::ToHttp2(..) => true,
|
||||||
|
Fallback::Http1Only => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
impl<E> Unpin for Fallback<E> {}
|
||||||
|
/// Deconstructed parts of a `Connection`.
|
||||||
|
///
|
||||||
|
/// This allows taking apart a `Connection` at a later time, in order to
|
||||||
|
/// reclaim the IO object, and additional related pieces.
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
pub(crate) struct Parts<T, S> {
|
||||||
|
/// The original IO object used in the handshake.
|
||||||
|
pub(crate) io: T,
|
||||||
|
/// A buffer of bytes that have been read but not processed as HTTP.
|
||||||
|
///
|
||||||
|
/// If the client sent additional bytes after its last request, and
|
||||||
|
/// this connection "ended" with an upgrade, the read buffer will contain
|
||||||
|
/// those bytes.
|
||||||
|
///
|
||||||
|
/// You will want to check for any existing bytes if you plan to continue
|
||||||
|
/// communicating on the IO object.
|
||||||
|
pub(crate) read_buf: Bytes,
|
||||||
|
/// The `Service` used to serve this connection.
|
||||||
|
pub(crate) service: S,
|
||||||
|
_inner: (),
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl Http {
|
||||||
|
/// Creates a new instance of the HTTP protocol, ready to spawn a server or
|
||||||
|
/// start accepting connections.
|
||||||
|
pub(crate) fn new() -> Http {
|
||||||
|
Http {
|
||||||
|
exec: Exec::Default,
|
||||||
|
h1_half_close: false,
|
||||||
|
h1_keep_alive: true,
|
||||||
|
h1_title_case_headers: false,
|
||||||
|
h1_preserve_header_case: false,
|
||||||
|
#[cfg(all(feature = "http1", feature = "runtime"))]
|
||||||
|
h1_header_read_timeout: None,
|
||||||
|
h1_writev: None,
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
h2_builder: Default::default(),
|
||||||
|
mode: ConnectionMode::default(),
|
||||||
|
max_buf_size: None,
|
||||||
|
pipeline_flush: false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl<E> Http<E> {
|
||||||
|
/// Sets whether HTTP1 is required.
|
||||||
|
///
|
||||||
|
/// Default is false
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_only(&mut self, val: bool) -> &mut Self {
|
||||||
|
if val {
|
||||||
|
self.mode = ConnectionMode::H1Only;
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
{
|
||||||
|
self.mode = ConnectionMode::Fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether HTTP/1 connections should support half-closures.
|
||||||
|
///
|
||||||
|
/// Clients can chose to shutdown their write-side while waiting
|
||||||
|
/// for the server to respond. Setting this to `true` will
|
||||||
|
/// prevent closing the connection immediately if `read`
|
||||||
|
/// detects an EOF in the middle of a request.
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_half_close(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.h1_half_close = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Enables or disables HTTP/1 keep-alive.
|
||||||
|
///
|
||||||
|
/// Default is true.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_keep_alive(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.h1_keep_alive = val;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether HTTP/1 connections will write header names as title case at
|
||||||
|
/// the socket level.
|
||||||
|
///
|
||||||
|
/// Note that this setting does not affect HTTP/2.
|
||||||
|
///
|
||||||
|
/// Default is false.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_title_case_headers(&mut self, enabled: bool) -> &mut Self {
|
||||||
|
self.h1_title_case_headers = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether to support preserving original header cases.
|
||||||
|
///
|
||||||
|
/// Currently, this will record the original cases received, and store them
|
||||||
|
/// in a private extension on the `Request`. It will also look for and use
|
||||||
|
/// such an extension in any provided `Response`.
|
||||||
|
///
|
||||||
|
/// Since the relevant extension is still private, there is no way to
|
||||||
|
/// interact with the original cases. The only effect this can have now is
|
||||||
|
/// to forward the cases in a proxy-like fashion.
|
||||||
|
///
|
||||||
|
/// Note that this setting does not affect HTTP/2.
|
||||||
|
///
|
||||||
|
/// Default is false.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_preserve_header_case(&mut self, enabled: bool) -> &mut Self {
|
||||||
|
self.h1_preserve_header_case = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set a timeout for reading client request headers. If a client does not
|
||||||
|
/// transmit the entire header within this time, the connection is closed.
|
||||||
|
///
|
||||||
|
/// Default is None.
|
||||||
|
#[cfg(all(feature = "http1", feature = "runtime"))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(all(feature = "http1", feature = "runtime"))))]
|
||||||
|
pub(crate) fn http1_header_read_timeout(
|
||||||
|
&mut self,
|
||||||
|
read_timeout: Duration,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.h1_header_read_timeout = Some(read_timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||||
|
/// or always flatten into a single buffer.
|
||||||
|
///
|
||||||
|
/// Note that setting this to false may mean more copies of body data,
|
||||||
|
/// but may also improve performance when an IO transport doesn't
|
||||||
|
/// support vectored writes well, such as most TLS implementations.
|
||||||
|
///
|
||||||
|
/// Setting this to true will force hyper to use queued strategy
|
||||||
|
/// which may eliminate unnecessary cloning on some TLS backends
|
||||||
|
///
|
||||||
|
/// Default is `auto`. In this mode hyper will try to guess which
|
||||||
|
/// mode to use
|
||||||
|
#[inline]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_writev(&mut self, val: bool) -> &mut Self {
|
||||||
|
self.h1_writev = Some(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets whether HTTP2 is required.
|
||||||
|
///
|
||||||
|
/// Default is false
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_only(&mut self, val: bool) -> &mut Self {
|
||||||
|
if val {
|
||||||
|
self.mode = ConnectionMode::H2Only;
|
||||||
|
} else {
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
{
|
||||||
|
self.mode = ConnectionMode::Fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
|
||||||
|
/// stream-level flow control.
|
||||||
|
///
|
||||||
|
/// Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// If not set, hyper will use a default.
|
||||||
|
///
|
||||||
|
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_initial_stream_window_size(
|
||||||
|
&mut self,
|
||||||
|
sz: impl Into<Option<u32>>,
|
||||||
|
) -> &mut Self {
|
||||||
|
if let Some(sz) = sz.into() {
|
||||||
|
self.h2_builder.adaptive_window = false;
|
||||||
|
self.h2_builder.initial_stream_window_size = sz;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the max connection-level flow control for HTTP2.
|
||||||
|
///
|
||||||
|
/// Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// If not set, hyper will use a default.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_initial_connection_window_size(
|
||||||
|
&mut self,
|
||||||
|
sz: impl Into<Option<u32>>,
|
||||||
|
) -> &mut Self {
|
||||||
|
if let Some(sz) = sz.into() {
|
||||||
|
self.h2_builder.adaptive_window = false;
|
||||||
|
self.h2_builder.initial_conn_window_size = sz;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets whether to use an adaptive flow control.
|
||||||
|
///
|
||||||
|
/// Enabling this will override the limits set in
|
||||||
|
/// `http2_initial_stream_window_size` and
|
||||||
|
/// `http2_initial_connection_window_size`.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_adaptive_window(&mut self, enabled: bool) -> &mut Self {
|
||||||
|
use proto::h2::SPEC_WINDOW_SIZE;
|
||||||
|
self.h2_builder.adaptive_window = enabled;
|
||||||
|
if enabled {
|
||||||
|
self.h2_builder.initial_conn_window_size = SPEC_WINDOW_SIZE;
|
||||||
|
self.h2_builder.initial_stream_window_size = SPEC_WINDOW_SIZE;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the maximum frame size to use for HTTP2.
|
||||||
|
///
|
||||||
|
/// Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// If not set, hyper will use a default.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_frame_size(
|
||||||
|
&mut self,
|
||||||
|
sz: impl Into<Option<u32>>,
|
||||||
|
) -> &mut Self {
|
||||||
|
if let Some(sz) = sz.into() {
|
||||||
|
self.h2_builder.max_frame_size = sz;
|
||||||
|
}
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2
|
||||||
|
/// connections.
|
||||||
|
///
|
||||||
|
/// Default is no limit (`std::u32::MAX`). Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_concurrent_streams(
|
||||||
|
&mut self,
|
||||||
|
max: impl Into<Option<u32>>,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.h2_builder.max_concurrent_streams = max.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets an interval for HTTP2 Ping frames should be sent to keep a
|
||||||
|
/// connection alive.
|
||||||
|
///
|
||||||
|
/// Pass `None` to disable HTTP2 keep-alive.
|
||||||
|
///
|
||||||
|
/// Default is currently disabled.
|
||||||
|
///
|
||||||
|
/// # Cargo Feature
|
||||||
|
///
|
||||||
|
/// Requires the `runtime` cargo feature to be enabled.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_keep_alive_interval(
|
||||||
|
&mut self,
|
||||||
|
interval: impl Into<Option<Duration>>,
|
||||||
|
) -> &mut Self {
|
||||||
|
self.h2_builder.keep_alive_interval = interval.into();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets a timeout for receiving an acknowledgement of the keep-alive ping.
|
||||||
|
///
|
||||||
|
/// If the ping is not acknowledged within the timeout, the connection will
|
||||||
|
/// be closed. Does nothing if `http2_keep_alive_interval` is disabled.
|
||||||
|
///
|
||||||
|
/// Default is 20 seconds.
|
||||||
|
///
|
||||||
|
/// # Cargo Feature
|
||||||
|
///
|
||||||
|
/// Requires the `runtime` cargo feature to be enabled.
|
||||||
|
#[cfg(feature = "runtime")]
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_keep_alive_timeout(&mut self, timeout: Duration) -> &mut Self {
|
||||||
|
self.h2_builder.keep_alive_timeout = timeout;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the maximum write buffer size for each HTTP/2 stream.
|
||||||
|
///
|
||||||
|
/// Default is currently ~400KB, but may change.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The value must be no larger than `u32::MAX`.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_send_buf_size(&mut self, max: usize) -> &mut Self {
|
||||||
|
assert!(max <= std::u32::MAX as usize);
|
||||||
|
self.h2_builder.max_send_buffer_size = max;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Enables the [extended CONNECT protocol].
|
||||||
|
///
|
||||||
|
/// [extended CONNECT protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) fn http2_enable_connect_protocol(&mut self) -> &mut Self {
|
||||||
|
self.h2_builder.enable_connect_protocol = true;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the max size of received header frames.
|
||||||
|
///
|
||||||
|
/// Default is currently ~16MB, but may change.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_header_list_size(&mut self, max: u32) -> &mut Self {
|
||||||
|
self.h2_builder.max_header_list_size = max;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the maximum buffer size for the connection.
|
||||||
|
///
|
||||||
|
/// Default is ~400kb.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The minimum value allowed is 8192. This method panics if the passed `max` is less than the minimum.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn max_buf_size(&mut self, max: usize) -> &mut Self {
|
||||||
|
assert!(
|
||||||
|
max >= proto::h1::MINIMUM_MAX_BUFFER_SIZE,
|
||||||
|
"the max_buf_size cannot be smaller than the minimum that h1 specifies."
|
||||||
|
);
|
||||||
|
self.max_buf_size = Some(max);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Aggregates flushes to better support pipelined responses.
|
||||||
|
///
|
||||||
|
/// Experimental, may have bugs.
|
||||||
|
///
|
||||||
|
/// Default is false.
|
||||||
|
pub(crate) fn pipeline_flush(&mut self, enabled: bool) -> &mut Self {
|
||||||
|
self.pipeline_flush = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the executor used to spawn background tasks.
|
||||||
|
///
|
||||||
|
/// Default uses implicit default (like `tokio::spawn`).
|
||||||
|
pub(crate) fn with_executor<E2>(self, exec: E2) -> Http<E2> {
|
||||||
|
Http {
|
||||||
|
exec,
|
||||||
|
h1_half_close: self.h1_half_close,
|
||||||
|
h1_keep_alive: self.h1_keep_alive,
|
||||||
|
h1_title_case_headers: self.h1_title_case_headers,
|
||||||
|
h1_preserve_header_case: self.h1_preserve_header_case,
|
||||||
|
#[cfg(all(feature = "http1", feature = "runtime"))]
|
||||||
|
h1_header_read_timeout: self.h1_header_read_timeout,
|
||||||
|
h1_writev: self.h1_writev,
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
h2_builder: self.h2_builder,
|
||||||
|
mode: self.mode,
|
||||||
|
max_buf_size: self.max_buf_size,
|
||||||
|
pipeline_flush: self.pipeline_flush,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Bind a connection together with a [`Service`](crate::service::Service).
|
||||||
|
///
|
||||||
|
/// This returns a Future that must be polled in order for HTTP to be
|
||||||
|
/// driven on the connection.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # use hyper::{Body, Request, Response};
|
||||||
|
/// # use hyper::service::Service;
|
||||||
|
/// # use hyper::server::conn::Http;
|
||||||
|
/// # use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
/// # async fn run<I, S>(some_io: I, some_service: S)
|
||||||
|
/// # where
|
||||||
|
/// # I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
/// # S: Service<hyper::Request<Body>, Response=hyper::Response<Body>> + Send + 'static,
|
||||||
|
/// # S::Error: Into<Box<dyn std::error::Error + Send + Sync>>,
|
||||||
|
/// # S::Future: Send,
|
||||||
|
/// # {
|
||||||
|
/// let http = Http::new();
|
||||||
|
/// let conn = http.serve_connection(some_io, some_service);
|
||||||
|
///
|
||||||
|
/// if let Err(e) = conn.await {
|
||||||
|
/// eprintln!("server connection error: {}", e);
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub(crate) fn serve_connection<S, I, Bd>(
|
||||||
|
&self,
|
||||||
|
io: I,
|
||||||
|
service: S,
|
||||||
|
) -> Connection<I, S, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = Bd>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
Bd: HttpBody + 'static,
|
||||||
|
Bd::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
E: ConnStreamExec<S::Future, Bd>,
|
||||||
|
{
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
macro_rules! h1 {
|
||||||
|
() => {
|
||||||
|
{ let mut conn = proto::Conn::new(io); if ! self.h1_keep_alive { conn
|
||||||
|
.disable_keep_alive(); } if self.h1_half_close { conn
|
||||||
|
.set_allow_half_close(); } if self.h1_title_case_headers { conn
|
||||||
|
.set_title_case_headers(); } if self.h1_preserve_header_case { conn
|
||||||
|
.set_preserve_header_case(); } #[cfg(all(feature = "http1", feature =
|
||||||
|
"runtime"))] if let Some(header_read_timeout) = self
|
||||||
|
.h1_header_read_timeout { conn
|
||||||
|
.set_http1_header_read_timeout(header_read_timeout); } if let
|
||||||
|
Some(writev) = self.h1_writev { if writev { conn
|
||||||
|
.set_write_strategy_queue(); } else { conn.set_write_strategy_flatten();
|
||||||
|
} } conn.set_flush_pipeline(self.pipeline_flush); if let Some(max) = self
|
||||||
|
.max_buf_size { conn.set_max_buf_size(max); } let sd =
|
||||||
|
proto::h1::dispatch::Server::new(service); ProtoServer::H1 { h1 :
|
||||||
|
proto::h1::Dispatcher::new(sd, conn), } }
|
||||||
|
};
|
||||||
|
}
|
||||||
|
let proto = match self.mode {
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
ConnectionMode::H1Only => h1!(),
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
ConnectionMode::H1Only | ConnectionMode::Fallback => h1!(),
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
ConnectionMode::H2Only => {
|
||||||
|
let rewind_io = Rewind::new(io);
|
||||||
|
let h2 = proto::h2::Server::new(
|
||||||
|
rewind_io,
|
||||||
|
service,
|
||||||
|
&self.h2_builder,
|
||||||
|
self.exec.clone(),
|
||||||
|
);
|
||||||
|
ProtoServer::H2 { h2 }
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Connection {
|
||||||
|
conn: Some(proto),
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
fallback: if self.mode == ConnectionMode::Fallback {
|
||||||
|
Fallback::ToHttp2(self.h2_builder.clone(), self.exec.clone())
|
||||||
|
} else {
|
||||||
|
Fallback::Http1Only
|
||||||
|
},
|
||||||
|
#[cfg(not(all(feature = "http1", feature = "http2")))]
|
||||||
|
fallback: PhantomData,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl<I, B, S, E> Connection<I, S, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
/// Start a graceful shutdown process for this connection.
|
||||||
|
///
|
||||||
|
/// This `Connection` should continue to be polled until shutdown
|
||||||
|
/// can finish.
|
||||||
|
///
|
||||||
|
/// # Note
|
||||||
|
///
|
||||||
|
/// This should only be called while the `Connection` future is still
|
||||||
|
/// pending. If called after `Connection::poll` has resolved, this does
|
||||||
|
/// nothing.
|
||||||
|
pub(crate) fn graceful_shutdown(mut self: Pin<&mut Self>) {
|
||||||
|
match self.conn {
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Some(ProtoServer::H1 { ref mut h1, .. }) => {
|
||||||
|
h1.disable_keep_alive();
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
Some(ProtoServer::H2 { ref mut h2 }) => {
|
||||||
|
h2.graceful_shutdown();
|
||||||
|
}
|
||||||
|
None => {}
|
||||||
|
#[cfg(not(feature = "http1"))]
|
||||||
|
Some(ProtoServer::H1 { ref mut h1, .. }) => match h1.0 {}
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
Some(ProtoServer::H2 { ref mut h2 }) => match h2.0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Return the inner IO object, and additional information.
|
||||||
|
///
|
||||||
|
/// If the IO object has been "rewound" the io will not contain those bytes rewound.
|
||||||
|
/// This should only be called after `poll_without_shutdown` signals
|
||||||
|
/// that the connection is "done". Otherwise, it may not have finished
|
||||||
|
/// flushing all necessary HTTP bytes.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
/// This method will panic if this connection is using an h2 protocol.
|
||||||
|
pub(crate) fn into_parts(self) -> Parts<I, S> {
|
||||||
|
self.try_into_parts().unwrap_or_else(|| panic!("h2 cannot into_inner"))
|
||||||
|
}
|
||||||
|
/// Return the inner IO object, and additional information, if available.
|
||||||
|
///
|
||||||
|
/// This method will return a `None` if this connection is using an h2 protocol.
|
||||||
|
pub(crate) fn try_into_parts(self) -> Option<Parts<I, S>> {
|
||||||
|
match self.conn.unwrap() {
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
ProtoServer::H1 { h1, .. } => {
|
||||||
|
let (io, read_buf, dispatch) = h1.into_inner();
|
||||||
|
Some(Parts {
|
||||||
|
io,
|
||||||
|
read_buf,
|
||||||
|
service: dispatch.into_service(),
|
||||||
|
_inner: (),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
ProtoServer::H2 { .. } => None,
|
||||||
|
#[cfg(not(feature = "http1"))]
|
||||||
|
ProtoServer::H1 { h1, .. } => match h1.0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// Poll the connection for completion, but without calling `shutdown`
|
||||||
|
/// on the underlying IO.
|
||||||
|
///
|
||||||
|
/// This is useful to allow running a connection while doing an HTTP
|
||||||
|
/// 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(crate) fn poll_without_shutdown(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<crate::Result<()>>
|
||||||
|
where
|
||||||
|
S: Unpin,
|
||||||
|
S::Future: Unpin,
|
||||||
|
B: Unpin,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
match *self.conn.as_mut().unwrap() {
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
ProtoServer::H1 { ref mut h1, .. } => {
|
||||||
|
match ready!(h1.poll_without_shutdown(cx)) {
|
||||||
|
Ok(()) => return Poll::Ready(Ok(())),
|
||||||
|
Err(e) => {
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
match *e.kind() {
|
||||||
|
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
|
||||||
|
self.upgrade_h2();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
ProtoServer::H2 { ref mut h2 } => {
|
||||||
|
return Pin::new(h2).poll(cx).map_ok(|_| ());
|
||||||
|
}
|
||||||
|
#[cfg(not(feature = "http1"))]
|
||||||
|
ProtoServer::H1 { ref mut h1, .. } => match h1.0 {}
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
ProtoServer::H2 { ref mut h2 } => match h2.0 {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
/// 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`.
|
||||||
|
///
|
||||||
|
/// # Error
|
||||||
|
///
|
||||||
|
/// This errors if the underlying connection protocol is not HTTP/1.
|
||||||
|
pub(crate) fn without_shutdown(
|
||||||
|
self,
|
||||||
|
) -> impl Future<Output = crate::Result<Parts<I, S>>>
|
||||||
|
where
|
||||||
|
S: Unpin,
|
||||||
|
S::Future: Unpin,
|
||||||
|
B: Unpin,
|
||||||
|
{
|
||||||
|
let mut conn = Some(self);
|
||||||
|
futures_util::future::poll_fn(move |cx| {
|
||||||
|
ready!(conn.as_mut().unwrap().poll_without_shutdown(cx))?;
|
||||||
|
Poll::Ready(
|
||||||
|
conn
|
||||||
|
.take()
|
||||||
|
.unwrap()
|
||||||
|
.try_into_parts()
|
||||||
|
.ok_or_else(crate::Error::new_without_shutdown_not_h1),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
fn upgrade_h2(&mut self) {
|
||||||
|
trace!("Trying to upgrade connection to h2");
|
||||||
|
let conn = self.conn.take();
|
||||||
|
let (io, read_buf, dispatch) = match conn.unwrap() {
|
||||||
|
ProtoServer::H1 { h1, .. } => h1.into_inner(),
|
||||||
|
ProtoServer::H2 { .. } => {
|
||||||
|
panic!("h2 cannot into_inner");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
let mut rewind_io = Rewind::new(io);
|
||||||
|
rewind_io.rewind(read_buf);
|
||||||
|
let (builder, exec) = match self.fallback {
|
||||||
|
Fallback::ToHttp2(ref builder, ref exec) => (builder, exec),
|
||||||
|
Fallback::Http1Only => unreachable!("upgrade_h2 with Fallback::Http1Only"),
|
||||||
|
};
|
||||||
|
let h2 = proto::h2::Server::new(
|
||||||
|
rewind_io,
|
||||||
|
dispatch.into_service(),
|
||||||
|
builder,
|
||||||
|
exec.clone(),
|
||||||
|
);
|
||||||
|
debug_assert!(self.conn.is_none());
|
||||||
|
self.conn = Some(ProtoServer::H2 { h2 });
|
||||||
|
}
|
||||||
|
/// Enable this connection to support higher-level HTTP upgrades.
|
||||||
|
///
|
||||||
|
/// See [the `upgrade` module](crate::upgrade) for more.
|
||||||
|
pub(crate) fn with_upgrades(self) -> UpgradeableConnection<I, S, E>
|
||||||
|
where
|
||||||
|
I: Send,
|
||||||
|
{
|
||||||
|
UpgradeableConnection {
|
||||||
|
inner: self,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl<I, B, S, E> Future for Connection<I, S, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin + 'static,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<()>;
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
loop {
|
||||||
|
match ready!(Pin::new(self.conn.as_mut().unwrap()).poll(cx)) {
|
||||||
|
Ok(done) => {
|
||||||
|
match done {
|
||||||
|
proto::Dispatched::Shutdown => {}
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
proto::Dispatched::Upgrade(pending) => {
|
||||||
|
pending.manual();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
#[cfg(feature = "http1")] #[cfg(feature = "http2")]
|
||||||
|
match *e.kind() {
|
||||||
|
Kind::Parse(Parse::VersionH2) if self.fallback.to_h2() => {
|
||||||
|
self.upgrade_h2();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl<I, S> fmt::Debug for Connection<I, S>
|
||||||
|
where
|
||||||
|
S: HttpService<Body>,
|
||||||
|
{
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Connection").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl Default for ConnectionMode {
|
||||||
|
#[cfg(all(feature = "http1", feature = "http2"))]
|
||||||
|
fn default() -> ConnectionMode {
|
||||||
|
ConnectionMode::Fallback
|
||||||
|
}
|
||||||
|
#[cfg(all(feature = "http1", not(feature = "http2")))]
|
||||||
|
fn default() -> ConnectionMode {
|
||||||
|
ConnectionMode::H1Only
|
||||||
|
}
|
||||||
|
#[cfg(all(not(feature = "http1"), feature = "http2"))]
|
||||||
|
fn default() -> ConnectionMode {
|
||||||
|
ConnectionMode::H2Only
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
impl<T, B, S, E> Future for ProtoServer<T, B, S, E>
|
||||||
|
where
|
||||||
|
T: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<proto::Dispatched>;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
match self.project() {
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
ProtoServerProj::H1 { h1, .. } => h1.poll(cx),
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
ProtoServerProj::H2 { h2 } => h2.poll(cx),
|
||||||
|
#[cfg(not(feature = "http1"))]
|
||||||
|
ProtoServerProj::H1 { h1, .. } => match h1.0 {}
|
||||||
|
#[cfg(not(feature = "http2"))]
|
||||||
|
ProtoServerProj::H2 { h2 } => match h2.0 {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(any(feature = "http1", feature = "http2"))]
|
||||||
|
mod upgrades {
|
||||||
|
use super::*;
|
||||||
|
#[must_use = "futures do nothing unless polled"]
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct UpgradeableConnection<T, S, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body>,
|
||||||
|
{
|
||||||
|
pub(super) inner: Connection<T, S, E>,
|
||||||
|
}
|
||||||
|
impl<I, B, S, E> UpgradeableConnection<I, S, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
/// Start a graceful shutdown process for this connection.
|
||||||
|
///
|
||||||
|
/// This `Connection` should continue to be polled until shutdown
|
||||||
|
/// can finish.
|
||||||
|
pub(crate) fn graceful_shutdown(mut self: Pin<&mut Self>) {
|
||||||
|
Pin::new(&mut self.inner).graceful_shutdown()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I, B, S, E> Future for UpgradeableConnection<I, S, E>
|
||||||
|
where
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<()>;
|
||||||
|
fn poll(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Self::Output> {
|
||||||
|
loop {
|
||||||
|
match ready!(Pin::new(self.inner.conn.as_mut().unwrap()).poll(cx)) {
|
||||||
|
Ok(proto::Dispatched::Shutdown) => return Poll::Ready(Ok(())),
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
Ok(proto::Dispatched::Upgrade(pending)) => {
|
||||||
|
match self.inner.conn.take() {
|
||||||
|
Some(ProtoServer::H1 { h1, .. }) => {
|
||||||
|
let (io, buf, _) = h1.into_inner();
|
||||||
|
pending.fulfill(Upgraded::new(io, buf));
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
drop(pending);
|
||||||
|
unreachable!("Upgrade expects h1")
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
#[cfg(feature = "http1")] #[cfg(feature = "http2")]
|
||||||
|
match *e.kind() {
|
||||||
|
Kind::Parse(
|
||||||
|
Parse::VersionH2,
|
||||||
|
) if self.inner.fallback.to_h2() => {
|
||||||
|
self.inner.upgrade_h2();
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
172
hyper/src/server/mod.rs
Normal file
172
hyper/src/server/mod.rs
Normal file
|
|
@ -0,0 +1,172 @@
|
||||||
|
//! HTTP Server
|
||||||
|
//!
|
||||||
|
//! A `Server` is created to listen on a port, parse HTTP requests, and hand
|
||||||
|
//! them off to a `Service`.
|
||||||
|
//!
|
||||||
|
//! There are two levels of APIs provide for constructing HTTP servers:
|
||||||
|
//!
|
||||||
|
//! - The higher-level [`Server`](Server) type.
|
||||||
|
//! - The lower-level [`conn`](conn) module.
|
||||||
|
//!
|
||||||
|
//! # Server
|
||||||
|
//!
|
||||||
|
//! The [`Server`](Server) is main way to start listening for HTTP requests.
|
||||||
|
//! It wraps a listener with a [`MakeService`](crate::service), and then should
|
||||||
|
//! be executed to start serving requests.
|
||||||
|
//!
|
||||||
|
//! [`Server`](Server) accepts connections in both HTTP1 and HTTP2 by default.
|
||||||
|
//!
|
||||||
|
//! ## Examples
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! use std::convert::Infallible;
|
||||||
|
//! use std::net::SocketAddr;
|
||||||
|
//! use hyper::{Body, Request, Response, Server};
|
||||||
|
//! use hyper::service::{make_service_fn, service_fn};
|
||||||
|
//!
|
||||||
|
//! async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
//! Ok(Response::new(Body::from("Hello World")))
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! # #[cfg(feature = "runtime")]
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! // Construct our SocketAddr to listen on...
|
||||||
|
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
//!
|
||||||
|
//! // And a MakeService to handle each connection...
|
||||||
|
//! let make_service = make_service_fn(|_conn| async {
|
||||||
|
//! Ok::<_, Infallible>(service_fn(handle))
|
||||||
|
//! });
|
||||||
|
//!
|
||||||
|
//! // Then bind and serve...
|
||||||
|
//! let server = Server::bind(&addr).serve(make_service);
|
||||||
|
//!
|
||||||
|
//! // And run forever...
|
||||||
|
//! if let Err(e) = server.await {
|
||||||
|
//! eprintln!("server error: {}", e);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # #[cfg(not(feature = "runtime"))]
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! If you don't need the connection and your service implements `Clone` you can use
|
||||||
|
//! [`tower::make::Shared`] instead of `make_service_fn` which is a bit simpler:
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! # use std::convert::Infallible;
|
||||||
|
//! # use std::net::SocketAddr;
|
||||||
|
//! # use hyper::{Body, Request, Response, Server};
|
||||||
|
//! # use hyper::service::{make_service_fn, service_fn};
|
||||||
|
//! # use tower::make::Shared;
|
||||||
|
//! # async fn handle(_req: Request<Body>) -> Result<Response<Body>, Infallible> {
|
||||||
|
//! # Ok(Response::new(Body::from("Hello World")))
|
||||||
|
//! # }
|
||||||
|
//! # #[cfg(feature = "runtime")]
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! // Construct our SocketAddr to listen on...
|
||||||
|
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
//!
|
||||||
|
//! // Shared is a MakeService that produces services by cloning an inner service...
|
||||||
|
//! let make_service = Shared::new(service_fn(handle));
|
||||||
|
//!
|
||||||
|
//! // Then bind and serve...
|
||||||
|
//! let server = Server::bind(&addr).serve(make_service);
|
||||||
|
//!
|
||||||
|
//! // And run forever...
|
||||||
|
//! if let Err(e) = server.await {
|
||||||
|
//! eprintln!("server error: {}", e);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # #[cfg(not(feature = "runtime"))]
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! Passing data to your request handler can be done like so:
|
||||||
|
//!
|
||||||
|
//! ```no_run
|
||||||
|
//! use std::convert::Infallible;
|
||||||
|
//! use std::net::SocketAddr;
|
||||||
|
//! use hyper::{Body, Request, Response, Server};
|
||||||
|
//! use hyper::service::{make_service_fn, service_fn};
|
||||||
|
//! # #[cfg(feature = "runtime")]
|
||||||
|
//! use hyper::server::conn::AddrStream;
|
||||||
|
//!
|
||||||
|
//! #[derive(Clone)]
|
||||||
|
//! struct AppContext {
|
||||||
|
//! // Whatever data your application needs can go here
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! async fn handle(
|
||||||
|
//! context: AppContext,
|
||||||
|
//! addr: SocketAddr,
|
||||||
|
//! req: Request<Body>
|
||||||
|
//! ) -> Result<Response<Body>, Infallible> {
|
||||||
|
//! Ok(Response::new(Body::from("Hello World")))
|
||||||
|
//! }
|
||||||
|
//!
|
||||||
|
//! # #[cfg(feature = "runtime")]
|
||||||
|
//! #[tokio::main]
|
||||||
|
//! async fn main() {
|
||||||
|
//! let context = AppContext {
|
||||||
|
//! // ...
|
||||||
|
//! };
|
||||||
|
//!
|
||||||
|
//! // A `MakeService` that produces a `Service` to handle each connection.
|
||||||
|
//! let make_service = make_service_fn(move |conn: &AddrStream| {
|
||||||
|
//! // We have to clone the context to share it with each invocation of
|
||||||
|
//! // `make_service`. If your data doesn't implement `Clone` consider using
|
||||||
|
//! // an `std::sync::Arc`.
|
||||||
|
//! let context = context.clone();
|
||||||
|
//!
|
||||||
|
//! // You can grab the address of the incoming connection like so.
|
||||||
|
//! let addr = conn.remote_addr();
|
||||||
|
//!
|
||||||
|
//! // Create a `Service` for responding to the request.
|
||||||
|
//! let service = service_fn(move |req| {
|
||||||
|
//! handle(context.clone(), addr, req)
|
||||||
|
//! });
|
||||||
|
//!
|
||||||
|
//! // Return the service to hyper.
|
||||||
|
//! async move { Ok::<_, Infallible>(service) }
|
||||||
|
//! });
|
||||||
|
//!
|
||||||
|
//! // Run the server like above...
|
||||||
|
//! let addr = SocketAddr::from(([127, 0, 0, 1], 3000));
|
||||||
|
//!
|
||||||
|
//! let server = Server::bind(&addr).serve(make_service);
|
||||||
|
//!
|
||||||
|
//! if let Err(e) = server.await {
|
||||||
|
//! eprintln!("server error: {}", e);
|
||||||
|
//! }
|
||||||
|
//! }
|
||||||
|
//! # #[cfg(not(feature = "runtime"))]
|
||||||
|
//! # fn main() {}
|
||||||
|
//! ```
|
||||||
|
//!
|
||||||
|
//! [`tower::make::Shared`]: https://docs.rs/tower/latest/tower/make/struct.Shared.html
|
||||||
|
|
||||||
|
pub mod accept;
|
||||||
|
pub mod conn;
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
mod tcp;
|
||||||
|
|
||||||
|
pub use self::server::Server;
|
||||||
|
|
||||||
|
cfg_feature! {
|
||||||
|
#![any(feature = "http1", feature = "http2")]
|
||||||
|
|
||||||
|
pub(crate) mod server;
|
||||||
|
pub use self::server::Builder;
|
||||||
|
|
||||||
|
mod shutdown;
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg_feature! {
|
||||||
|
#![not(any(feature = "http1", feature = "http2"))]
|
||||||
|
|
||||||
|
mod server_stub;
|
||||||
|
use server_stub as server;
|
||||||
|
}
|
||||||
598
hyper/src/server/server.rs
Normal file
598
hyper/src/server/server.rs
Normal file
|
|
@ -0,0 +1,598 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
use std::time::Duration;
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tracing::trace;
|
||||||
|
use super::accept::Accept;
|
||||||
|
#[cfg(all(feature = "tcp"))]
|
||||||
|
use super::tcp::AddrIncoming;
|
||||||
|
use crate::body::{Body, HttpBody};
|
||||||
|
use crate::common::exec::Exec;
|
||||||
|
use crate::common::exec::{ConnStreamExec, NewSvcExec};
|
||||||
|
use crate::common::{task, Future, Pin, Poll, Unpin};
|
||||||
|
use super::conn::{Connection, Http as Http_, UpgradeableConnection};
|
||||||
|
use super::shutdown::{Graceful, GracefulWatcher};
|
||||||
|
use crate::service::{HttpService, MakeServiceRef};
|
||||||
|
use self::new_svc::NewSvcTask;
|
||||||
|
pin_project! {
|
||||||
|
#[doc =
|
||||||
|
" A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default."]
|
||||||
|
#[doc = ""] #[doc =
|
||||||
|
" `Server` is a `Future` mapping a bound listener with a set of service"] #[doc =
|
||||||
|
" handlers. It is built using the [`Builder`](Builder), and the future"] #[doc =
|
||||||
|
" completes when the server has been shutdown. It should be run by an"] #[doc =
|
||||||
|
" `Executor`."] pub struct Server < I, S, E = Exec > { #[pin] incoming : I,
|
||||||
|
make_service : S, protocol : Http_ < E >, }
|
||||||
|
}
|
||||||
|
/// A builder for a [`Server`](Server).
|
||||||
|
#[derive(Debug)]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
pub struct Builder<I, E = Exec> {
|
||||||
|
incoming: I,
|
||||||
|
protocol: Http_<E>,
|
||||||
|
}
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
impl<I> Server<I, ()> {
|
||||||
|
/// Starts a [`Builder`](Builder) with the provided incoming stream.
|
||||||
|
pub fn builder(incoming: I) -> Builder<I> {
|
||||||
|
Builder {
|
||||||
|
incoming,
|
||||||
|
protocol: Http_::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(all(feature = "tcp", any(feature = "http1", feature = "http2"))))
|
||||||
|
)]
|
||||||
|
impl Server<AddrIncoming, ()> {
|
||||||
|
/// Binds to the provided address, and returns a [`Builder`](Builder).
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// This method will panic if binding to the address fails. For a method
|
||||||
|
/// to bind to an address and return a `Result`, see `Server::try_bind`.
|
||||||
|
pub fn bind(addr: &SocketAddr) -> Builder<AddrIncoming> {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
/// Tries to bind to the provided address, and returns a [`Builder`](Builder).
|
||||||
|
pub(crate) fn try_bind(addr: &SocketAddr) -> crate::Result<Builder<AddrIncoming>> {
|
||||||
|
AddrIncoming::new(addr).map(Server::builder)
|
||||||
|
}
|
||||||
|
/// Create a new instance from a `std::net::TcpListener` instance.
|
||||||
|
pub(crate) fn from_tcp(
|
||||||
|
listener: StdTcpListener,
|
||||||
|
) -> Result<Builder<AddrIncoming>, crate::Error> {
|
||||||
|
AddrIncoming::from_std(listener).map(Server::builder)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(all(feature = "tcp", any(feature = "http1", feature = "http2"))))
|
||||||
|
)]
|
||||||
|
impl<S, E> Server<AddrIncoming, S, E> {
|
||||||
|
/// Returns the local address that this server is bound to.
|
||||||
|
pub(crate) fn local_addr(&self) -> SocketAddr {
|
||||||
|
self.incoming.local_addr()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
impl<I, IO, IE, S, E, B> Server<I, S, E>
|
||||||
|
where
|
||||||
|
I: Accept<Conn = IO, Error = IE>,
|
||||||
|
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||||
|
{
|
||||||
|
/// Prepares a server to handle graceful shutdown when the provided future
|
||||||
|
/// completes.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # fn main() {}
|
||||||
|
/// # #[cfg(feature = "tcp")]
|
||||||
|
/// # async fn run() {
|
||||||
|
/// # use hyper::{Body, Response, Server, Error};
|
||||||
|
/// # use hyper::service::{make_service_fn, service_fn};
|
||||||
|
/// # let make_service = make_service_fn(|_| async {
|
||||||
|
/// # Ok::<_, Error>(service_fn(|_req| async {
|
||||||
|
/// # Ok::<_, Error>(Response::new(Body::from("Hello World")))
|
||||||
|
/// # }))
|
||||||
|
/// # });
|
||||||
|
/// // Make a server from the previous examples...
|
||||||
|
/// let server = Server::bind(&([127, 0, 0, 1], 3000).into())
|
||||||
|
/// .serve(make_service);
|
||||||
|
///
|
||||||
|
/// // Prepare some signal for when the server should start shutting down...
|
||||||
|
/// let (tx, rx) = tokio::sync::oneshot::channel::<()>();
|
||||||
|
/// let graceful = server
|
||||||
|
/// .with_graceful_shutdown(async {
|
||||||
|
/// rx.await.ok();
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // Await the `server` receiving the signal...
|
||||||
|
/// if let Err(e) = graceful.await {
|
||||||
|
/// eprintln!("server error: {}", e);
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// // And later, trigger the signal by calling `tx.send(())`.
|
||||||
|
/// let _ = tx.send(());
|
||||||
|
/// # }
|
||||||
|
/// ```
|
||||||
|
pub fn with_graceful_shutdown<F>(self, signal: F) -> Graceful<I, S, F, E>
|
||||||
|
where
|
||||||
|
F: Future<Output = ()>,
|
||||||
|
E: NewSvcExec<IO, S::Future, S::Service, E, GracefulWatcher>,
|
||||||
|
{
|
||||||
|
Graceful::new(self, signal)
|
||||||
|
}
|
||||||
|
fn poll_next_(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<crate::Result<Connecting<IO, S::Future, E>>>> {
|
||||||
|
let me = self.project();
|
||||||
|
match ready!(me.make_service.poll_ready_ref(cx)) {
|
||||||
|
Ok(()) => {}
|
||||||
|
Err(e) => {
|
||||||
|
trace!("make_service closed");
|
||||||
|
return Poll::Ready(Some(Err(crate::Error::new_user_make_service(e))));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Some(item) = ready!(me.incoming.poll_accept(cx)) {
|
||||||
|
let io = item.map_err(crate::Error::new_accept)?;
|
||||||
|
let new_fut = me.make_service.make_service_ref(&io);
|
||||||
|
Poll::Ready(
|
||||||
|
Some(
|
||||||
|
Ok(Connecting {
|
||||||
|
future: new_fut,
|
||||||
|
io: Some(io),
|
||||||
|
protocol: me.protocol.clone(),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
Poll::Ready(None)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(super) fn poll_watch<W>(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
watcher: &W,
|
||||||
|
) -> Poll<crate::Result<()>>
|
||||||
|
where
|
||||||
|
E: NewSvcExec<IO, S::Future, S::Service, E, W>,
|
||||||
|
W: Watcher<IO, S::Service, E>,
|
||||||
|
{
|
||||||
|
loop {
|
||||||
|
if let Some(connecting) = ready!(self.as_mut().poll_next_(cx) ?) {
|
||||||
|
let fut = NewSvcTask::new(connecting, watcher.clone());
|
||||||
|
self.as_mut().project().protocol.exec.execute_new_svc(fut);
|
||||||
|
} else {
|
||||||
|
return Poll::Ready(Ok(()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
impl<I, IO, IE, S, B, E> Future for Server<I, S, E>
|
||||||
|
where
|
||||||
|
I: Accept<Conn = IO, Error = IE>,
|
||||||
|
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||||
|
E: NewSvcExec<IO, S::Future, S::Service, E, NoopWatcher>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<()>;
|
||||||
|
fn poll(mut self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
loop {
|
||||||
|
if let Some(connecting) = ready!(self.as_mut().poll_next_(cx) ?) {
|
||||||
|
let fut = NewSvcTask::new(connecting, NoopWatcher);
|
||||||
|
self.as_mut().project().protocol.exec.execute_new_svc(fut);
|
||||||
|
} else {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I: fmt::Debug, S: fmt::Debug> fmt::Debug for Server<I, S> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
let mut st = f.debug_struct("Server");
|
||||||
|
st.field("listener", &self.incoming);
|
||||||
|
st.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))]
|
||||||
|
impl<I, E> Builder<I, E> {
|
||||||
|
/// Start a new builder, wrapping an incoming stream and low-level options.
|
||||||
|
///
|
||||||
|
/// For a more convenient constructor, see [`Server::bind`](Server::bind).
|
||||||
|
pub(crate) fn new(incoming: I, protocol: Http_<E>) -> Self {
|
||||||
|
Builder { incoming, protocol }
|
||||||
|
}
|
||||||
|
/// Sets whether to use keep-alive for HTTP/1 connections.
|
||||||
|
///
|
||||||
|
/// Default is `true`.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_keepalive(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.http1_keep_alive(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether HTTP/1 connections should support half-closures.
|
||||||
|
///
|
||||||
|
/// Clients can chose to shutdown their write-side while waiting
|
||||||
|
/// for the server to respond. Setting this to `true` will
|
||||||
|
/// prevent closing the connection immediately if `read`
|
||||||
|
/// detects an EOF in the middle of a request.
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_half_close(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.http1_half_close(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the maximum buffer size.
|
||||||
|
///
|
||||||
|
/// Default is ~ 400kb.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_max_buf_size(mut self, val: usize) -> Self {
|
||||||
|
self.protocol.max_buf_size(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
#[doc(hidden)]
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub fn http1_pipeline_flush(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.pipeline_flush(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether HTTP/1 connections should try to use vectored writes,
|
||||||
|
/// or always flatten into a single buffer.
|
||||||
|
///
|
||||||
|
/// Note that setting this to false may mean more copies of body data,
|
||||||
|
/// but may also improve performance when an IO transport doesn't
|
||||||
|
/// support vectored writes well, such as most TLS implementations.
|
||||||
|
///
|
||||||
|
/// Setting this to true will force hyper to use queued strategy
|
||||||
|
/// which may eliminate unnecessary cloning on some TLS backends
|
||||||
|
///
|
||||||
|
/// Default is `auto`. In this mode hyper will try to guess which
|
||||||
|
/// mode to use
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
pub(crate) fn http1_writev(mut self, enabled: bool) -> Self {
|
||||||
|
self.protocol.http1_writev(enabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether HTTP/1 connections will write header names as title case at
|
||||||
|
/// the socket level.
|
||||||
|
///
|
||||||
|
/// Note that this setting does not affect HTTP/2.
|
||||||
|
///
|
||||||
|
/// Default is false.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_title_case_headers(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.http1_title_case_headers(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether to support preserving original header cases.
|
||||||
|
///
|
||||||
|
/// Currently, this will record the original cases received, and store them
|
||||||
|
/// in a private extension on the `Request`. It will also look for and use
|
||||||
|
/// such an extension in any provided `Response`.
|
||||||
|
///
|
||||||
|
/// Since the relevant extension is still private, there is no way to
|
||||||
|
/// interact with the original cases. The only effect this can have now is
|
||||||
|
/// to forward the cases in a proxy-like fashion.
|
||||||
|
///
|
||||||
|
/// Note that this setting does not affect HTTP/2.
|
||||||
|
///
|
||||||
|
/// Default is false.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_preserve_header_case(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.http1_preserve_header_case(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set a timeout for reading client request headers. If a client does not
|
||||||
|
/// transmit the entire header within this time, the connection is closed.
|
||||||
|
///
|
||||||
|
/// Default is None.
|
||||||
|
#[cfg(all(feature = "http1", feature = "runtime"))]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(all(feature = "http1", feature = "runtime"))))]
|
||||||
|
pub(crate) fn http1_header_read_timeout(mut self, read_timeout: Duration) -> Self {
|
||||||
|
self.protocol.http1_header_read_timeout(read_timeout);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets whether HTTP/1 is required.
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
#[cfg(feature = "http1")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http1")))]
|
||||||
|
pub(crate) fn http1_only(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.http1_only(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets whether HTTP/2 is required.
|
||||||
|
///
|
||||||
|
/// Default is `false`.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_only(mut self, val: bool) -> Self {
|
||||||
|
self.protocol.http2_only(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the [`SETTINGS_INITIAL_WINDOW_SIZE`][spec] option for HTTP2
|
||||||
|
/// stream-level flow control.
|
||||||
|
///
|
||||||
|
/// Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// If not set, hyper will use a default.
|
||||||
|
///
|
||||||
|
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_INITIAL_WINDOW_SIZE
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_initial_stream_window_size(
|
||||||
|
mut self,
|
||||||
|
sz: impl Into<Option<u32>>,
|
||||||
|
) -> Self {
|
||||||
|
self.protocol.http2_initial_stream_window_size(sz.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the max connection-level flow control for HTTP2
|
||||||
|
///
|
||||||
|
/// Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// If not set, hyper will use a default.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_initial_connection_window_size(
|
||||||
|
mut self,
|
||||||
|
sz: impl Into<Option<u32>>,
|
||||||
|
) -> Self {
|
||||||
|
self.protocol.http2_initial_connection_window_size(sz.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets whether to use an adaptive flow control.
|
||||||
|
///
|
||||||
|
/// Enabling this will override the limits set in
|
||||||
|
/// `http2_initial_stream_window_size` and
|
||||||
|
/// `http2_initial_connection_window_size`.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_adaptive_window(mut self, enabled: bool) -> Self {
|
||||||
|
self.protocol.http2_adaptive_window(enabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the maximum frame size to use for HTTP2.
|
||||||
|
///
|
||||||
|
/// Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// If not set, hyper will use a default.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_frame_size(mut self, sz: impl Into<Option<u32>>) -> Self {
|
||||||
|
self.protocol.http2_max_frame_size(sz);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the max size of received header frames.
|
||||||
|
///
|
||||||
|
/// Default is currently ~16MB, but may change.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_header_list_size(mut self, max: u32) -> Self {
|
||||||
|
self.protocol.http2_max_header_list_size(max);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the [`SETTINGS_MAX_CONCURRENT_STREAMS`][spec] option for HTTP2
|
||||||
|
/// connections.
|
||||||
|
///
|
||||||
|
/// Default is no limit (`std::u32::MAX`). Passing `None` will do nothing.
|
||||||
|
///
|
||||||
|
/// [spec]: https://http2.github.io/http2-spec/#SETTINGS_MAX_CONCURRENT_STREAMS
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_concurrent_streams(
|
||||||
|
mut self,
|
||||||
|
max: impl Into<Option<u32>>,
|
||||||
|
) -> Self {
|
||||||
|
self.protocol.http2_max_concurrent_streams(max.into());
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the maximum write buffer size for each HTTP/2 stream.
|
||||||
|
///
|
||||||
|
/// Default is currently ~400KB, but may change.
|
||||||
|
///
|
||||||
|
/// # Panics
|
||||||
|
///
|
||||||
|
/// The value must be no larger than `u32::MAX`.
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(feature = "http2")))]
|
||||||
|
pub(crate) fn http2_max_send_buf_size(mut self, max: usize) -> Self {
|
||||||
|
self.protocol.http2_max_send_buf_size(max);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Enables the [extended CONNECT protocol].
|
||||||
|
///
|
||||||
|
/// [extended CONNECT protocol]: https://datatracker.ietf.org/doc/html/rfc8441#section-4
|
||||||
|
#[cfg(feature = "http2")]
|
||||||
|
pub(crate) fn http2_enable_connect_protocol(mut self) -> Self {
|
||||||
|
self.protocol.http2_enable_connect_protocol();
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Sets the `Executor` to deal with connection tasks.
|
||||||
|
///
|
||||||
|
/// Default is `tokio::spawn`.
|
||||||
|
pub(crate) fn executor<E2>(self, executor: E2) -> Builder<I, E2> {
|
||||||
|
Builder {
|
||||||
|
incoming: self.incoming,
|
||||||
|
protocol: self.protocol.with_executor(executor),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
///
|
||||||
|
pub fn serve<S, B>(self, _: S) -> Server<I, S>
|
||||||
|
where
|
||||||
|
I: Accept,
|
||||||
|
I::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
S: MakeServiceRef<I::Conn, Body, ResBody = B>,
|
||||||
|
{
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#[cfg(feature = "tcp")]
|
||||||
|
#[cfg_attr(
|
||||||
|
docsrs,
|
||||||
|
doc(cfg(all(feature = "tcp", any(feature = "http1", feature = "http2"))))
|
||||||
|
)]
|
||||||
|
impl<E> Builder<AddrIncoming, E> {
|
||||||
|
/// Set the duration to remain idle before sending TCP keepalive probes.
|
||||||
|
///
|
||||||
|
/// If `None` is specified, keepalive is disabled.
|
||||||
|
pub(crate) fn tcp_keepalive(mut self, keepalive: Option<Duration>) -> Self {
|
||||||
|
self.incoming.set_keepalive(keepalive);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the duration between two successive TCP keepalive retransmissions,
|
||||||
|
/// if acknowledgement to the previous keepalive transmission is not received.
|
||||||
|
pub(crate) fn tcp_keepalive_interval(mut self, interval: Option<Duration>) -> Self {
|
||||||
|
self.incoming.set_keepalive_interval(interval);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the number of retransmissions to be carried out before declaring that remote end is not available.
|
||||||
|
pub(crate) fn tcp_keepalive_retries(mut self, retries: Option<u32>) -> Self {
|
||||||
|
self.incoming.set_keepalive_retries(retries);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set the value of `TCP_NODELAY` option for accepted connections.
|
||||||
|
pub(crate) fn tcp_nodelay(mut self, enabled: bool) -> Self {
|
||||||
|
self.incoming.set_nodelay(enabled);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
/// Set whether to sleep on accept errors.
|
||||||
|
///
|
||||||
|
/// A possible scenario is that the process has hit the max open files
|
||||||
|
/// allowed, and so trying to accept a new connection will fail with
|
||||||
|
/// EMFILE. In some cases, it's preferable to just wait for some time, if
|
||||||
|
/// the application will likely close some files (or connections), and try
|
||||||
|
/// to accept the connection again. If this option is true, the error will
|
||||||
|
/// be logged at the error level, since it is still a big deal, and then
|
||||||
|
/// the listener will sleep for 1 second.
|
||||||
|
///
|
||||||
|
/// In other cases, hitting the max open files should be treat similarly
|
||||||
|
/// to being out-of-memory, and simply error (and shutdown). Setting this
|
||||||
|
/// option to false will allow that.
|
||||||
|
///
|
||||||
|
/// For more details see [`AddrIncoming::set_sleep_on_errors`]
|
||||||
|
pub(crate) fn tcp_sleep_on_accept_errors(mut self, val: bool) -> Self {
|
||||||
|
self.incoming.set_sleep_on_errors(val);
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait Watcher<I, S: HttpService<Body>, E>: Clone {
|
||||||
|
type Future: Future<Output = crate::Result<()>>;
|
||||||
|
fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future;
|
||||||
|
}
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
#[derive(Copy, Clone)]
|
||||||
|
pub(crate) struct NoopWatcher;
|
||||||
|
impl<I, S, E> Watcher<I, S, E> for NoopWatcher
|
||||||
|
where
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
S: HttpService<Body>,
|
||||||
|
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||||
|
S::ResBody: 'static,
|
||||||
|
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Future = UpgradeableConnection<I, S, E>;
|
||||||
|
fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future {
|
||||||
|
conn
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub(crate) mod new_svc {
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
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;
|
||||||
|
pin_project! {
|
||||||
|
#[allow(missing_debug_implementations)] pub struct NewSvcTask < I, N, S :
|
||||||
|
HttpService < Body >, E, W : Watcher < I, S, E >> { #[pin] state : State < I, N,
|
||||||
|
S, E, W >, }
|
||||||
|
}
|
||||||
|
pin_project! {
|
||||||
|
#[project = StateProj] pub (super) enum State < I, N, S : HttpService < Body >,
|
||||||
|
E, W : Watcher < I, S, E >> { Connecting { #[pin] connecting : Connecting < I, N,
|
||||||
|
E >, watcher : W, }, Connected { #[pin] future : W::Future, }, }
|
||||||
|
}
|
||||||
|
impl<I, N, S: HttpService<Body>, E, W: Watcher<I, S, E>> NewSvcTask<I, N, S, E, W> {
|
||||||
|
pub(super) fn new(connecting: Connecting<I, N, E>, watcher: W) -> Self {
|
||||||
|
NewSvcTask {
|
||||||
|
state: State::Connecting {
|
||||||
|
connecting,
|
||||||
|
watcher,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<I, N, S, NE, B, E, W> Future for NewSvcTask<I, N, S, E, W>
|
||||||
|
where
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
N: Future<Output = Result<S, NE>>,
|
||||||
|
NE: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
W: Watcher<I, S, E>,
|
||||||
|
{
|
||||||
|
type Output = ();
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
loop {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pin_project! {
|
||||||
|
#[doc = " A future building a new `Service` to a `Connection`."] #[doc = ""] #[doc =
|
||||||
|
" Wraps the future returned from `MakeService` into one that returns"] #[doc =
|
||||||
|
" a `Connection`."] #[must_use = "futures do nothing unless polled"] #[derive(Debug)]
|
||||||
|
#[cfg_attr(docsrs, doc(cfg(any(feature = "http1", feature = "http2"))))] pub struct
|
||||||
|
Connecting < I, F, E = Exec > { #[pin] future : F, io : Option < I >, protocol :
|
||||||
|
Http_ < E >, }
|
||||||
|
}
|
||||||
|
impl<I, F, S, FE, E, B> Future for Connecting<I, F, E>
|
||||||
|
where
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
F: Future<Output = Result<S, FE>>,
|
||||||
|
S: HttpService<Body, ResBody = B>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, B>,
|
||||||
|
{
|
||||||
|
type Output = Result<Connection<I, S, E>, FE>;
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut me = self.project();
|
||||||
|
let service = ready!(me.future.poll(cx))?;
|
||||||
|
let io = Option::take(&mut me.io).expect("polled after complete");
|
||||||
|
Poll::Ready(Ok(me.protocol.serve_connection(io, service)))
|
||||||
|
}
|
||||||
|
}
|
||||||
13
hyper/src/server/server_stub.rs
Normal file
13
hyper/src/server/server_stub.rs
Normal file
|
|
@ -0,0 +1,13 @@
|
||||||
|
use std::fmt;
|
||||||
|
use crate::common::exec::Exec;
|
||||||
|
/// A listening HTTP server that accepts connections in both HTTP1 and HTTP2 by default.
|
||||||
|
///
|
||||||
|
/// Needs at least one of the `http1` and `http2` features to be activated to actually be useful.
|
||||||
|
pub(crate) struct Server<I, S, E = Exec> {
|
||||||
|
_marker: std::marker::PhantomData<(I, S, E)>,
|
||||||
|
}
|
||||||
|
impl<I: fmt::Debug, S: fmt::Debug> fmt::Debug for Server<I, S> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("Server").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
128
hyper/src/server/shutdown.rs
Normal file
128
hyper/src/server/shutdown.rs
Normal file
|
|
@ -0,0 +1,128 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
|
use pin_project_lite::pin_project;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
|
use super::accept::Accept;
|
||||||
|
use super::conn::UpgradeableConnection;
|
||||||
|
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! {
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub struct Graceful<I, S, F, E> {
|
||||||
|
#[pin]
|
||||||
|
state: State<I, S, F, E>,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pin_project! {
|
||||||
|
#[project = StateProj]
|
||||||
|
pub(super) enum State<I, S, F, E> {
|
||||||
|
Running {
|
||||||
|
drain: Option<(Signal, Watch)>,
|
||||||
|
#[pin]
|
||||||
|
server: Server<I, S, E>,
|
||||||
|
#[pin]
|
||||||
|
signal: F,
|
||||||
|
},
|
||||||
|
Draining { draining: Draining },
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, S, F, E> Graceful<I, S, F, E> {
|
||||||
|
pub(super) fn new(server: Server<I, S, E>, signal: F) -> Self {
|
||||||
|
let drain = Some(drain::channel());
|
||||||
|
Graceful {
|
||||||
|
state: State::Running {
|
||||||
|
drain,
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<I, IO, IE, S, B, F, E> Future for Graceful<I, S, F, E>
|
||||||
|
where
|
||||||
|
I: Accept<Conn = IO, Error = IE>,
|
||||||
|
IE: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
IO: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
S: MakeServiceRef<IO, Body, ResBody = B>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
B: HttpBody + 'static,
|
||||||
|
B::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
F: Future<Output = ()>,
|
||||||
|
E: ConnStreamExec<<S::Service as HttpService<Body>>::Future, B>,
|
||||||
|
E: NewSvcExec<IO, S::Future, S::Service, E, GracefulWatcher>,
|
||||||
|
{
|
||||||
|
type Output = crate::Result<()>;
|
||||||
|
|
||||||
|
fn poll(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<Self::Output> {
|
||||||
|
let mut me = self.project();
|
||||||
|
loop {
|
||||||
|
let next = {
|
||||||
|
match me.state.as_mut().project() {
|
||||||
|
StateProj::Running {
|
||||||
|
drain,
|
||||||
|
server,
|
||||||
|
signal,
|
||||||
|
} => match signal.poll(cx) {
|
||||||
|
Poll::Ready(()) => {
|
||||||
|
debug!("signal received, starting graceful shutdown");
|
||||||
|
let sig = drain.take().expect("drain channel").0;
|
||||||
|
State::Draining {
|
||||||
|
draining: sig.drain(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
let watch = drain.as_ref().expect("drain channel").1.clone();
|
||||||
|
return server.poll_watch(cx, &GracefulWatcher(watch));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
StateProj::Draining { ref mut draining } => {
|
||||||
|
return Pin::new(draining).poll(cx).map(Ok);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
me.state.set(next);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct GracefulWatcher(Watch);
|
||||||
|
|
||||||
|
impl<I, S, E> Watcher<I, S, E> for GracefulWatcher
|
||||||
|
where
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin + Send + 'static,
|
||||||
|
S: HttpService<Body>,
|
||||||
|
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||||
|
S::ResBody: 'static,
|
||||||
|
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Future =
|
||||||
|
Watching<UpgradeableConnection<I, S, E>, fn(Pin<&mut UpgradeableConnection<I, S, E>>)>;
|
||||||
|
|
||||||
|
fn watch(&self, conn: UpgradeableConnection<I, S, E>) -> Self::Future {
|
||||||
|
self.0.clone().watch(conn, on_drain)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn on_drain<I, S, E>(conn: Pin<&mut UpgradeableConnection<I, S, E>>)
|
||||||
|
where
|
||||||
|
S: HttpService<Body>,
|
||||||
|
S::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
I: AsyncRead + AsyncWrite + Unpin,
|
||||||
|
S::ResBody: HttpBody + 'static,
|
||||||
|
<S::ResBody as HttpBody>::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
E: ConnStreamExec<S::Future, S::ResBody>,
|
||||||
|
{
|
||||||
|
conn.graceful_shutdown()
|
||||||
|
}
|
||||||
484
hyper/src/server/tcp.rs
Normal file
484
hyper/src/server/tcp.rs
Normal file
|
|
@ -0,0 +1,484 @@
|
||||||
|
use std::fmt;
|
||||||
|
use std::io;
|
||||||
|
use std::net::{SocketAddr, TcpListener as StdTcpListener};
|
||||||
|
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;
|
||||||
|
|
||||||
|
#[derive(Default, Debug, Clone, Copy)]
|
||||||
|
struct TcpKeepaliveConfig {
|
||||||
|
time: Option<Duration>,
|
||||||
|
interval: Option<Duration>,
|
||||||
|
retries: Option<u32>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl TcpKeepaliveConfig {
|
||||||
|
/// Converts into a `socket2::TcpKeealive` if there is any keep alive configuration.
|
||||||
|
fn into_socket2(self) -> Option<TcpKeepalive> {
|
||||||
|
let mut dirty = false;
|
||||||
|
let mut ka = TcpKeepalive::new();
|
||||||
|
if let Some(time) = self.time {
|
||||||
|
ka = ka.with_time(time);
|
||||||
|
dirty = true
|
||||||
|
}
|
||||||
|
if let Some(interval) = self.interval {
|
||||||
|
ka = Self::ka_with_interval(ka, interval, &mut dirty)
|
||||||
|
};
|
||||||
|
if let Some(retries) = self.retries {
|
||||||
|
ka = Self::ka_with_retries(ka, retries, &mut dirty)
|
||||||
|
};
|
||||||
|
if dirty {
|
||||||
|
Some(ka)
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "illumos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_vendor = "apple",
|
||||||
|
windows,
|
||||||
|
))]
|
||||||
|
fn ka_with_interval(ka: TcpKeepalive, interval: Duration, dirty: &mut bool) -> TcpKeepalive {
|
||||||
|
*dirty = true;
|
||||||
|
ka.with_interval(interval)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "illumos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_vendor = "apple",
|
||||||
|
windows,
|
||||||
|
)))]
|
||||||
|
fn ka_with_interval(ka: TcpKeepalive, _: Duration, _: &mut bool) -> TcpKeepalive {
|
||||||
|
ka // no-op as keepalive interval is not supported on this platform
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "illumos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_vendor = "apple",
|
||||||
|
))]
|
||||||
|
fn ka_with_retries(ka: TcpKeepalive, retries: u32, dirty: &mut bool) -> TcpKeepalive {
|
||||||
|
*dirty = true;
|
||||||
|
ka.with_retries(retries)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(not(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "illumos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "netbsd",
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A stream of connections from binding to an address.
|
||||||
|
#[must_use = "streams do nothing unless polled"]
|
||||||
|
pub struct AddrIncoming {
|
||||||
|
addr: SocketAddr,
|
||||||
|
listener: TcpListener,
|
||||||
|
sleep_on_errors: bool,
|
||||||
|
tcp_keepalive_config: TcpKeepaliveConfig,
|
||||||
|
tcp_nodelay: bool,
|
||||||
|
timeout: Option<Pin<Box<Sleep>>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddrIncoming {
|
||||||
|
pub(super) fn new(addr: &SocketAddr) -> crate::Result<Self> {
|
||||||
|
let std_listener = StdTcpListener::bind(addr).map_err(crate::Error::new_listen)?;
|
||||||
|
|
||||||
|
AddrIncoming::from_std(std_listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_std(std_listener: StdTcpListener) -> crate::Result<Self> {
|
||||||
|
// TcpListener::from_std doesn't set O_NONBLOCK
|
||||||
|
std_listener
|
||||||
|
.set_nonblocking(true)
|
||||||
|
.map_err(crate::Error::new_listen)?;
|
||||||
|
let listener = TcpListener::from_std(std_listener).map_err(crate::Error::new_listen)?;
|
||||||
|
AddrIncoming::from_listener(listener)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `AddrIncoming` binding to provided socket address.
|
||||||
|
pub fn bind(addr: &SocketAddr) -> crate::Result<Self> {
|
||||||
|
AddrIncoming::new(addr)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Creates a new `AddrIncoming` from an existing `tokio::net::TcpListener`.
|
||||||
|
pub fn from_listener(listener: TcpListener) -> crate::Result<Self> {
|
||||||
|
let addr = listener.local_addr().map_err(crate::Error::new_listen)?;
|
||||||
|
Ok(AddrIncoming {
|
||||||
|
listener,
|
||||||
|
addr,
|
||||||
|
sleep_on_errors: true,
|
||||||
|
tcp_keepalive_config: TcpKeepaliveConfig::default(),
|
||||||
|
tcp_nodelay: false,
|
||||||
|
timeout: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the local address bound to this listener.
|
||||||
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
|
self.addr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the duration to remain idle before sending TCP keepalive probes.
|
||||||
|
///
|
||||||
|
/// If `None` is specified, keepalive is disabled.
|
||||||
|
pub fn set_keepalive(&mut self, time: Option<Duration>) -> &mut Self {
|
||||||
|
self.tcp_keepalive_config.time = time;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the duration between two successive TCP keepalive retransmissions,
|
||||||
|
/// if acknowledgement to the previous keepalive transmission is not received.
|
||||||
|
pub fn set_keepalive_interval(&mut self, interval: Option<Duration>) -> &mut Self {
|
||||||
|
self.tcp_keepalive_config.interval = interval;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the number of retransmissions to be carried out before declaring that remote end is not available.
|
||||||
|
pub fn set_keepalive_retries(&mut self, retries: Option<u32>) -> &mut Self {
|
||||||
|
self.tcp_keepalive_config.retries = retries;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set the value of `TCP_NODELAY` option for accepted connections.
|
||||||
|
pub fn set_nodelay(&mut self, enabled: bool) -> &mut Self {
|
||||||
|
self.tcp_nodelay = enabled;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Set whether to sleep on accept errors.
|
||||||
|
///
|
||||||
|
/// A possible scenario is that the process has hit the max open files
|
||||||
|
/// allowed, and so trying to accept a new connection will fail with
|
||||||
|
/// `EMFILE`. In some cases, it's preferable to just wait for some time, if
|
||||||
|
/// the application will likely close some files (or connections), and try
|
||||||
|
/// to accept the connection again. If this option is `true`, the error
|
||||||
|
/// will be logged at the `error` level, since it is still a big deal,
|
||||||
|
/// and then the listener will sleep for 1 second.
|
||||||
|
///
|
||||||
|
/// In other cases, hitting the max open files should be treat similarly
|
||||||
|
/// to being out-of-memory, and simply error (and shutdown). Setting
|
||||||
|
/// this option to `false` will allow that.
|
||||||
|
///
|
||||||
|
/// Default is `true`.
|
||||||
|
pub fn set_sleep_on_errors(&mut self, val: bool) {
|
||||||
|
self.sleep_on_errors = val;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn poll_next_(&mut self, cx: &mut task::Context<'_>) -> Poll<io::Result<AddrStream>> {
|
||||||
|
// Check if a previous timeout is active that was set by IO errors.
|
||||||
|
if let Some(ref mut to) = self.timeout {
|
||||||
|
ready!(Pin::new(to).poll(cx));
|
||||||
|
}
|
||||||
|
self.timeout = None;
|
||||||
|
|
||||||
|
loop {
|
||||||
|
match ready!(self.listener.poll_accept(cx)) {
|
||||||
|
Ok((socket, remote_addr)) => {
|
||||||
|
if let Some(tcp_keepalive) = &self.tcp_keepalive_config.into_socket2() {
|
||||||
|
let sock_ref = socket2::SockRef::from(&socket);
|
||||||
|
if let Err(e) = sock_ref.set_tcp_keepalive(tcp_keepalive) {
|
||||||
|
trace!("error trying to set TCP keepalive: {}", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if let Err(e) = socket.set_nodelay(self.tcp_nodelay) {
|
||||||
|
trace!("error trying to set TCP nodelay: {}", e);
|
||||||
|
}
|
||||||
|
let local_addr = socket.local_addr()?;
|
||||||
|
return Poll::Ready(Ok(AddrStream::new(socket, remote_addr, local_addr)));
|
||||||
|
}
|
||||||
|
Err(e) => {
|
||||||
|
// Connection errors can be ignored directly, continue by
|
||||||
|
// accepting the next request.
|
||||||
|
if is_connection_error(&e) {
|
||||||
|
debug!("accepted connection already errored: {}", e);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if self.sleep_on_errors {
|
||||||
|
error!("accept error: {}", e);
|
||||||
|
|
||||||
|
// Sleep 1s.
|
||||||
|
let mut timeout = Box::pin(tokio::time::sleep(Duration::from_secs(1)));
|
||||||
|
|
||||||
|
match timeout.as_mut().poll(cx) {
|
||||||
|
Poll::Ready(()) => {
|
||||||
|
// Wow, it's been a second already? Ok then...
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Poll::Pending => {
|
||||||
|
self.timeout = Some(timeout);
|
||||||
|
return Poll::Pending;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
return Poll::Ready(Err(e));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Accept for AddrIncoming {
|
||||||
|
type Conn = AddrStream;
|
||||||
|
type Error = io::Error;
|
||||||
|
|
||||||
|
fn poll_accept(
|
||||||
|
mut self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Option<Result<Self::Conn, Self::Error>>> {
|
||||||
|
let result = ready!(self.poll_next_(cx));
|
||||||
|
Poll::Ready(Some(result))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This function defines errors that are per-connection. Which basically
|
||||||
|
/// means that if we get this error from `accept()` system call it means
|
||||||
|
/// next connection might be ready to be accepted.
|
||||||
|
///
|
||||||
|
/// All other errors will incur a timeout before next `accept()` is performed.
|
||||||
|
/// The timeout is useful to handle resource exhaustion errors like ENFILE
|
||||||
|
/// and EMFILE. Otherwise, could enter into tight loop.
|
||||||
|
fn is_connection_error(e: &io::Error) -> bool {
|
||||||
|
matches!(
|
||||||
|
e.kind(),
|
||||||
|
io::ErrorKind::ConnectionRefused
|
||||||
|
| io::ErrorKind::ConnectionAborted
|
||||||
|
| io::ErrorKind::ConnectionReset
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Debug for AddrIncoming {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("AddrIncoming")
|
||||||
|
.field("addr", &self.addr)
|
||||||
|
.field("sleep_on_errors", &self.sleep_on_errors)
|
||||||
|
.field("tcp_keepalive_config", &self.tcp_keepalive_config)
|
||||||
|
.field("tcp_nodelay", &self.tcp_nodelay)
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
mod addr_stream {
|
||||||
|
use std::io;
|
||||||
|
use std::net::SocketAddr;
|
||||||
|
#[cfg(unix)]
|
||||||
|
use std::os::unix::io::{AsRawFd, RawFd};
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite, ReadBuf};
|
||||||
|
use tokio::net::TcpStream;
|
||||||
|
|
||||||
|
use crate::common::{task, Pin, Poll};
|
||||||
|
|
||||||
|
pin_project_lite::pin_project! {
|
||||||
|
/// A transport returned yieled by `AddrIncoming`.
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct AddrStream {
|
||||||
|
#[pin]
|
||||||
|
inner: TcpStream,
|
||||||
|
pub(super) remote_addr: SocketAddr,
|
||||||
|
pub(super) local_addr: SocketAddr
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AddrStream {
|
||||||
|
pub(super) fn new(
|
||||||
|
tcp: TcpStream,
|
||||||
|
remote_addr: SocketAddr,
|
||||||
|
local_addr: SocketAddr,
|
||||||
|
) -> AddrStream {
|
||||||
|
AddrStream {
|
||||||
|
inner: tcp,
|
||||||
|
remote_addr,
|
||||||
|
local_addr,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the remote (peer) address of this connection.
|
||||||
|
#[inline]
|
||||||
|
pub fn remote_addr(&self) -> SocketAddr {
|
||||||
|
self.remote_addr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the local address of this connection.
|
||||||
|
#[inline]
|
||||||
|
pub fn local_addr(&self) -> SocketAddr {
|
||||||
|
self.local_addr
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Consumes the AddrStream and returns the underlying IO object
|
||||||
|
#[inline]
|
||||||
|
pub fn into_inner(self) -> TcpStream {
|
||||||
|
self.inner
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Attempt to receive data on the socket, without removing that data
|
||||||
|
/// from the queue, registering the current task for wakeup if data is
|
||||||
|
/// not yet available.
|
||||||
|
pub fn poll_peek(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &mut tokio::io::ReadBuf<'_>,
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
self.inner.poll_peek(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncRead for AddrStream {
|
||||||
|
#[inline]
|
||||||
|
fn poll_read(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &mut ReadBuf<'_>,
|
||||||
|
) -> Poll<io::Result<()>> {
|
||||||
|
self.project().inner.poll_read(cx, buf)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl AsyncWrite for AddrStream {
|
||||||
|
#[inline]
|
||||||
|
fn poll_write(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
buf: &[u8],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
self.project().inner.poll_write(cx, buf)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_write_vectored(
|
||||||
|
self: Pin<&mut Self>,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
bufs: &[io::IoSlice<'_>],
|
||||||
|
) -> Poll<io::Result<usize>> {
|
||||||
|
self.project().inner.poll_write_vectored(cx, bufs)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_flush(self: Pin<&mut Self>, _cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
// TCP flush is a noop
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn poll_shutdown(self: Pin<&mut Self>, cx: &mut task::Context<'_>) -> Poll<io::Result<()>> {
|
||||||
|
self.project().inner.poll_shutdown(cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn is_write_vectored(&self) -> bool {
|
||||||
|
// Note that since `self.inner` is a `TcpStream`, this could
|
||||||
|
// *probably* be hard-coded to return `true`...but it seems more
|
||||||
|
// correct to ask it anyway (maybe we're on some platform without
|
||||||
|
// scatter-gather IO?)
|
||||||
|
self.inner.is_write_vectored()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(unix)]
|
||||||
|
impl AsRawFd for AddrStream {
|
||||||
|
fn as_raw_fd(&self) -> RawFd {
|
||||||
|
self.inner.as_raw_fd()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::time::Duration;
|
||||||
|
use crate::server::tcp::TcpKeepaliveConfig;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn no_tcp_keepalive_config() {
|
||||||
|
assert!(TcpKeepaliveConfig::default().into_socket2().is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tcp_keepalive_time_config() {
|
||||||
|
let mut kac = TcpKeepaliveConfig::default();
|
||||||
|
kac.time = Some(Duration::from_secs(60));
|
||||||
|
if let Some(tcp_keepalive) = kac.into_socket2() {
|
||||||
|
assert!(format!("{tcp_keepalive:?}").contains("time: Some(60s)"));
|
||||||
|
} else {
|
||||||
|
panic!("test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "illumos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_vendor = "apple",
|
||||||
|
windows,
|
||||||
|
))]
|
||||||
|
#[test]
|
||||||
|
fn tcp_keepalive_interval_config() {
|
||||||
|
let mut kac = TcpKeepaliveConfig::default();
|
||||||
|
kac.interval = Some(Duration::from_secs(1));
|
||||||
|
if let Some(tcp_keepalive) = kac.into_socket2() {
|
||||||
|
assert!(format!("{tcp_keepalive:?}").contains("interval: Some(1s)"));
|
||||||
|
} else {
|
||||||
|
panic!("test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(any(
|
||||||
|
target_os = "android",
|
||||||
|
target_os = "dragonfly",
|
||||||
|
target_os = "freebsd",
|
||||||
|
target_os = "fuchsia",
|
||||||
|
target_os = "illumos",
|
||||||
|
target_os = "linux",
|
||||||
|
target_os = "netbsd",
|
||||||
|
target_vendor = "apple",
|
||||||
|
))]
|
||||||
|
#[test]
|
||||||
|
fn tcp_keepalive_retries_config() {
|
||||||
|
let mut kac = TcpKeepaliveConfig::default();
|
||||||
|
kac.retries = Some(3);
|
||||||
|
if let Some(tcp_keepalive) = kac.into_socket2() {
|
||||||
|
assert!(format!("{tcp_keepalive:?}").contains("retries: Some(3)"));
|
||||||
|
} else {
|
||||||
|
panic!("test failed");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
58
hyper/src/service/http.rs
Normal file
58
hyper/src/service/http.rs
Normal file
|
|
@ -0,0 +1,58 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
|
||||||
|
use crate::body::HttpBody;
|
||||||
|
use crate::common::{task, Future, Poll};
|
||||||
|
use crate::{Request, Response};
|
||||||
|
|
||||||
|
/// An asynchronous function from `Request` to `Response`.
|
||||||
|
pub trait HttpService<ReqBody>: sealed::Sealed<ReqBody> {
|
||||||
|
/// The `HttpBody` body of the `http::Response`.
|
||||||
|
type ResBody: HttpBody;
|
||||||
|
|
||||||
|
/// The error type that can occur within this `Service`.
|
||||||
|
///
|
||||||
|
/// Note: Returning an `Error` to a hyper server will cause the connection
|
||||||
|
/// to be abruptly aborted. In most cases, it is better to return a `Response`
|
||||||
|
/// with a 4xx or 5xx status code.
|
||||||
|
type Error: Into<Box<dyn StdError + Send + Sync>>;
|
||||||
|
|
||||||
|
/// The `Future` returned by this `Service`.
|
||||||
|
type Future: Future<Output = Result<Response<Self::ResBody>, Self::Error>>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>>;
|
||||||
|
|
||||||
|
#[doc(hidden)]
|
||||||
|
fn call(&mut self, req: Request<ReqBody>) -> Self::Future;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, B1, B2> HttpService<B1> for T
|
||||||
|
where
|
||||||
|
T: tower_service::Service<Request<B1>, Response = Response<B2>>,
|
||||||
|
B2: HttpBody,
|
||||||
|
T::Error: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type ResBody = B2;
|
||||||
|
|
||||||
|
type Error = T::Error;
|
||||||
|
type Future = T::Future;
|
||||||
|
|
||||||
|
fn poll_ready(&mut self, cx: &mut task::Context<'_>) -> Poll<Result<(), Self::Error>> {
|
||||||
|
tower_service::Service::poll_ready(self, cx)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn call(&mut self, req: Request<B1>) -> Self::Future {
|
||||||
|
tower_service::Service::call(self, req)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T, B1, B2> sealed::Sealed<B1> for T
|
||||||
|
where
|
||||||
|
T: tower_service::Service<Request<B1>, Response = Response<B2>>,
|
||||||
|
B2: HttpBody,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed<T> {}
|
||||||
|
}
|
||||||
164
hyper/src/service/make.rs
Normal file
164
hyper/src/service/make.rs
Normal file
|
|
@ -0,0 +1,164 @@
|
||||||
|
use std::error::Error as StdError;
|
||||||
|
use std::fmt;
|
||||||
|
use tokio::io::{AsyncRead, AsyncWrite};
|
||||||
|
use super::{HttpService, Service};
|
||||||
|
use crate::body::HttpBody;
|
||||||
|
use crate::common::{task, Future, Poll};
|
||||||
|
pub(crate) trait MakeConnection<Target>: self::sealed::Sealed<(Target,)> {
|
||||||
|
type Connection: AsyncRead + AsyncWrite;
|
||||||
|
type Error;
|
||||||
|
type Future: Future<Output = Result<Self::Connection, Self::Error>>;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>>;
|
||||||
|
fn make_connection(&mut self, target: Target) -> Self::Future;
|
||||||
|
}
|
||||||
|
impl<S, Target> self::sealed::Sealed<(Target,)> for S
|
||||||
|
where
|
||||||
|
S: Service<Target>,
|
||||||
|
{}
|
||||||
|
impl<S, Target> MakeConnection<Target> for S
|
||||||
|
where
|
||||||
|
S: Service<Target>,
|
||||||
|
S::Response: AsyncRead + AsyncWrite,
|
||||||
|
{
|
||||||
|
type Connection = S::Response;
|
||||||
|
type Error = S::Error;
|
||||||
|
type Future = S::Future;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Service::poll_ready(self, cx)
|
||||||
|
}
|
||||||
|
fn make_connection(&mut self, target: Target) -> Self::Future {
|
||||||
|
Service::call(self, target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pub trait MakeServiceRef<Target, ReqBody>: self::sealed::Sealed<(Target, ReqBody)> {
|
||||||
|
type ResBody: HttpBody;
|
||||||
|
type Error: Into<Box<dyn StdError + Send + Sync>>;
|
||||||
|
type Service: HttpService<ReqBody, ResBody = Self::ResBody, Error = Self::Error>;
|
||||||
|
type MakeError: Into<Box<dyn StdError + Send + Sync>>;
|
||||||
|
type Future: Future<Output = Result<Self::Service, Self::MakeError>>;
|
||||||
|
type __DontNameMe: self::sealed::CantImpl;
|
||||||
|
fn poll_ready_ref(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::MakeError>>;
|
||||||
|
fn make_service_ref(&mut self, target: &Target) -> Self::Future;
|
||||||
|
}
|
||||||
|
impl<T, Target, E, ME, S, F, IB, OB> MakeServiceRef<Target, IB> for T
|
||||||
|
where
|
||||||
|
T: for<'a> Service<&'a Target, Error = ME, Response = S, Future = F>,
|
||||||
|
E: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
ME: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
S: HttpService<IB, ResBody = OB, Error = E>,
|
||||||
|
F: Future<Output = Result<S, ME>>,
|
||||||
|
IB: HttpBody,
|
||||||
|
OB: HttpBody,
|
||||||
|
{
|
||||||
|
type Error = E;
|
||||||
|
type Service = S;
|
||||||
|
type ResBody = OB;
|
||||||
|
type MakeError = ME;
|
||||||
|
type Future = F;
|
||||||
|
type __DontNameMe = self::sealed::CantName;
|
||||||
|
fn poll_ready_ref(
|
||||||
|
&mut self,
|
||||||
|
cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::MakeError>> {
|
||||||
|
self.poll_ready(cx)
|
||||||
|
}
|
||||||
|
fn make_service_ref(&mut self, target: &Target) -> Self::Future {
|
||||||
|
self.call(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<T, Target, S, B1, B2> self::sealed::Sealed<(Target, B1)> for T
|
||||||
|
where
|
||||||
|
T: for<'a> Service<&'a Target, Response = S>,
|
||||||
|
S: HttpService<B1, ResBody = B2>,
|
||||||
|
B1: HttpBody,
|
||||||
|
B2: HttpBody,
|
||||||
|
{}
|
||||||
|
/// Create a `MakeService` from a function.
|
||||||
|
///
|
||||||
|
/// # Example
|
||||||
|
///
|
||||||
|
/// ```
|
||||||
|
/// # #[cfg(feature = "runtime")]
|
||||||
|
/// # async fn run() {
|
||||||
|
/// use std::convert::Infallible;
|
||||||
|
/// use hyper::{Body, Request, Response, Server};
|
||||||
|
/// use hyper::server::conn::AddrStream;
|
||||||
|
/// use hyper::service::{make_service_fn, service_fn};
|
||||||
|
///
|
||||||
|
/// let addr = ([127, 0, 0, 1], 3000).into();
|
||||||
|
///
|
||||||
|
/// let make_svc = make_service_fn(|socket: &AddrStream| {
|
||||||
|
/// let remote_addr = socket.remote_addr();
|
||||||
|
/// async move {
|
||||||
|
/// Ok::<_, Infallible>(service_fn(move |_: Request<Body>| async move {
|
||||||
|
/// Ok::<_, Infallible>(
|
||||||
|
/// Response::new(Body::from(format!("Hello, {}!", remote_addr)))
|
||||||
|
/// )
|
||||||
|
/// }))
|
||||||
|
/// }
|
||||||
|
/// });
|
||||||
|
///
|
||||||
|
/// // Then bind and serve...
|
||||||
|
/// let server = Server::bind(&addr)
|
||||||
|
/// .serve(make_svc);
|
||||||
|
///
|
||||||
|
/// // Finally, spawn `server` onto an Executor...
|
||||||
|
/// if let Err(e) = server.await {
|
||||||
|
/// eprintln!("server error: {}", e);
|
||||||
|
/// }
|
||||||
|
/// # }
|
||||||
|
/// # fn main() {}
|
||||||
|
/// ```
|
||||||
|
pub fn make_service_fn<F, Target, Ret>(f: F) -> MakeServiceFn<F>
|
||||||
|
where
|
||||||
|
F: FnMut(&Target) -> Ret,
|
||||||
|
Ret: Future,
|
||||||
|
{
|
||||||
|
MakeServiceFn { f }
|
||||||
|
}
|
||||||
|
/// `MakeService` returned from [`make_service_fn`]
|
||||||
|
#[derive(Clone, Copy)]
|
||||||
|
pub struct MakeServiceFn<F> {
|
||||||
|
f: F,
|
||||||
|
}
|
||||||
|
impl<'t, F, Ret, Target, Svc, MkErr> Service<&'t Target> for MakeServiceFn<F>
|
||||||
|
where
|
||||||
|
F: FnMut(&Target) -> Ret,
|
||||||
|
Ret: Future<Output = Result<Svc, MkErr>>,
|
||||||
|
MkErr: Into<Box<dyn StdError + Send + Sync>>,
|
||||||
|
{
|
||||||
|
type Error = MkErr;
|
||||||
|
type Response = Svc;
|
||||||
|
type Future = Ret;
|
||||||
|
fn poll_ready(
|
||||||
|
&mut self,
|
||||||
|
_cx: &mut task::Context<'_>,
|
||||||
|
) -> Poll<Result<(), Self::Error>> {
|
||||||
|
Poll::Ready(Ok(()))
|
||||||
|
}
|
||||||
|
fn call(&mut self, target: &'t Target) -> Self::Future {
|
||||||
|
(self.f)(target)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
impl<F> fmt::Debug for MakeServiceFn<F> {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
|
f.debug_struct("MakeServiceFn").finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
mod sealed {
|
||||||
|
pub trait Sealed<X> {}
|
||||||
|
#[allow(unreachable_pub)]
|
||||||
|
pub trait CantImpl {}
|
||||||
|
#[allow(missing_debug_implementations)]
|
||||||
|
pub enum CantName {}
|
||||||
|
impl CantImpl for CantName {}
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue