同步虚拟 XHR 支持的文件系统用法

Emscripten 支持使用 XHR 从 HTTP 服务器懒加载二进制数据。此功能可用于创建编译代码中同步文件访问的后端。

后端可以缩短启动时间,因为在运行编译代码之前不需要预加载整个文件系统。如果 Web 服务器支持 字节服务,它也可能非常高效 - 在这种情况下,Emscripten 只能读取实际需要的文件部分。

警告

此机制仅在 Web Workers 中可用(由于浏览器限制)。

注意

如果不支持字节服务,则 Emscripten 将不得不加载整个文件(无论大小),即使只读取了一个字节。

测试代码

如何在测试代码中实现同步虚拟 XHR 支持的文件系统的示例,见 test/test_browser.py(见 test_chunked_synchronous_xhr)。测试用例还包含一个 HTTP 服务器(见 test_chunked_synchronous_xhr_server),它显示可能需要设置的 CORS 标头(如果资源从与 Emscripten 运行的域相同的域托管,则不会出现问题)。

测试使用 checksummer.c 作为 Emscripten 编译的程序。这只是一个使用同步 libc 文件系统调用的普通 C 程序,比如 fopen()fread()fclose() 等。

JavaScript 代码被添加(使用 emccpre-js 选项)以将 checksummer.c 中的文件系统调用映射到虚拟文件系统中的文件。此文件在 Emscripten 初始化的早期使用 FS.createLazyFile() 创建,但只有在编译代码首次访问文件时才从服务器加载内容。添加的 JavaScript 代码还设置了 Web Worker 与主线程之间的通信。

说明

  1. 您需要向生成的代码中添加 JavaScript 以映射您的编译本机代码访问的文件和服务器。

    测试代码只是使用 FS.createLazyFile() 在虚拟文件系统中创建一个文件,并将编译代码设置为使用同一个文件(/bigfile)。

    , r"""
          Module.arguments = ["/bigfile"];
          Module.preInit = () => {
            FS.createLazyFile('/', "bigfile", "https://127.0.0.1:11111/bogus_file_path", true, false);
          };
          
    

    注意

    • 编译后的测试代码(在本例中)从命令行参数获取文件名 - 这些参数在 Emscripten 中使用 Module.arguments 设置。

    • 创建文件的调用被添加到 Module.preInit。这确保它在任何编译代码之前运行。

    • 使用 emccprejs 选项添加了额外的 JavaScript。

  2. 添加的 JavaScript 还应包含允许 Web Worker 与原始线程通信的代码。

    测试代码为此目的将以下 JavaScript 添加到 Web Worker。它使用 postMessage() 将其 stdout 发送回主线程。

    
          Module.print = (s) => self.postMessage({channel: "stdout", line: s});
          Module.printErr = (s) => { self.postMessage({channel: "stderr", char: s, trace: ((doTrace && s === 10) ? new Error().stack : null)}); doTrace = false; };
        
    

    注意

    如果您使用上述解决方案,则父页面可能应该包含手工编写的粘合代码来处理 stdout 数据。

  3. 您将需要一个生成 Web Worker 的页面。

    下面的 测试代码 展示了这一点。

     '''
          <html>
          <body>
            Worker Test
            <script>
              var worker = new Worker('worker.js');
              worker.onmessage = async (event) => {
                await fetch('https://127.0.0.1:%s/report_result?' + event.data);
                window.close();
              };
            </script>
          </body>
          </html>
        ''' % self.port)
    
        for file_data in (1, 0):
          cmd = [EMCC, test_file('hello_world_worker.cpp'), '-o', 'worker.js'] + self.get_emcc_args()
          if file_data:
            cmd += ['--preload-file', 'file.dat']
          self.run_process(cmd)
          self.assertExists('worker.js')
          self.run_browser('main.html', '/report_result?hello from worker, and :' + ('data for w' if file_data else '') + ':')
    
        # code should run standalone too
        # To great memories >4gb we need the canary version of node
        if self.is_4gb():
          self.require_node_canary()
        self.assertContained('you should not see this text when in a worker!', self.run_js('worker.js'))
    
      @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/19608')
      def test_mmap_lazyfile(self):
        create_file('lazydata.dat', 'hello world')
        create_file('pre.js', '''
          Module["preInit"] = () => {
            FS.createLazyFile('/', "lazy.txt", "lazydata.dat", true, false);
          }
        ''')
        self.emcc_args += ['--pre-js=pre.js', '--proxy-to-worker']
        self.btest_exit('test_mmap_lazyfile.c')
    
      @no_wasmfs('https://github.com/emscripten-core/emscripten/issues/19608')
      @no_firefox('keeps sending OPTIONS requests, and eventually errors')
      def test_chunked_synchronous_xhr(self):
        main = 'chunked_sync_xhr.html'
        worker_filename = "download_and_checksum_worker.js"
    
        create_file(main, r"""
          <!doctype html>
          <html>
          <head><meta charset="utf-8"><title>Chunked XHR</title></head>
          <body>
            Chunked XHR Web Worker Test
            <script>
              var worker = new Worker("%s");
              var buffer = [];
              worker.onmessage = async (event) => {
                if (event.data.channel === "stdout") {
                  await fetch('https://127.0.0.1:%s/report_result?' + event.data.line);
                  window.close();
                } else {
                  if (event.data.trace) event.data.trace.split("\n").map(function(v) { console.error(v); });
                  if (event.data.line) {
                    console.error(event.data.line);
                  } else {
                    var v = event.data.char;
                    if (v == 10) {
                      var line = buffer.splice(0);
                      console.error(line = line.map(function(charCode){return String.fromCharCode(charCode);}).join(''));
                    } else {
                      buffer.push(v);
                    }
                  }
                }
              };
            </script>
          </body>
          </html>