This is an automated email from the ASF dual-hosted git repository.
xuanwo pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/opendal-reqsign.git
The following commit(s) were added to refs/heads/main by this push:
new 4997eef feat: Make http-send-reqwest available on wasm (#656)
4997eef is described below
commit 4997eef7616e600e2ae509af64b60dfa861ad7eb
Author: Xuanwo <[email protected]>
AuthorDate: Sun Oct 19 17:51:30 2025 +0900
feat: Make http-send-reqwest available on wasm (#656)
Allow https://github.com/apache/opendal/pull/6656 to pass.
---
**This PR was primarily authored with Codex using GPT-5-Codex and then
hand-reviewed by me. I AM responsible for every change made in this PR.
I aimed to keep it aligned with our goals, though I may have missed
minor issues. Please flag anything that feels off, I'll fix it
quickly.**
---------
Signed-off-by: Xuanwo <[email protected]>
---
.github/workflows/ci.yml | 14 +++-
context/http-send-reqwest/Cargo.toml | 9 ++-
.../http-send-reqwest/examples/custom_client.rs | 12 ++++
context/http-send-reqwest/src/lib.rs | 79 ++++++++++++++++++----
context/http-send-reqwest/tests/wasm.rs | 42 ++++++++++++
5 files changed, 142 insertions(+), 14 deletions(-)
diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index a13abda..b73bc6e 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -85,9 +85,21 @@ jobs:
env:
RUST_LOG: DEBUG
RUST_BACKTRACE: full
-
- name: Doc Test
run: cargo test --doc --all-features --workspace
env:
RUST_LOG: DEBUG
RUST_BACKTRACE: full
+
+ wasm_tests:
+ runs-on: ubuntu-latest
+ steps:
+ - uses: actions/checkout@v5
+ - name: Install wasm-bindgen-cli
+ run: cargo install wasm-bindgen-cli --version 0.2.104 --locked
+ - name: Run wasm tests
+ env:
+ CARGO_TARGET_WASM32_UNKNOWN_UNKNOWN_RUNNER: wasm-bindgen-test-runner
+ run: |
+ rustup target add wasm32-unknown-unknown
+ cargo test --target wasm32-unknown-unknown -p
reqsign-http-send-reqwest --test wasm
diff --git a/context/http-send-reqwest/Cargo.toml
b/context/http-send-reqwest/Cargo.toml
index f215dd6..4b5a21c 100644
--- a/context/http-send-reqwest/Cargo.toml
+++ b/context/http-send-reqwest/Cargo.toml
@@ -38,5 +38,12 @@ reqsign-core = { workspace = true }
reqwest = { workspace = true, default-features = false }
[dev-dependencies]
-tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
reqwest = { workspace = true, default-features = false, features =
["default-tls"] }
+wasm-bindgen-test = { version = "0.3" }
+
+[target.'cfg(target_arch = "wasm32")'.dependencies]
+futures-channel = { version = "0.3" }
+wasm-bindgen-futures = { version = "0.4" }
+
+[target.'cfg(not(target_arch = "wasm32"))'.dev-dependencies]
+tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
diff --git a/context/http-send-reqwest/examples/custom_client.rs
b/context/http-send-reqwest/examples/custom_client.rs
index 1124ff5..546ef85 100644
--- a/context/http-send-reqwest/examples/custom_client.rs
+++ b/context/http-send-reqwest/examples/custom_client.rs
@@ -15,13 +15,20 @@
// specific language governing permissions and limitations
// under the License.
+#[cfg(not(target_arch = "wasm32"))]
use anyhow::Result;
+#[cfg(not(target_arch = "wasm32"))]
use bytes::Bytes;
+#[cfg(not(target_arch = "wasm32"))]
use reqsign_core::{Context, OsEnv};
+#[cfg(not(target_arch = "wasm32"))]
use reqsign_http_send_reqwest::ReqwestHttpSend;
+#[cfg(not(target_arch = "wasm32"))]
use reqwest::Client;
+#[cfg(not(target_arch = "wasm32"))]
use std::time::Duration;
+#[cfg(not(target_arch = "wasm32"))]
#[tokio::main]
async fn main() -> Result<()> {
// Create a custom reqwest client with specific configuration
@@ -95,3 +102,8 @@ async fn main() -> Result<()> {
Ok(())
}
+
+#[cfg(target_arch = "wasm32")]
+fn main() {
+ panic!("custom_client example is not supported on wasm targets");
+}
diff --git a/context/http-send-reqwest/src/lib.rs
b/context/http-send-reqwest/src/lib.rs
index eb81c84..dfbf029 100644
--- a/context/http-send-reqwest/src/lib.rs
+++ b/context/http-send-reqwest/src/lib.rs
@@ -95,9 +95,14 @@
use async_trait::async_trait;
use bytes::Bytes;
+#[cfg(target_arch = "wasm32")]
+use futures_channel::oneshot;
+#[cfg(not(target_arch = "wasm32"))]
use http_body_util::BodyExt;
use reqsign_core::{Error, HttpSend, Result};
use reqwest::{Client, Request};
+#[cfg(target_arch = "wasm32")]
+use wasm_bindgen_futures::spawn_local;
/// Reqwest-based implementation of the `HttpSend` trait.
///
@@ -137,18 +142,68 @@ impl HttpSend for ReqwestHttpSend {
async fn http_send(&self, req: http::Request<Bytes>) ->
Result<http::Response<Bytes>> {
let req = Request::try_from(req)
.map_err(|e| Error::unexpected("failed to convert
request").with_source(e))?;
- let resp: http::Response<_> = self
- .client
- .execute(req)
- .await
- .map_err(|e| Error::unexpected("failed to send HTTP
request").with_source(e))?
- .into();
- let (parts, body) = resp.into_parts();
- let bs = BodyExt::collect(body)
- .await
- .map(|buf| buf.to_bytes())
- .map_err(|e| Error::unexpected("failed to collect response
body").with_source(e))?;
- Ok(http::Response::from_parts(parts, bs))
+ #[cfg(not(target_arch = "wasm32"))]
+ {
+ return http_send_native(&self.client, req).await;
+ }
+
+ #[cfg(target_arch = "wasm32")]
+ {
+ return http_send_wasm(&self.client, req).await;
+ }
}
}
+
+#[cfg(not(target_arch = "wasm32"))]
+async fn http_send_native(client: &Client, req: Request) ->
Result<http::Response<Bytes>> {
+ let resp = client
+ .execute(req)
+ .await
+ .map_err(|e| Error::unexpected("failed to send HTTP
request").with_source(e))?;
+
+ let resp: http::Response<_> = resp.into();
+ let (parts, body) = resp.into_parts();
+ let bs = BodyExt::collect(body)
+ .await
+ .map(|buf| buf.to_bytes())
+ .map_err(|e| Error::unexpected("failed to collect response
body").with_source(e))?;
+ Ok(http::Response::from_parts(parts, bs))
+}
+
+#[cfg(target_arch = "wasm32")]
+async fn http_send_wasm(client: &Client, req: Request) ->
Result<http::Response<Bytes>> {
+ let (tx, rx) = oneshot::channel();
+ let client = client.clone();
+
+ // reqwest's wasm client is !Send, so drive the request on the local
executor
+ // and forward the result back through a channel to satisfy HttpSend's
Send requirement.
+ spawn_local(async move {
+ let result = async move {
+ let resp = client
+ .execute(req)
+ .await
+ .map_err(|e| Error::unexpected("failed to send HTTP
request").with_source(e))?;
+
+ let status = resp.status();
+ let headers = resp.headers().clone();
+ let body = resp
+ .bytes()
+ .await
+ .map_err(|e| Error::unexpected("failed to collect response
body").with_source(e))?;
+
+ let mut response = http::Response::builder()
+ .status(status)
+ .body(body)
+ .map_err(|e| Error::unexpected("failed to build HTTP
response").with_source(e))?;
+ *response.headers_mut() = headers;
+ Ok(response)
+ }
+ .await;
+
+ let _ = tx.send(result);
+ });
+
+ rx.await
+ .map_err(|_| Error::unexpected("failed to receive response from wasm
task"))?
+}
diff --git a/context/http-send-reqwest/tests/wasm.rs
b/context/http-send-reqwest/tests/wasm.rs
new file mode 100644
index 0000000..611c909
--- /dev/null
+++ b/context/http-send-reqwest/tests/wasm.rs
@@ -0,0 +1,42 @@
+// Licensed to the Apache Software Foundation (ASF) under one
+// or more contributor license agreements. See the NOTICE file
+// distributed with this work for additional information
+// regarding copyright ownership. The ASF licenses this file
+// to you under the Apache License, Version 2.0 (the
+// "License"); you may not use this file except in compliance
+// with the License. You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing,
+// software distributed under the License is distributed on an
+// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+// KIND, either express or implied. See the License for the
+// specific language governing permissions and limitations
+// under the License.
+
+#![cfg(target_arch = "wasm32")]
+
+use bytes::Bytes;
+use http::Request;
+use reqsign_core::HttpSend;
+use reqsign_http_send_reqwest::ReqwestHttpSend;
+use wasm_bindgen_test::*;
+
+#[wasm_bindgen_test]
+async fn http_send_returns_error_for_unreachable_host() {
+ let client = reqwest::Client::new();
+ let http_send = ReqwestHttpSend::new(client);
+
+ let req = Request::builder()
+ .method("GET")
+ .uri("https://nonexistent.invalid")
+ .body(Bytes::new())
+ .expect("request builds");
+
+ let result = http_send.http_send(req).await;
+ assert!(
+ result.is_err(),
+ "expected unreachable host to produce error"
+ );
+}