【问题标题】:Rust - Wasm - Iterating over input fileRust - Wasm - 迭代输入文件
【发布时间】:2021-08-28 20:06:40
【问题描述】:

所以,我试图通过输入字段上传的文件内容访问迭代器。

我可以通过 web-sys 将 JS 文件很好地传递到 Wasm,但我一生无法弄清楚如何访问 Rust 中传递文件的长度/名称以外的任何内容。

我认为我可以将整个文件作为 ByteArray 传递到 Wasm 并对其进行迭代,但我希望直接迭代文件内容而不进行复制,因为文件本身会非常大(~1 GB)。

我在 Mozilla JS 文档中发现我应该能够访问底层文件 blob,通过 .stream() 方法从中获取 ReadableStream 并从应该能够迭代的文件中获取 Reader,但是在 web-sys 中,ReadableStream 的 .getReader() 方法返回一个简单的 JSValue,我对此无能为力。

我在这里遗漏了什么,或者这个功能只是在 web-sys 中丢失了,还是有其他方法可以做到这一点? 也许在 JS 中创建 Iterator 并将其传递给 Rust?

【问题讨论】:

  • 您是否尝试过使用.dyn_into::<ReadableStream>().unwrap()JsValue 转换为可用类型?如果您有任何您尝试过的代码示例,我们可以从那里开始。也许链接你提到的参考......
  • 或类似let reader: Reader = rstream.getReader().try_into().unwrap();
  • web-sys 中没有Reader

标签: rust webassembly wasm-bindgen


【解决方案1】:

我认为您可以使用FileReader 做类似的事情。

这是一个示例,我在其中记录文件的文本内容:

use wasm_bindgen::prelude::*;
use web_sys::{Event, FileReader, HtmlInputElement};

use wasm_bindgen::JsCast;

#[wasm_bindgen]
extern "C" {
    #[wasm_bindgen(js_namespace = console)]
    fn log(s: &str);
}

#[wasm_bindgen(start)]
pub fn main() -> Result<(), JsValue> {
    let window = web_sys::window().expect("no global `window` exists");
    let document = window.document().expect("should have a document on window");
    let body = document.body().expect("document should have a body");

    let filereader = FileReader::new().unwrap().dyn_into::<FileReader>()?;

    let closure = Closure::wrap(Box::new(move |event: Event| {
        let element = event.target().unwrap().dyn_into::<FileReader>().unwrap();
        let data = element.result().unwrap();
        let js_data = js_sys::Uint8Array::from(data);
        let rust_str: String = js_data.to_string().into();
        log(rust_str.as_str());
    }) as Box<dyn FnMut(_)>);
 
    filereader.set_onloadend(Some(closure.as_ref().unchecked_ref()));
    closure.forget();

    let fileinput: HtmlInputElement = document.create_element("input").unwrap().dyn_into::<HtmlInputElement>()?;
    fileinput.set_type("file");

    let closure = Closure::wrap(Box::new(move |event: Event| {
        let element = event.target().unwrap().dyn_into::<HtmlInputElement>().unwrap();
        let filelist = element.files().unwrap();

        let file = filelist.get(0).unwrap();

        filereader.read_as_text(&file).unwrap();
        //log(filelist.length().to_string().as_str());
    }) as Box<dyn FnMut(_)>);
    fileinput.add_event_listener_with_callback("change", closure.as_ref().unchecked_ref())?;
    closure.forget();

    body.append_child(&fileinput)?;

    Ok(())
}

还有 HTML:

<html>
  <head>
    <meta content="text/html;charset=utf-8" http-equiv="Content-Type"/>
  </head>
  <body>
    <script type="module">
      import init from './pkg/without_a_bundler.js';

      async function run() {
        await init();
      }

      run();
    </script>
  </body>
</html>

Cargo.toml

[package]
name = "without-a-bundler"
version = "0.1.0"
authors = [""]
edition = "2018"

[lib]
crate-type = ["cdylib"]

[dependencies]
js-sys = "0.3.51"
wasm-bindgen = "0.2.74"

[dependencies.web-sys]
version = "0.3.4"
features = [
  'Blob',
  'BlobEvent',
  'Document',
  'Element',
  'Event',
  'File',
  'FileList',
  'FileReader',
  'HtmlElement',
  'HtmlInputElement',
  'Node',
  'ReadableStream',
  'Window',
]

但是我不知道如何使用ReadableStreamget_reader(),因为根据链接的documentation,它应该返回ReadableStreamDefaultReaderReadableStreamBYOBReader。虽然后者是实验性的,因此我认为这是可以理解的,它没有出现在 web-sys 中,但我不知道为什么 ReadableStreamDefaultReader 也没有出现。

【讨论】:

    【解决方案2】:

    我设法使用read_as_binary_string 获得了一个工作示例。

    这是代码

    lib.rs

    use js_sys::JsString;
    use std::cell::RefCell;
    use std::rc::Rc;
    use wasm_bindgen::prelude::*;
    use wasm_bindgen::JsCast;
    use web_sys::{console, Event, FileReader, HtmlInputElement};
    
    #[wasm_bindgen(start)]
    pub fn main_wasm() {
        let my_file: Rc<RefCell<Vec<u8>>> = Rc::new(RefCell::new(Vec::new()));
        set_file_reader(&my_file);
    }
    
    fn set_file_reader(file: &Rc<RefCell<Vec<u8>>>) {
        let filereader = FileReader::new().unwrap().dyn_into::<FileReader>().unwrap();
        let my_file = Rc::clone(&file);
    
        let onload = Closure::wrap(Box::new(move |event: Event| {
            let element = event.target().unwrap().dyn_into::<FileReader>().unwrap();
            let data = element.result().unwrap();
            let file_string: JsString = data.dyn_into::<JsString>().unwrap();
            let file_vec: Vec<u8> = file_string.iter().map(|x| x as u8).collect();
            *my_file.borrow_mut() = file_vec;
            console::log_1(&format!("file loaded: {:?}", file_string).into());
        }) as Box<dyn FnMut(_)>);
    
        filereader.set_onloadend(Some(onload.as_ref().unchecked_ref()));
        onload.forget();
    
        let fileinput: HtmlInputElement = web_sys::window()
            .unwrap()
            .document()
            .expect("should have a document.")
            .create_element("input")
            .unwrap()
            .dyn_into::<HtmlInputElement>()
            .unwrap();
    
        fileinput.set_id("file-upload");
        fileinput.set_type("file");
    
        web_sys::window()
            .unwrap()
            .document()
            .unwrap()
            .body()
            .expect("document should have a body")
            .append_child(&fileinput)
            .unwrap();
    
        let callback = Closure::wrap(Box::new(move |event: Event| {
            let element = event
                .target()
                .unwrap()
                .dyn_into::<HtmlInputElement>()
                .unwrap();
            let filelist = element.files().unwrap();
    
            let _file = filelist.get(0).expect("should have a file handle.");
            filereader.read_as_binary_string(&_file).unwrap();
        }) as Box<dyn FnMut(_)>);
    
        fileinput
            .add_event_listener_with_callback("change", callback.as_ref().unchecked_ref())
            .unwrap();
        callback.forget();
    }
    

    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="utf-8" />
      </head>
      <body>
        <noscript
          >This page contains webassembly and javascript content, please enable
          javascript in your browser.</noscript
        >
        <script src="./stack.js"></script>
        <script>
          wasm_bindgen("./stack_bg.wasm");
        </script>
      </body>
    </html>
    

    还有Cargo.toml

    [package]
    name = "stack"
    version = "0.1.0"
    authors = [""]
    edition = "2018"
    
    
    [lib]
    crate-type = ["cdylib", "rlib"]
    
    [dependencies]
    js-sys = "0.3.55"
    
    wee_alloc = { version = "0.4.2", optional = true }
    
    
    [dependencies.web-sys]
    version = "0.3.4"
    features = [
      'Document',
      'Window',
      'console',
      'Event',
      'FileReader',
      'File',
      'FileList',
      'HtmlInputElement']
    
    [dev-dependencies]
    wasm-bindgen-test = "0.2"
    
    [dependencies.wasm-bindgen]
    version = "0.2.70"
    
    [profile.release]
    # Tell `rustc` to optimize for small code size.
    opt-level = "s"
    debug = false
    
    
    

    您可以在此处查看示例:http://rustwasmfileinput.glitch.me/

    【讨论】:

    • 在代码库中添加 cmets
    【解决方案3】:

    最好的办法是使用 wasm_streams crate,它将 Web 流 API(如您从 .stream() 方法获得的 ReadableStream)连接到 Rust 异步流 API。

    官方示例使用 Fetch API 作为源,但这部分也与您的 File 用例相关:https://github.com/MattiasBuelens/wasm-streams/blob/f6dacf58a8826dc67923ab4a3bae87635690ca64/examples/fetch_as_stream.rs#L25-L33

    let body = ReadableStream::from_raw(raw_body.dyn_into().unwrap_throw());
    
    // Convert the JS ReadableStream to a Rust stream
    let mut stream = body.into_stream();
    
    // Consume the stream, logging each individual chunk
    while let Some(Ok(chunk)) = stream.next().await {
        console::log_1(&chunk);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2021-10-26
      • 1970-01-01
      • 2022-01-25
      • 1970-01-01
      • 2021-09-17
      • 2018-03-21
      • 2023-04-08
      • 1970-01-01
      相关资源
      最近更新 更多