Skip to content

Commit 4a94afe

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

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
Cargo.lock
45

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,
@@ -134,10 +134,13 @@ pub struct TransportBuilder {
134134
client_builder: reqwest::ClientBuilder,
135135
conn_pool: Box<dyn ConnectionPool>,
136136
credentials: Option<Credentials>,
137-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
137+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
138138
cert_validation: Option<CertificateValidation>,
139+
#[cfg(not(target_arch = "wasm32"))]
139140
proxy: Option<Url>,
141+
#[cfg(not(target_arch = "wasm32"))]
140142
proxy_credentials: Option<Credentials>,
143+
#[cfg(not(target_arch = "wasm32"))]
141144
disable_proxy: bool,
142145
headers: HeaderMap,
143146
meta_header: bool,
@@ -155,10 +158,13 @@ impl TransportBuilder {
155158
client_builder: reqwest::ClientBuilder::new(),
156159
conn_pool: Box::new(conn_pool),
157160
credentials: None,
158-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
161+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
159162
cert_validation: None,
163+
#[cfg(not(target_arch = "wasm32"))]
160164
proxy: None,
165+
#[cfg(not(target_arch = "wasm32"))]
161166
proxy_credentials: None,
167+
#[cfg(not(target_arch = "wasm32"))]
162168
disable_proxy: false,
163169
headers: HeaderMap::new(),
164170
meta_header: true,
@@ -170,6 +176,7 @@ impl TransportBuilder {
170176
///
171177
/// An optional username and password will be used to set the
172178
/// `Proxy-Authorization` header using Basic Authentication.
179+
#[cfg(not(target_arch = "wasm32"))]
173180
pub fn proxy(mut self, url: Url, username: Option<&str>, password: Option<&str>) -> Self {
174181
self.proxy = Some(url);
175182
if let Some(u) = username {
@@ -183,6 +190,7 @@ impl TransportBuilder {
183190
/// Whether to disable proxies, including system proxies.
184191
///
185192
/// NOTE: System proxies are enabled by default.
193+
#[cfg(not(target_arch = "wasm32"))]
186194
pub fn disable_proxy(mut self) -> Self {
187195
self.disable_proxy = true;
188196
self
@@ -197,7 +205,7 @@ impl TransportBuilder {
197205
/// Validation applied to the certificate provided to establish a HTTPS connection.
198206
/// By default, full validation is applied. When using a self-signed certificate,
199207
/// different validation can be applied.
200-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
208+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
201209
pub fn cert_validation(mut self, validation: CertificateValidation) -> Self {
202210
self.cert_validation = Some(validation);
203211
self
@@ -248,11 +256,12 @@ impl TransportBuilder {
248256
client_builder = client_builder.default_headers(self.headers);
249257
}
250258

259+
#[cfg(not(target_arch = "wasm32"))]
251260
if let Some(t) = self.timeout {
252261
client_builder = client_builder.timeout(t);
253262
}
254263

255-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
264+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
256265
{
257266
if let Some(creds) = &self.credentials {
258267
if let Credentials::Certificate(cert) = creds {
@@ -276,7 +285,7 @@ impl TransportBuilder {
276285
};
277286
}
278287

279-
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
288+
#[cfg(all(any(feature = "native-tls", feature = "rustls-tls"), not(target_arch = "wasm32")))]
280289
if let Some(v) = self.cert_validation {
281290
client_builder = match v {
282291
CertificateValidation::Default => client_builder,
@@ -296,6 +305,7 @@ impl TransportBuilder {
296305
}
297306
}
298307

308+
#[cfg(not(target_arch = "wasm32"))]
299309
if self.disable_proxy {
300310
client_builder = client_builder.no_proxy();
301311
} else if let Some(url) = self.proxy {
@@ -402,6 +412,7 @@ impl Transport {
402412
headers: HeaderMap,
403413
query_string: Option<&Q>,
404414
body: Option<B>,
415+
#[allow(unused_variables)]
405416
timeout: Option<Duration>,
406417
) -> Result<Response, Error>
407418
where
@@ -413,6 +424,7 @@ impl Transport {
413424
let reqwest_method = self.method(method);
414425
let mut request_builder = self.client.request(reqwest_method, url);
415426

427+
#[cfg(not(target_arch = "wasm32"))]
416428
if let Some(t) = timeout {
417429
request_builder = request_builder.timeout(t);
418430
}
@@ -422,7 +434,23 @@ impl Transport {
422434
// on a specific request, we want it to overwrite.
423435
if let Some(c) = &self.credentials {
424436
request_builder = match c {
425-
Credentials::Basic(u, p) => request_builder.basic_auth(u, Some(p)),
437+
Credentials::Basic(u, p) => {
438+
#[cfg(not(target_arch = "wasm32"))]
439+
{
440+
request_builder.basic_auth(u, Some(p))
441+
}
442+
#[cfg(target_arch = "wasm32")]
443+
{
444+
// Missing basic_auth in the wasm32 target
445+
let mut header_value = b"Basic ".to_vec();
446+
{
447+
let mut encoder = Base64Encoder::new(&mut header_value, base64::STANDARD);
448+
// The unwraps here are fine because Vec::write* is infallible.
449+
write!(encoder, "{}:{}", u, p).unwrap();
450+
}
451+
request_builder.header(reqwest::header::AUTHORIZATION, header_value)
452+
}
453+
},
426454
Credentials::Bearer(t) => request_builder.bearer_auth(t),
427455
#[cfg(any(feature = "native-tls", feature = "rustls-tls"))]
428456
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
//!
@@ -370,10 +370,12 @@ mod readme {
370370
extern crate dyn_clone;
371371

372372
pub mod auth;
373-
pub mod cert;
374373
pub mod http;
375374
pub mod params;
376375

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

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)