@@ -10,6 +10,7 @@ use base64::Engine;
1010use hyper:: body:: Bytes ;
1111use hyper:: http:: { HeaderMap , HeaderValue } ;
1212use hyper_util:: client:: legacy:: Client ;
13+ #[ cfg( not( all( target_os = "wasi" , target_env = "p2" ) ) ) ]
1314use hyper_util:: client:: legacy:: connect:: HttpConnector ;
1415use hyper_util:: rt:: TokioExecutor ;
1516use jsonrpsee_core:: BoxError ;
@@ -30,56 +31,69 @@ use crate::{HttpBody, HttpRequest, HttpResponse};
3031#[ cfg( feature = "tls" ) ]
3132use 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-
6676const CONTENT_TYPE_JSON : & str = "application/json" ;
6777
6878/// Wrapper over HTTP transport and connector.
6979#[ derive( Debug ) ]
7080pub 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
7892impl < 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 }
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