Emscripten 支援使用 XHR 從 HTTP 伺服器延遲載入二進位資料。此功能可用於建立從已編譯程式碼同步存取檔案的後端。
此後端可以縮短啟動時間,因為在執行已編譯程式碼之前,不需要預先載入整個檔案系統。如果網路伺服器支援位元組服務,它也可以非常有效率 — 在這種情況下,Emscripten 可以僅讀取實際需要的檔案部分。
警告
此機制僅在Web Worker 中可行(由於瀏覽器限制)。
注意
如果網路伺服器不支援位元組服務,即使僅讀取單一位元組,Emscripten 也必須載入整個檔案(無論多大)。
在 test/test_browser.py 的測試程式碼中提供如何實作同步虛擬 XHR 後端檔案系統的範例(請參閱 test_chunked_synchronous_xhr
)。測試案例還包含一個 HTTP 伺服器(請參閱 test_chunked_synchronous_xhr_server),顯示可能需要設定的 CORS 標頭(如果資源是從 Emscripten 執行的相同網域託管,則沒有問題)。
測試使用 checksummer.c 作為 Emscripten 編譯的程式。這只是一個使用同步 *libc* 檔案系統呼叫(例如 fopen()
、fread()
、fclose()
等)的原始 C 程式。
加入 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
中。這可確保它在任何已編譯程式碼之前執行。
其他 JavaScript 是使用 *emcc* 的 prejs 選項加入的。
加入的 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; };
注意
如果您使用上述解決方案,父頁面可能應包含手寫的 glue code 來處理 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>