Skip to main content

multistore_cf_workers/
body.rs

1//! Zero-copy body wrapper for Cloudflare Workers.
2//!
3//! Holds the raw `ReadableStream` from an incoming request so it can be
4//! forwarded to the backend without copying through WASM memory.
5
6use bytes::Bytes;
7
8/// Zero-copy body wrapper. Holds the raw `ReadableStream` from the incoming
9/// request, passing it through the Gateway untouched for Forward requests.
10pub struct JsBody(Option<web_sys::ReadableStream>);
11
12impl JsBody {
13    /// Wrap an optional `ReadableStream` from a `web_sys::Request`.
14    pub fn new(stream: Option<web_sys::ReadableStream>) -> Self {
15        Self(stream)
16    }
17
18    /// Borrow the inner stream, if present.
19    pub fn stream(&self) -> Option<&web_sys::ReadableStream> {
20        self.0.as_ref()
21    }
22
23    /// Build a `JsBody` whose stream yields the given bytes.
24    ///
25    /// Used to hand an equivalent body back after collecting one (e.g.
26    /// [`RequestParts::absorb_form_body`](crate::request::RequestParts::absorb_form_body)),
27    /// so a request that falls through to the forwarding path still carries
28    /// its payload. Uses the same `web_sys::Response` trick as
29    /// [`collect_js_body`], in reverse.
30    pub fn from_bytes(bytes: &[u8]) -> Result<Self, String> {
31        if bytes.is_empty() {
32            return Ok(Self::new(None));
33        }
34        let mut buf = bytes.to_vec();
35        let resp = web_sys::Response::new_with_opt_u8_array(Some(&mut buf))
36            .map_err(|e| format!("Response::new failed: {:?}", e))?;
37        Ok(Self::new(resp.body()))
38    }
39}
40
41// SAFETY: Workers is single-threaded; these are required by Gateway's generic bounds.
42unsafe impl Send for JsBody {}
43unsafe impl Sync for JsBody {}
44
45/// Materialize a `JsBody` into `Bytes` for the NeedsBody path.
46///
47/// Uses the `Response::arrayBuffer()` JS trick: wrap the stream in a
48/// `web_sys::Response`, call `.array_buffer()`, and convert via `Uint8Array`.
49/// This is only used for small multipart payloads.
50pub async fn collect_js_body(body: JsBody) -> std::result::Result<Bytes, String> {
51    match body.0 {
52        None => Ok(Bytes::new()),
53        Some(stream) => {
54            let resp = web_sys::Response::new_with_opt_readable_stream(Some(&stream))
55                .map_err(|e| format!("Response::new failed: {:?}", e))?;
56            let promise = resp
57                .array_buffer()
58                .map_err(|e| format!("arrayBuffer() failed: {:?}", e))?;
59            let buf = wasm_bindgen_futures::JsFuture::from(promise)
60                .await
61                .map_err(|e| format!("arrayBuffer await failed: {:?}", e))?;
62            let uint8 = js_sys::Uint8Array::new(&buf);
63            Ok(Bytes::from(uint8.to_vec()))
64        }
65    }
66}