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 代码被添加(使用 emcc 的 pre-js 选项)以将 checksummer.c 中的文件系统调用映射到虚拟文件系统中的文件。此文件在 Emscripten 初始化的早期使用 FS.createLazyFile()
创建,但只有在编译代码首次访问文件时才从服务器加载内容。添加的 JavaScript 代码还设置了 Web Worker 与主线程之间的通信。
您需要向生成的代码中添加 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
。这确保它在任何编译代码之前运行。
使用 emcc 的 prejs 选项添加了额外的 JavaScript。
添加的 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
数据。
您将需要一个生成 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>