Skip to content

Commit 713c055

Browse files
committed
Replaced HttpConnector with WasiConnector for wasip2
1 parent c4d883f commit 713c055

1 file changed

Lines changed: 58 additions & 43 deletions

File tree

client/http-client/src/transport.rs

Lines changed: 58 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ use base64::Engine;
1010
use hyper::body::Bytes;
1111
use hyper::http::{HeaderMap, HeaderValue};
1212
use hyper_util::client::legacy::Client;
13+
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
1314
use hyper_util::client::legacy::connect::HttpConnector;
1415
use hyper_util::rt::TokioExecutor;
1516
use jsonrpsee_core::BoxError;
@@ -30,56 +31,69 @@ use crate::{HttpBody, HttpRequest, HttpResponse};
3031
#[cfg(feature = "tls")]
3132
use crate::{CertificateStore, CustomCertStore};
3233

33-
/// DNS resolver that calls `std::net` directly, bypassing tokio's `spawn_blocking`.
34-
/// Required for wasip2 where threads are not available.
34+
/// TCP connector for wasip2 that bypasses hyper-util's `HttpConnector`.
35+
///
36+
/// `HttpConnector` creates sockets via socket2 and calls `set_nonblocking()`,
37+
/// which fails on wasip2. This connector uses `tokio::net::TcpStream::connect()`
38+
/// instead, which goes through mio's wasip2-compatible path.
39+
///
40+
/// DNS resolution uses `std::net::ToSocketAddrs` directly (not `spawn_blocking`)
41+
/// since wasip2 is single-threaded but wasi-libc provides working `getaddrinfo`.
3542
#[cfg(all(target_os = "wasi", target_env = "p2"))]
36-
mod direct_resolver {
37-
use std::future::Ready;
38-
use std::net::SocketAddr;
39-
use std::task::{Context, Poll};
40-
41-
#[derive(Clone, Copy, Debug)]
42-
pub struct DirectResolver;
43+
#[derive(Clone, Debug)]
44+
struct WasiConnector;
4345

44-
impl tower::Service<hyper_util::client::legacy::connect::dns::Name> for DirectResolver {
45-
type Response = std::vec::IntoIter<SocketAddr>;
46-
type Error = std::io::Error;
47-
type Future = Ready<Result<Self::Response, Self::Error>>;
46+
#[cfg(all(target_os = "wasi", target_env = "p2"))]
47+
impl Service<hyper::Uri> for WasiConnector {
48+
type Response = hyper_util::rt::TokioIo<tokio::net::TcpStream>;
49+
type Error = Box<dyn std::error::Error + Send + Sync>;
50+
type Future = Pin<Box<dyn Future<Output = Result<Self::Response, Self::Error>> + Send>>;
4851

49-
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
50-
Poll::Ready(Ok(()))
51-
}
52+
fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
53+
Poll::Ready(Ok(()))
54+
}
5255

53-
fn call(&mut self, name: hyper_util::client::legacy::connect::dns::Name) -> Self::Future {
54-
let result = std::net::ToSocketAddrs::to_socket_addrs(&(name.as_str(), 0u16))
55-
.map(|addrs| addrs.collect::<Vec<_>>().into_iter());
56-
std::future::ready(result)
57-
}
56+
fn call(&mut self, uri: hyper::Uri) -> Self::Future {
57+
Box::pin(async move {
58+
let host = uri.host().ok_or("missing host")?;
59+
let port = uri.port_u16().unwrap_or(match uri.scheme_str() {
60+
Some("https") => 443,
61+
_ => 80,
62+
});
63+
64+
// Resolve DNS synchronously via std::net (wasi-libc getaddrinfo)
65+
let addr = std::net::ToSocketAddrs::to_socket_addrs(&(host, port))?
66+
.next()
67+
.ok_or("DNS resolved no addresses")?;
68+
69+
// Connect via tokio (goes through mio's wasip2-compatible path)
70+
let stream = tokio::net::TcpStream::connect(addr).await?;
71+
Ok(hyper_util::rt::TokioIo::new(stream))
72+
})
5873
}
5974
}
6075

61-
#[cfg(all(target_os = "wasi", target_env = "p2"))]
62-
type Connector = HttpConnector<direct_resolver::DirectResolver>;
63-
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
64-
type Connector = HttpConnector;
65-
6676
const CONTENT_TYPE_JSON: &str = "application/json";
6777

6878
/// Wrapper over HTTP transport and connector.
6979
#[derive(Debug)]
7080
pub enum HttpBackend<B = HttpBody> {
7181
/// Hyper client with https connector.
72-
#[cfg(feature = "tls")]
73-
Https(Client<hyper_rustls::HttpsConnector<Connector>, B>),
82+
#[cfg(all(feature = "tls", not(all(target_os = "wasi", target_env = "p2"))))]
83+
Https(Client<hyper_rustls::HttpsConnector<HttpConnector>, B>),
7484
/// Hyper client with http connector.
75-
Http(Client<Connector, B>),
85+
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
86+
Http(Client<HttpConnector, B>),
87+
/// Hyper client with wasip2 connector.
88+
#[cfg(all(target_os = "wasi", target_env = "p2"))]
89+
Http(Client<WasiConnector, B>),
7690
}
7791

7892
impl<B> Clone for HttpBackend<B> {
7993
fn clone(&self) -> Self {
8094
match self {
8195
Self::Http(inner) => Self::Http(inner.clone()),
82-
#[cfg(feature = "tls")]
96+
#[cfg(all(feature = "tls", not(all(target_os = "wasi", target_env = "p2"))))]
8397
Self::Https(inner) => Self::Https(inner.clone()),
8498
}
8599
}
@@ -98,7 +112,7 @@ where
98112
fn poll_ready(&mut self, ctx: &mut Context<'_>) -> Poll<Result<(), Self::Error>> {
99113
match self {
100114
Self::Http(inner) => inner.poll_ready(ctx),
101-
#[cfg(feature = "tls")]
115+
#[cfg(all(feature = "tls", not(all(target_os = "wasi", target_env = "p2"))))]
102116
Self::Https(inner) => inner.poll_ready(ctx),
103117
}
104118
.map_err(|e| Error::Http(HttpError::Stream(e.into())))
@@ -107,7 +121,7 @@ where
107121
fn call(&mut self, req: HttpRequest<B>) -> Self::Future {
108122
let resp = match self {
109123
Self::Http(inner) => inner.call(req),
110-
#[cfg(feature = "tls")]
124+
#[cfg(all(feature = "tls", not(all(target_os = "wasi", target_env = "p2"))))]
111125
Self::Https(inner) => inner.call(req),
112126
};
113127

@@ -264,26 +278,27 @@ impl<L> HttpTransportClientBuilder<L> {
264278
let client = match url.scheme() {
265279
"http" => {
266280
#[cfg(all(target_os = "wasi", target_env = "p2"))]
267-
let mut connector = HttpConnector::new_with_resolver(direct_resolver::DirectResolver);
281+
{
282+
HttpBackend::Http(Client::builder(TokioExecutor::new()).build(WasiConnector))
283+
}
268284
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
269-
let mut connector = HttpConnector::new();
270-
connector.set_nodelay(tcp_no_delay);
271-
connector.set_keepalive(keep_alive_duration);
272-
connector.set_keepalive_interval(keep_alive_interval);
273-
connector.set_keepalive_retries(keep_alive_retries);
274-
HttpBackend::Http(Client::builder(TokioExecutor::new()).build(connector))
285+
{
286+
let mut connector = HttpConnector::new();
287+
connector.set_nodelay(tcp_no_delay);
288+
connector.set_keepalive(keep_alive_duration);
289+
connector.set_keepalive_interval(keep_alive_interval);
290+
connector.set_keepalive_retries(keep_alive_retries);
291+
HttpBackend::Http(Client::builder(TokioExecutor::new()).build(connector))
292+
}
275293
}
276-
#[cfg(feature = "tls")]
294+
#[cfg(all(feature = "tls", not(all(target_os = "wasi", target_env = "p2"))))]
277295
"https" => {
278296
// Make sure that the TLS provider is set. If not, set a default one.
279297
// Otherwise, creating `tls` configuration may panic if there are multiple
280298
// providers available due to `rustls` features (e.g. both `ring` and `aws-lc-rs`).
281299
// Function returns an error if the provider is already installed, and we're fine with it.
282300
let _ = rustls::crypto::ring::default_provider().install_default();
283301

284-
#[cfg(all(target_os = "wasi", target_env = "p2"))]
285-
let mut http_conn = HttpConnector::new_with_resolver(direct_resolver::DirectResolver);
286-
#[cfg(not(all(target_os = "wasi", target_env = "p2")))]
287302
let mut http_conn = HttpConnector::new();
288303
http_conn.set_nodelay(tcp_no_delay);
289304
http_conn.enforce_http(false);

0 commit comments

Comments
 (0)