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"
+    );
+}

Reply via email to