Skip to content

Commit cbf3e72

Browse files
committed
Allow running the client on wasm32 targets using fetch() for http requests
1 parent baa023d commit cbf3e72

File tree

14 files changed

+418
-11
lines changed

14 files changed

+418
-11
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

elasticsearch/src/http/transport.rs

Lines changed: 36 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,9 @@
1818
*/
1919
//! HTTP transport and connection components
2020
21-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
21+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
2222
use crate::auth::ClientCertificate;
23-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
23+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
2424
use crate::cert::CertificateValidation;
2525
use crate::{
2626
auth::Credentials,
@@ -140,10 +140,13 @@ pub struct TransportBuilder {
140140
client_builder: reqwest::ClientBuilder,
141141
conn_pool: Box<dyn ConnectionPool>,
142142
credentials: Option<Credentials>,
143-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
143+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
144144
cert_validation: Option<CertificateValidation>,
145+
#[cfg(not(target_arch = "wasm32"))]
145146
proxy: Option<Url>,
147+
#[cfg(not(target_arch = "wasm32"))]
146148
proxy_credentials: Option<Credentials>,
149+
#[cfg(not(target_arch = "wasm32"))]
147150
disable_proxy: bool,
148151
headers: HeaderMap,
149152
meta_header: bool,
@@ -161,10 +164,13 @@ impl TransportBuilder {
161164
client_builder: reqwest::ClientBuilder::new(),
162165
conn_pool: Box::new(conn_pool),
163166
credentials: None,
164-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
167+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
165168
cert_validation: None,
169+
#[cfg(not(target_arch = "wasm32"))]
166170
proxy: None,
171+
#[cfg(not(target_arch = "wasm32"))]
167172
proxy_credentials: None,
173+
#[cfg(not(target_arch = "wasm32"))]
168174
disable_proxy: false,
169175
headers: HeaderMap::new(),
170176
meta_header: true,
@@ -176,6 +182,7 @@ impl TransportBuilder {
176182
///
177183
/// An optional username and password will be used to set the
178184
/// `Proxy-Authorization` header using Basic Authentication.
185+
#[cfg(not(target_arch = "wasm32"))]
179186
pub fn proxy(mut self, url: Url, username: Option<&str>, password: Option<&str>) -> Self {
180187
self.proxy = Some(url);
181188
if let Some(u) = username {
@@ -189,6 +196,7 @@ impl TransportBuilder {
189196
/// Whether to disable proxies, including system proxies.
190197
///
191198
/// NOTE: System proxies are enabled by default.
199+
#[cfg(not(target_arch = "wasm32"))]
192200
pub fn disable_proxy(mut self) -> Self {
193201
self.disable_proxy = true;
194202
self
@@ -203,7 +211,7 @@ impl TransportBuilder {
203211
/// Validation applied to the certificate provided to establish a HTTPS connection.
204212
/// By default, full validation is applied. When using a self-signed certificate,
205213
/// different validation can be applied.
206-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
214+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
207215
pub fn cert_validation(mut self, validation: CertificateValidation) -> Self {
208216
self.cert_validation = Some(validation);
209217
self
@@ -254,11 +262,12 @@ impl TransportBuilder {
254262
client_builder = client_builder.default_headers(self.headers);
255263
}
256264

265+
#[cfg(not(target_arch = "wasm32"))]
257266
if let Some(t) = self.timeout {
258267
client_builder = client_builder.timeout(t);
259268
}
260269

261-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
270+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
262271
{
263272
if let Some(Credentials::Certificate(cert)) = &self.credentials {
264273
client_builder = match cert {
@@ -280,7 +289,7 @@ impl TransportBuilder {
280289
};
281290
}
282291

283-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
292+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
284293
if let Some(v) = self.cert_validation {
285294
client_builder = match v {
286295
CertificateValidation::Default => client_builder,
@@ -300,6 +309,7 @@ impl TransportBuilder {
300309
}
301310
}
302311

312+
#[cfg(not(target_arch = "wasm32"))]
303313
if self.disable_proxy {
304314
client_builder = client_builder.no_proxy();
305315
} else if let Some(url) = self.proxy {
@@ -463,6 +473,7 @@ impl Transport {
463473
headers: HeaderMap,
464474
query_string: Option<&Q>,
465475
body: Option<B>,
476+
#[allow(unused_variables)]
466477
timeout: Option<Duration>,
467478
) -> Result<reqwest::RequestBuilder, Error>
468479
where
@@ -473,6 +484,7 @@ impl Transport {
473484
let url = connection.url.join(path.trim_start_matches('/'))?;
474485
let mut request_builder = self.client.request(reqwest_method, url);
475486

487+
#[cfg(not(target_arch = "wasm32"))]
476488
if let Some(t) = timeout {
477489
request_builder = request_builder.timeout(t);
478490
}
@@ -482,7 +494,23 @@ impl Transport {
482494
// on a specific request, we want it to overwrite.
483495
if let Some(c) = &self.credentials {
484496
request_builder = match c {
485-
Credentials::Basic(u, p) => request_builder.basic_auth(u, Some(p)),
497+
Credentials::Basic(u, p) => {
498+
#[cfg(not(target_arch = "wasm32"))]
499+
{
500+
request_builder.basic_auth(u, Some(p))
501+
}
502+
#[cfg(target_arch = "wasm32")]
503+
{
504+
// Missing basic_auth in the wasm32 target
505+
let mut header_value = b"Basic ".to_vec();
506+
{
507+
let mut encoder = Base64Encoder::new(&mut header_value, base64::STANDARD);
508+
// The unwraps here are fine because Vec::write* is infallible.
509+
write!(encoder, "{}:{}", u, p).unwrap();
510+
}
511+
request_builder.header(reqwest::header::AUTHORIZATION, header_value)
512+
}
513+
},
486514
Credentials::Bearer(t) => request_builder.bearer_auth(t),
487515
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
488516
Credentials::Certificate(_) => request_builder,

elasticsearch/src/lib.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,7 +150,7 @@
150150
//! # }
151151
//! ```
152152
//!
153-
//! More control over how a [Transport](http::transport::Transport) is built can be
153+
//! More control over how a [e](http::transport::Transport) is built can be
154154
//! achieved using [TransportBuilder](http::transport::TransportBuilder) to build a transport, and
155155
//! passing it to [Elasticsearch::new] create a new instance of [Elasticsearch]
156156
//!
@@ -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/).

examples/cloudflare_worker/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
/target
2+
**/*.rs.bk
3+
wasm-pack.log
4+
build/

examples/cloudflare_worker/Cargo.toml

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
[package]
2+
name = "example_cloudflare_worker"
3+
version = "0.1.0"
4+
publish = false
5+
authors = ["Elastic and Contributors"]
6+
edition = "2018"
7+
8+
[workspace] # not part of the parent workspace
9+
10+
[lib]
11+
crate-type = ["cdylib", "rlib"]
12+
13+
[features]
14+
default = ["console_error_panic_hook"]
15+
16+
[dependencies]
17+
cfg-if = "0.1.2"
18+
worker = "0.0.9"
19+
serde_json = "1.0"
20+
web-sys = { version = "0.3", features = ["console"] }
21+
22+
elasticsearch = { path = "../../elasticsearch" }
23+
serde = "1.0"
24+
url = "2.2"
25+
26+
# The `console_error_panic_hook` crate provides better debugging of panics by
27+
# logging them with `console.error`. This is great for development, but requires
28+
# all the `std::fmt` and `std::panicking` infrastructure, so isn't great for
29+
# code size when deploying.
30+
console_error_panic_hook = { version = "0.1.1", optional = true }
31+
32+
[profile.release]
33+
# Tell `rustc` to optimize for small code size.
34+
opt-level = "s"

examples/cloudflare_worker/README.md

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Cloudflare workers
2+
3+
Example using [Cloudflare workers](https://workers.cloudflare.com/). Workers are Javascript function-as-a-service that can run WebAssembly. The [`wrangler` CLI](https://developers.cloudflare.com/workers/) provides everything to compile workers, test them locally and deploy them to Cloudflare.
4+
5+
After setting up the `wrangler` CLI, run locally with:
6+
7+
```
8+
wrangler secret put ES_URL
9+
<enter elasticsearch url>
10+
11+
wrangler secret put ES_LOGIN
12+
<enter elasticsearch login>
13+
14+
wrangler secret put ES_PASSWORD
15+
<enter elasticsearch password>
16+
17+
wrangler dev
18+
```
19+

examples/cloudflare_worker/src/lib.rs

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
use worker::*;
2+
use serde::Deserialize;
3+
use serde::Serialize;
4+
use elasticsearch::Elasticsearch;
5+
6+
mod utils;
7+
8+
// Types returned by the 'info' endpoint
9+
#[derive(Debug, Serialize, Deserialize)]
10+
pub struct VersionInfo {
11+
pub number: String,
12+
pub build_date: String,
13+
pub build_hash: String,
14+
pub build_snapshot: bool
15+
}
16+
17+
#[derive(Debug, Serialize, Deserialize)]
18+
pub struct InfoResponse {
19+
pub name: String,
20+
pub cluster_name: String,
21+
pub version: VersionInfo,
22+
pub tagline: String,
23+
}
24+
25+
// Global application context
26+
struct AppContext {
27+
pub es_client: Elasticsearch,
28+
}
29+
30+
#[event(fetch)]
31+
pub async fn main(req: Request, env: Env, _ctx: worker::Context) -> Result<Response> {
32+
log_request(&req);
33+
34+
// Optionally, get more helpful error messages written to the console in the case of a panic.
35+
utils::set_panic_hook();
36+
37+
// Fetch secrets and create ES client
38+
let url = env.secret("ES_URL")?.to_string();
39+
let login = env.secret("ES_LOGIN")?.to_string();
40+
let password = env.secret("ES_PASSWORD")?.to_string();
41+
42+
let router = Router::with_data(AppContext {
43+
es_client: create_client(url, login, password)
44+
.map_err(|e| Error::RustError(e.to_string()))?
45+
});
46+
47+
// HTTP request router
48+
router
49+
.get("/", |_, _| {
50+
Response::from_html("Hello from Workers! Please go to <a href='/es'>/es</a>")
51+
})
52+
.get_async("/es", |mut req, ctx| async move {
53+
let result = call_es(ctx.data.es_client).await
54+
.map_err(|e| Error::RustError(e.to_string()))?;
55+
Response::ok(result)
56+
})
57+
.run(req, env)
58+
.await
59+
}
60+
61+
fn create_client(url: String, login: String, password: String) -> StdResult<Elasticsearch, elasticsearch::Error> {
62+
63+
let url = url::Url::parse(&url)?;
64+
let pool = elasticsearch::http::transport::SingleNodeConnectionPool::new(url);
65+
let credentials = elasticsearch::auth::Credentials::Basic(login.into(), password.into());
66+
let transport = elasticsearch::http::transport::TransportBuilder::new(pool)
67+
.auth(credentials)
68+
.build()?;
69+
70+
Ok(Elasticsearch::new(transport))
71+
}
72+
73+
pub async fn call_es(client: Elasticsearch) -> std::result::Result<String, elasticsearch::Error> {
74+
let response = client.info().send().await?;
75+
let info = response.json::<InfoResponse>().await?;
76+
77+
Ok(format!("version: {}, build_date: {}", info.version.number, info.version.build_date))
78+
}
79+
80+
fn log_request(req: &Request) {
81+
console_log!(
82+
"{} - [{}], located at: {:?}, within: {}",
83+
Date::now().to_string(),
84+
req.path(),
85+
req.cf().coordinates().unwrap_or_default(),
86+
req.cf().region().unwrap_or("unknown region".into())
87+
);
88+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
use cfg_if::cfg_if;
2+
3+
cfg_if! {
4+
// https://github.com/rustwasm/console_error_panic_hook#readme
5+
if #[cfg(feature = "console_error_panic_hook")] {
6+
extern crate console_error_panic_hook;
7+
pub use self::console_error_panic_hook::set_once as set_panic_hook;
8+
} else {
9+
#[inline]
10+
pub fn set_panic_hook() {}
11+
}
12+
}
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name = "cloudflare_worker"
2+
type = "javascript"
3+
workers_dev = true
4+
compatibility_date = "2022-04-06"
5+
6+
[vars]
7+
WORKERS_RS_VERSION = "0.0.9"
8+
9+
# Secrets:
10+
# ES_URL
11+
# ES_LOGIN
12+
# ES_PASSWORD
13+
14+
[build]
15+
command = "cargo install -q worker-build && worker-build --release" # required
16+
17+
[build.upload]
18+
dir = "build/worker"
19+
format = "modules"
20+
main = "./shim.mjs"
21+
22+
[[build.upload.rules]]
23+
globs = ["**/*.wasm"]
24+
type = "CompiledWasm"
25+
26+
# read more about configuring your Worker via wrangler.toml at:
27+
# https://developers.cloudflare.com/workers/cli-wrangler/configuration

0 commit comments

Comments
 (0)