Skip to content

Commit

Permalink
feat: add the HTTP bindings to perform HTTP requests from workers (#168)
Browse files Browse the repository at this point in the history
* feat: add the HTTP bindings to perform HTTP requests from workers

* fix: exclude wit-bindgen-backport packages from cargo deny

* fix: remove unnecesary channel when calling reqwest

* fix: remove unnecesary intermediate variable

* improve: implement From<reqwest::Error> to build the final error for the binding
  • Loading branch information
Angelmmiguel authored Jul 5, 2023
1 parent 59a6d6f commit 103f1cf
Show file tree
Hide file tree
Showing 10 changed files with 326 additions and 11 deletions.
96 changes: 89 additions & 7 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -68,8 +68,10 @@ exclude = [
actix-web = "4"
anyhow = "1.0.66"
lazy_static = "1.4.0"
reqwest = "0.11"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85"
tokio = "1.28"
toml = "0.7.0"
wws-config = { path = "./crates/config" }
wws-runtimes = { path = "./crates/runtimes" }
Expand Down
3 changes: 2 additions & 1 deletion crates/project/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,14 @@ repository = { workspace = true }

[dependencies]
anyhow = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
wws-store = { workspace = true }
url = "2.3.1"
sha256 = "1.1.1"
reqwest = "0.11"
git2 = "0.17.2"
# Not all platforms require OpenSSL
openssl = { workspace = true, optional = true }
Expand Down
5 changes: 5 additions & 0 deletions crates/worker/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,19 @@ doctest = false
[dependencies]
actix-web = { workspace = true }
anyhow = { workspace = true }
reqwest = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
tokio = { workspace = true }
toml = { workspace = true }
wasmtime = { workspace = true }
wasmtime-wasi = { workspace = true }
wasi-common = { workspace = true }
wws-config = { workspace = true }
wws-data-kv = { workspace = true }
wws-runtimes = { workspace = true }
# We didn't integrate components yet. For an initial binding implementation,
# we will use the wit-bindgen-wasmtime crate maintained by the Fermyon team.
wit-bindgen-wasmtime = { git = "https://github.com/fermyon/wit-bindgen-backport", rev = "b89d5079ba5b07b319631a1b191d2139f126c976" }
base64 = "0.21.0"
sha256 = "1.1.1"
119 changes: 119 additions & 0 deletions crates/worker/src/bindings/http.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

use reqwest::Method;
use tokio::runtime::Builder;

// Implement the HTTP bindings for the workers.
wit_bindgen_wasmtime::export!({paths: ["../../wit/core/http.wit"]});
use http::{Http, HttpError, HttpMethod, HttpRequest, HttpRequestError, HttpResponse};

pub use http::add_to_linker;

pub struct HttpBindings {}

/// Map the reqwest error to a known http-error
/// HttpError comes from the HTTP bindings
impl From<reqwest::Error> for HttpError {
fn from(value: reqwest::Error) -> Self {
if value.is_timeout() {
HttpError::Timeout
} else if value.is_redirect() {
HttpError::RedirectLoop
} else if value.is_request() {
HttpError::InvalidRequest
} else if value.is_body() {
HttpError::InvalidRequestBody
} else if value.is_decode() {
HttpError::InvalidResponseBody
} else {
HttpError::InternalError
}
}
}

impl Http for HttpBindings {
fn send_http_request(
&mut self,
req: HttpRequest<'_>,
) -> Result<HttpResponse, HttpRequestError> {
// Create local variables from the request
let mut headers = Vec::new();
let uri = req.uri.to_string();
let body = req.body.unwrap_or(&[]).to_vec();

for (key, value) in req.headers {
headers.push((key.to_string(), value.to_string()));
}

// Run the request in an async thread
let thread_result = std::thread::spawn(move || {
Builder::new_current_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let client = reqwest::Client::new();

let method = match req.method {
HttpMethod::Get => Method::GET,
HttpMethod::Post => Method::POST,
HttpMethod::Put => Method::PUT,
HttpMethod::Patch => Method::PATCH,
HttpMethod::Delete => Method::DELETE,
HttpMethod::Options => Method::OPTIONS,
HttpMethod::Head => Method::HEAD,
};

let mut builder = client.request(method, uri);

for (key, value) in headers {
builder = builder.header(key, value);
}

builder = builder.body(body);

match builder.send().await {
Ok(res) => {
let mut headers = Vec::new();
let status = res.status().as_u16();

for (name, value) in res.headers().iter() {
headers
.push((name.to_string(), value.to_str().unwrap().to_string()));
}

let body = res.bytes().await;

Ok(HttpResponse {
headers,
status,
body: Some(body.unwrap().to_vec()),
})
}
Err(e) => {
let message = e.to_string();

// Manage the different possible errors from Reqwest
Err(HttpRequestError {
error: e.into(),
message,
})
}
}
})
})
.join();

match thread_result {
Ok(res) => match res {
Ok(res) => Ok(res),
Err(err) => Err(err),
},
Err(_) => Err(HttpRequestError {
error: HttpError::InternalError,
message: "There was an error processing the request on the host side.".to_string(),
}),
}
}
}
4 changes: 4 additions & 0 deletions crates/worker/src/bindings/mod.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
// Copyright 2023 VMware, Inc.
// SPDX-License-Identifier: Apache-2.0

pub mod http;
Loading

0 comments on commit 103f1cf

Please sign in to comment.