Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,4 @@ jobs:
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@nightly
- run: cargo rustdoc -- --cfg docsrs -D rustdoc::broken-intra-doc-links
- run: cargo rustdoc --features full -- --cfg docsrs -D rustdoc::broken-intra-doc-links
11 changes: 10 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ pin-project-lite = "0.2.4"
socket2 = { version = ">=0.5.9, <0.7", optional = true, features = ["all"] }
tracing = { version = "0.1", default-features = false, features = ["std"], optional = true }
tokio = { version = "1", optional = true, default-features = false }
tower-layer = { version = "0.3", optional = true }
tower-service = { version = "0.3", optional = true }

[dev-dependencies]
Expand All @@ -42,6 +43,8 @@ futures-util = { version = "0.3.16", default-features = false, features = ["allo
http-body-util = "0.1.0"
tokio = { version = "1", features = ["macros", "test-util", "signal"] }
tokio-test = "0.4"
tower = { version = "0.5", features = ["util"] }
tower-test = "0.4"
pretty_env_logger = "0.5"

[target.'cfg(any(target_os = "linux", target_os = "macos"))'.dev-dependencies]
Expand All @@ -54,12 +57,13 @@ system-configuration = { version = "0.6.1", optional = true }
windows-registry = { version = "0.5", optional = true }

[features]
default = []
default = ["client", "client-pool"]

# Shorthand to enable everything
full = [
"client",
"client-legacy",
"client-pool",
"client-proxy",
"client-proxy-system",
"server",
Expand All @@ -74,6 +78,7 @@ full = [

client = ["hyper/client", "tokio/net", "dep:tracing", "dep:futures-channel", "dep:tower-service"]
client-legacy = ["client", "dep:socket2", "tokio/sync", "dep:libc", "dep:futures-util"]
client-pool = ["tokio/sync", "dep:futures-util", "dep:tower-layer"]
client-proxy = ["client", "dep:base64", "dep:ipnet", "dep:percent-encoding"]
client-proxy-system = ["dep:system-configuration", "dep:windows-registry"]

Expand All @@ -95,6 +100,10 @@ __internal_happy_eyeballs_tests = []

[[example]]
name = "client"
required-features = ["client-legacy", "client-pool", "http1", "tokio"]

[[example]]
name = "client_legacy"
required-features = ["client-legacy", "http1", "tokio"]

[[example]]
Expand Down
172 changes: 146 additions & 26 deletions examples/client.rs
Original file line number Diff line number Diff line change
@@ -1,37 +1,157 @@
use std::env;
use tower::ServiceExt;
use tower_service::Service;

use http_body_util::Empty;
use hyper::Request;
use hyper_util::client::legacy::{connect::HttpConnector, Client};
use hyper_util::client::pool;

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = match env::args().nth(1) {
Some(url) => url,
None => {
eprintln!("Usage: client <url>");
return Ok(());
}
};

// HTTPS requires picking a TLS implementation, so give a better
// warning if the user tries to request an 'https' URL.
let url = url.parse::<hyper::Uri>()?;
if url.scheme_str() != Some("http") {
eprintln!("This example only works with 'http' URLs.");
return Ok(());
async fn main() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
send_nego().await
}

async fn send_h1() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let tcp = hyper_util::client::legacy::connect::HttpConnector::new();

let http1 = tcp.and_then(|conn| {
Box::pin(async move {
let (mut tx, c) = hyper::client::conn::http1::handshake::<
_,
http_body_util::Empty<hyper::body::Bytes>,
>(conn)
.await?;
tokio::spawn(async move {
if let Err(e) = c.await {
eprintln!("connection error: {:?}", e);
}
});
let svc = tower::service_fn(move |req| tx.send_request(req));
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(svc)
})
});

let mut p = pool::Cache::new(http1).build();

let mut c = p.call(http::Uri::from_static("http://hyper.rs")).await?;
eprintln!("{:?}", c);

let req = http::Request::builder()
.header("host", "hyper.rs")
.body(http_body_util::Empty::new())
.unwrap();

c.ready().await?;
let resp = c.call(req).await?;
eprintln!("{:?}", resp);

Ok(())
}

async fn send_h2() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let tcp = hyper_util::client::legacy::connect::HttpConnector::new();

let http2 = tcp.and_then(|conn| {
Box::pin(async move {
let (mut tx, c) = hyper::client::conn::http2::handshake::<
_,
_,
http_body_util::Empty<hyper::body::Bytes>,
>(hyper_util::rt::TokioExecutor::new(), conn)
.await?;
println!("connected");
tokio::spawn(async move {
if let Err(e) = c.await {
eprintln!("connection error: {:?}", e);
}
});
let svc = tower::service_fn(move |req| tx.send_request(req));
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(svc)
})
});

let mut p = pool::Singleton::new(http2);

for _ in 0..5 {
let mut c = p
.call(http::Uri::from_static("http://localhost:3000"))
.await?;
eprintln!("{:?}", c);

let req = http::Request::builder()
.header("host", "hyper.rs")
.body(http_body_util::Empty::new())
.unwrap();

c.ready().await?;
let resp = c.call(req).await?;
eprintln!("{:?}", resp);
}

let client = Client::builder(hyper_util::rt::TokioExecutor::new()).build(HttpConnector::new());
Ok(())
}

let req = Request::builder()
.uri(url)
.body(Empty::<bytes::Bytes>::new())?;
async fn send_nego() -> Result<(), Box<dyn std::error::Error + Send + Sync>> {
let tcp = hyper_util::client::legacy::connect::HttpConnector::new();

let resp = client.request(req).await?;
let http1 = tower::layer::layer_fn(|tcp| {
tower::service_fn(move |dst| {
let inner = tcp.call(dst);
async move {
let conn = inner.await?;
let (mut tx, c) = hyper::client::conn::http1::handshake::<
_,
http_body_util::Empty<hyper::body::Bytes>,
>(conn)
.await?;
tokio::spawn(async move {
if let Err(e) = c.await {
eprintln!("connection error: {:?}", e);
}
});
let svc = tower::service_fn(move |req| tx.send_request(req));
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(svc)
}
})
});

eprintln!("{:?} {:?}", resp.version(), resp.status());
eprintln!("{:#?}", resp.headers());
let http2 = tower::layer::layer_fn(|tcp| {
tower::service_fn(move |dst| {
let inner = tcp.call(dst);
async move {
let conn = inner.await?;
let (mut tx, c) = hyper::client::conn::http2::handshake::<
_,
_,
http_body_util::Empty<hyper::body::Bytes>,
>(hyper_util::rt::TokioExecutor::new(), conn)
.await?;
println!("connected");
tokio::spawn(async move {
if let Err(e) = c.await {
eprintln!("connection error: {:?}", e);
}
});
let svc = tower::service_fn(move |req| tx.send_request(req));
Ok::<_, Box<dyn std::error::Error + Send + Sync>>(svc)
}
})
});

let mut svc = pool::negotiate(tcp, |_| false, http1, http2);

for _ in 0..5 {
let mut c = svc
.call(http::Uri::from_static("http://localhost:3000"))
.await?;
eprintln!("{:?}", c);

let req = http::Request::builder()
.header("host", "hyper.rs")
.body(http_body_util::Empty::new())
.unwrap();

c.ready().await?;
let resp = c.call(req).await?;
eprintln!("{:?}", resp);
}

Ok(())
}
37 changes: 37 additions & 0 deletions examples/client_legacy.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use std::env;

use http_body_util::Empty;
use hyper::Request;
use hyper_util::client::legacy::{connect::HttpConnector, Client};

#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let url = match env::args().nth(1) {
Some(url) => url,
None => {
eprintln!("Usage: client <url>");
return Ok(());
}
};

// HTTPS requires picking a TLS implementation, so give a better
// warning if the user tries to request an 'https' URL.
let url = url.parse::<hyper::Uri>()?;
if url.scheme_str() != Some("http") {
eprintln!("This example only works with 'http' URLs.");
return Ok(());
}

let client = Client::builder(hyper_util::rt::TokioExecutor::new()).build(HttpConnector::new());

let req = Request::builder()
.uri(url)
.body(Empty::<bytes::Bytes>::new())?;

let resp = client.request(req).await?;

eprintln!("{:?} {:?}", resp.version(), resp.status());
eprintln!("{:#?}", resp.headers());

Ok(())
}
3 changes: 3 additions & 0 deletions src/client/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,5 +4,8 @@
#[cfg(feature = "client-legacy")]
pub mod legacy;

#[cfg(feature = "client-pool")]
pub mod pool;

#[cfg(feature = "client-proxy")]
pub mod proxy;
Loading
Loading