Rust/WebAssembly流式HTTP请求:将ReadableStream的JsValue转为向量
问题:Rust/Wasm流式读取二进制文件并转换为16位整数向量
背景
作为Rust新手,我正在开发一个展示大量边的图形网页,计划用WebAssembly做图布局计算节点位置,并用WebGL绘图。需要流式请求一个约7MB的二进制文件,文件由16位整数组成,以最大值作为分隔符,每个索引对应目标节点索引。希望流式读取数据,拿到第一块后就开始布局。
问题
需要将流式响应的JsValue块正确转换为数组,最终目标是转为16位整数向量。
尝试的方案
初始可运行代码(lib.rs)
use js_sys::Uint8Array; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::{ReadableStreamDefaultReader, Response}; #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } #[wasm_bindgen] pub async fn fetch_and_compute_graph() -> Result<JsValue, JsValue> { let window = web_sys::window().unwrap(); let resp_promise = window.fetch_with_str(&"./edges.bin"); let resp_value = JsFuture::from(resp_promise).await?; let resp: Response = resp_value.dyn_into().unwrap(); log(&format!("Response status code: {}", resp.status())); if resp.status() != 200 { return Err(JsValue::FALSE); } let reader_obj = resp.body().unwrap().get_reader(); let stream_reader: ReadableStreamDefaultReader = reader_obj.dyn_into().unwrap(); // 后续尝试的代码添加在这里 }
尝试1:直接类型转换
尝试将read()返回的结果转为Uint8Array,但报错:
let chunk_obj = JsFuture::from(stream_reader.read()).await?; let chunk_bytes: Uint8Array = chunk_obj.dyn_into().unwrap();
报错信息:
Uncaught (in promise) RuntimeError: unreachable executed __wbg_adapter_14 http://localhost:8080/pkg/rust_wasm_centrality.js:204 real http://localhost:8080/pkg/rust_wasm_centrality.js:189 promise callback*getImports/imports.wbg.__wbg_then_11f7a54d67b4bfad http://localhost:8080/pkg/rust_wasm_centrality.js:326 __wbg_adapter_14 http://localhost:8080/pkg/rust_wasm_centrality.js:204 real http://localhost:8080/pkg/rust_wasm_centrality.js:189 rust_wasm_centrality_bg.wasm:24649:1
尝试2:serde反序列化
用serde-wasm-bindgen反序列化,但结果转为了JS对象,不符合预期:
let chunk_obj = JsFuture::from(stream_reader.read()).await?; let bytes = serde_wasm_bindgen::from_value(chunk_obj)?;
控制台输出错误:
Loading graph result: Error: invalid type: JsValue(Object({"done":false,"value":{"0":170,"1":4,"2":180,"3":4,...
解决方案
核心问题是ReadableStreamDefaultReader.read()返回的是包含done和value的JS对象,而非直接的Uint8Array。需要先解构这个对象,提取出二进制块,再转换为16位整数向量。
完整修正代码
use js_sys::Uint8Array; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use wasm_bindgen_futures::JsFuture; use web_sys::{ReadableStreamDefaultReader, Response}; use std::io::{Cursor, Read}; use byteorder::{ByteOrder, LittleEndian}; // 需要添加byteorder依赖 #[wasm_bindgen] extern "C" { #[wasm_bindgen(js_namespace = console)] fn log(s: &str); } #[wasm_bindgen] pub async fn fetch_and_compute_graph() -> Result<JsValue, JsValue> { let window = web_sys::window().unwrap(); let resp_promise = window.fetch_with_str(&"./edges.bin"); let resp_value = JsFuture::from(resp_promise).await?; let resp: Response = resp_value.dyn_into().unwrap(); log(&format!("Response status code: {}", resp.status())); if resp.status() != 200 { return Err(JsValue::FALSE); } let reader_obj = resp.body().unwrap().get_reader(); let stream_reader: ReadableStreamDefaultReader = reader_obj.dyn_into().unwrap(); loop { let result = JsFuture::from(stream_reader.read()).await?; // 解构done标记,判断流是否结束 let done = js_sys::Reflect::get(&result, &JsValue::from_str("done"))? .as_bool() .ok_or("Failed to get 'done' flag")?; if done { log("Stream completed"); break; } // 提取二进制块并转为Uint8Array let value = js_sys::Reflect::get(&result, &JsValue::from_str("value"))?; let chunk_bytes: Uint8Array = value.dyn_into()?; // 将Uint8Array复制到Rust的u8向量中 let mut buffer = vec![0u8; chunk_bytes.length() as usize]; chunk_bytes.copy_to(&mut buffer); // 转换为16位整数向量(根据实际字节序调整为BigEndian) let mut u16_vec = Vec::with_capacity(buffer.len() / 2); let mut cursor = Cursor::new(buffer); while let Ok(u16_val) = LittleEndian::read_u16(&mut cursor) { u16_vec.push(u16_val); } // 处理当前块的16位整数,比如启动布局计算 log(&format!("Processed {} 16-bit integers", u16_vec.len())); } Ok(JsValue::UNDEFINED) }
依赖调整
在Cargo.toml中添加byteorder依赖:
[dependencies] # 原有依赖保留 byteorder = "1.4"
关键说明
- 解构流读取结果:通过
js_sys::Reflect::get读取done和value属性,判断流是否结束并提取二进制块。 - JS数组转Rust向量:用
copy_to方法将JS的Uint8Array复制到Rust的Vec<u8>中。 - 字节序处理:使用
byteorder库解析16位整数,若二进制文件是大端字节序,替换LittleEndian为BigEndian即可。
内容的提问来源于stack exchange,提问作者Simon Crowe
相关产品推荐
相关产品推荐

