Skip to content

Commit bcade94

Browse files
authored
Allow running on wasm32 targets using fetch() for http requests (#200)
1 parent baa023d commit bcade94

File tree

11 files changed

+1568
-49
lines changed

11 files changed

+1568
-49
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
/target
1+
target
2+
pkg
23
**/*.rs.bk
34

45
.idea

Cargo.lock

Lines changed: 0 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

elasticsearch/Cargo.toml

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,17 +36,24 @@ url = "2"
3636
serde = { version = "1", features = ["derive"] }
3737
serde_json = "1"
3838
serde_with = "3"
39-
tokio = { version = "1", default-features = false, features = ["macros", "net", "time", "rt-multi-thread"] }
39+
40+
41+
#tokio = { version = "1", default-features = false, features = ["macros", "net", "time", "rt-multi-thread"] }
4042
void = "1"
4143

44+
[target.'cfg(not(target_arch = "wasm32"))'.dependencies.tokio]
45+
version = "1.0"
46+
default-features = false
47+
features = ["macros", "net", "time", "rt-multi-thread"]
48+
4249
[dev-dependencies]
4350
chrono = { version = "0.4", features = ["serde"] }
4451
clap = { version = "4", features = ["env"]}
4552
failure = "0.1"
4653
futures = "0.3"
4754
http = "1"
4855
axum = "0.7"
49-
hyper = { version = "1", features = ["server", "http1"] }
56+
#hyper = { version = "1", features = ["server", "http1"] }
5057
os_type = "2"
5158
regex="1"
5259
#sysinfo = "0.31"

elasticsearch/src/http/transport.rs

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@
1818
*/
1919
//! HTTP transport and connection components
2020
21+
#[cfg(all(target_arch = "wasm32", any(feature = "native-tls", feature = "rustls-tls")))]
22+
compile_error!("TLS features are not compatible with the wasm target");
23+
2124
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
2225
use crate::auth::ClientCertificate;
2326
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
@@ -130,6 +133,8 @@ fn build_meta() -> String {
130133
meta.push_str(",tls=n");
131134
} else if cfg!(feature = "rustls-tls") {
132135
meta.push_str(",tls=r");
136+
} else if cfg!(target_arch = "wasm32") {
137+
meta.push_str(",tls=w");
133138
}
134139

135140
meta
@@ -138,15 +143,19 @@ fn build_meta() -> String {
138143
/// Builds a HTTP transport to make API calls to Elasticsearch
139144
pub struct TransportBuilder {
140145
client_builder: reqwest::ClientBuilder,
141-
conn_pool: Box<dyn ConnectionPool>,
146+
conn_pool: Arc<dyn ConnectionPool>,
142147
credentials: Option<Credentials>,
143148
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
144149
cert_validation: Option<CertificateValidation>,
150+
#[cfg(not(target_arch = "wasm32"))]
145151
proxy: Option<Url>,
152+
#[cfg(not(target_arch = "wasm32"))]
146153
proxy_credentials: Option<Credentials>,
154+
#[cfg(not(target_arch = "wasm32"))]
147155
disable_proxy: bool,
148156
headers: HeaderMap,
149157
meta_header: bool,
158+
#[cfg(not(target_arch = "wasm32"))]
150159
timeout: Option<Duration>,
151160
}
152161

@@ -159,15 +168,19 @@ impl TransportBuilder {
159168
{
160169
Self {
161170
client_builder: reqwest::ClientBuilder::new(),
162-
conn_pool: Box::new(conn_pool),
171+
conn_pool: Arc::new(conn_pool),
163172
credentials: None,
164173
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
165174
cert_validation: None,
175+
#[cfg(not(target_arch = "wasm32"))]
166176
proxy: None,
177+
#[cfg(not(target_arch = "wasm32"))]
167178
proxy_credentials: None,
179+
#[cfg(not(target_arch = "wasm32"))]
168180
disable_proxy: false,
169181
headers: HeaderMap::new(),
170182
meta_header: true,
183+
#[cfg(not(target_arch = "wasm32"))]
171184
timeout: None,
172185
}
173186
}
@@ -176,6 +189,7 @@ impl TransportBuilder {
176189
///
177190
/// An optional username and password will be used to set the
178191
/// `Proxy-Authorization` header using Basic Authentication.
192+
#[cfg(not(target_arch = "wasm32"))]
179193
pub fn proxy(mut self, url: Url, username: Option<&str>, password: Option<&str>) -> Self {
180194
self.proxy = Some(url);
181195
if let Some(u) = username {
@@ -189,6 +203,7 @@ impl TransportBuilder {
189203
/// Whether to disable proxies, including system proxies.
190204
///
191205
/// NOTE: System proxies are enabled by default.
206+
#[cfg(not(target_arch = "wasm32"))]
192207
pub fn disable_proxy(mut self) -> Self {
193208
self.disable_proxy = true;
194209
self
@@ -241,6 +256,7 @@ impl TransportBuilder {
241256
///
242257
/// The timeout is applied from when the request starts connecting until the response body has finished.
243258
/// Default is no timeout.
259+
#[cfg(not(target_arch = "wasm32"))]
244260
pub fn timeout(mut self, timeout: Duration) -> Self {
245261
self.timeout = Some(timeout);
246262
self
@@ -254,6 +270,7 @@ impl TransportBuilder {
254270
client_builder = client_builder.default_headers(self.headers);
255271
}
256272

273+
#[cfg(not(target_arch = "wasm32"))]
257274
if let Some(t) = self.timeout {
258275
client_builder = client_builder.timeout(t);
259276
}
@@ -300,6 +317,7 @@ impl TransportBuilder {
300317
}
301318
}
302319

320+
#[cfg(not(target_arch = "wasm32"))]
303321
if self.disable_proxy {
304322
client_builder = client_builder.no_proxy();
305323
} else if let Some(url) = self.proxy {
@@ -316,7 +334,7 @@ impl TransportBuilder {
316334
let client = client_builder.build()?;
317335
Ok(Transport {
318336
client,
319-
conn_pool: Arc::new(self.conn_pool),
337+
conn_pool: self.conn_pool,
320338
credentials: self.credentials,
321339
send_meta: self.meta_header,
322340
})
@@ -363,7 +381,7 @@ impl Connection {
363381
pub struct Transport {
364382
client: reqwest::Client,
365383
credentials: Option<Credentials>,
366-
conn_pool: Arc<Box<dyn ConnectionPool>>,
384+
conn_pool: Arc<dyn ConnectionPool>,
367385
send_meta: bool,
368386
}
369387

@@ -463,6 +481,7 @@ impl Transport {
463481
headers: HeaderMap,
464482
query_string: Option<&Q>,
465483
body: Option<B>,
484+
#[allow(unused_variables)]
466485
timeout: Option<Duration>,
467486
) -> Result<reqwest::RequestBuilder, Error>
468487
where
@@ -473,6 +492,7 @@ impl Transport {
473492
let url = connection.url.join(path.trim_start_matches('/'))?;
474493
let mut request_builder = self.client.request(reqwest_method, url);
475494

495+
#[cfg(not(target_arch = "wasm32"))]
476496
if let Some(t) = timeout {
477497
request_builder = request_builder.timeout(t);
478498
}
@@ -564,6 +584,47 @@ impl Transport {
564584
)?)
565585
}
566586

587+
async fn reseed(&self) {
588+
// Requests will execute against old connection pool during reseed
589+
let connection = self.conn_pool.next();
590+
591+
// Build node info request
592+
let node_request = self.request_builder(
593+
&connection,
594+
Method::Get,
595+
"_nodes/http?filter_path=nodes.*.http",
596+
HeaderMap::default(),
597+
None::<&()>,
598+
None::<()>,
599+
None,
600+
).unwrap();
601+
602+
let scheme = connection.url.scheme();
603+
let resp = node_request.send().await.unwrap();
604+
let json: Value = resp.json().await.unwrap();
605+
let connections: Vec<Connection> = json["nodes"]
606+
.as_object()
607+
.unwrap()
608+
.iter()
609+
.map(|(_, node)| {
610+
let address = node["http"]["publish_address"]
611+
.as_str()
612+
.or_else(|| {
613+
Some(
614+
node["http"]["bound_address"].as_array().unwrap()[0]
615+
.as_str()
616+
.unwrap(),
617+
)
618+
})
619+
.unwrap();
620+
let url = Self::parse_to_url(address, scheme).unwrap();
621+
Connection::new(url)
622+
})
623+
.collect();
624+
625+
self.conn_pool.reseed(connections);
626+
}
627+
567628
/// Creates an asynchronous request that can be awaited
568629
pub async fn send<B, Q>(
569630
&self,
@@ -578,47 +639,19 @@ impl Transport {
578639
B: Body,
579640
Q: Serialize + ?Sized,
580641
{
581-
// Requests will execute against old connection pool during reseed
582642
if self.conn_pool.reseedable() {
583-
let conn_pool = self.conn_pool.clone();
584-
let connection = conn_pool.next();
585-
586-
// Build node info request
587-
let node_request = self.request_builder(
588-
&connection,
589-
Method::Get,
590-
"_nodes/http?filter_path=nodes.*.http",
591-
headers.clone(),
592-
None::<&Q>,
593-
None::<B>,
594-
timeout,
595-
)?;
596-
597-
tokio::spawn(async move {
598-
let scheme = connection.url.scheme();
599-
let resp = node_request.send().await.unwrap();
600-
let json: Value = resp.json().await.unwrap();
601-
let connections: Vec<Connection> = json["nodes"]
602-
.as_object()
603-
.unwrap()
604-
.iter()
605-
.map(|(_, node)| {
606-
let address = node["http"]["publish_address"]
607-
.as_str()
608-
.or_else(|| {
609-
Some(
610-
node["http"]["bound_address"].as_array().unwrap()[0]
611-
.as_str()
612-
.unwrap(),
613-
)
614-
})
615-
.unwrap();
616-
let url = Self::parse_to_url(address, scheme).unwrap();
617-
Connection::new(url)
618-
})
619-
.collect();
620-
conn_pool.reseed(connections);
621-
});
643+
#[cfg(not(target_arch = "wasm32"))]
644+
{
645+
let transport = self.clone();
646+
tokio::spawn(async move { transport.reseed().await });
647+
}
648+
#[cfg(target_arch = "wasm32")]
649+
{
650+
// Reseed synchronously (i.e. do not spawn a background task) in WASM.
651+
// Running in the background is platform-dependent (web-sys / wasi), we'll
652+
// address this if synchronous reseed is an issue.
653+
self.reseed().await
654+
}
622655
}
623656

624657
let connection = self.conn_pool.next();

elasticsearch/src/lib.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -371,10 +371,12 @@ mod readme {
371371
extern crate dyn_clone;
372372

373373
pub mod auth;
374-
pub mod cert;
375374
pub mod http;
376375
pub mod params;
377376

377+
#[cfg(not(target_arch = "wasm32"))]
378+
pub mod cert;
379+
378380
// GENERATED-BEGIN:namespace-modules
379381
// Generated code - do not edit until the next GENERATED-END marker
380382

examples/README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
This directory contains standalone examples that need their own independent build configuration.
2+
3+
Other examples can also be found in [`elasticsearch/examples`](../elasticsearch/examples/).

0 commit comments

Comments
 (0)