要通过 postMessage
和 onmessage
将 WebWorker 中的 fetch
请求转发到主线程处理,需要以下步骤:
1. 核心思路
- 劫持 WebWorker 的
fetch
:在 Worker 脚本中覆盖原生fetch
,改为通过postMessage
将请求参数发送给主线程。 - 主线程代理请求:主线程监听 Worker 的消息,收到请求后用自己的
fetch
发起真实请求,再将结果返回给 Worker。 - Worker 接收响应:Worker 监听主线程返回的结果,模拟原生
fetch
的 Promise 行为。
2. 完整实现代码
(1) OpenResty Lua 注入脚本
修改返回的 HTML,动态替换 Worker 的原始逻辑:
location /your-path {
body_filter_by_lua_block {
local inject_code = [[
<script>
// Main thread logic
(function() {
// Store original Worker constructor
const OriginalWorker = window.Worker;
// Override Worker to inject our hook
window.Worker = function(workerUrl) {
// Step1: Fetch and modify the worker script
const modifiedScript = fetch(workerUrl)
.then(res => res.text())
.then(scriptText => {
// Inject our hook code into the worker script
return `
// ---- Injected Hook Start ----
const originalFetch = fetch;
fetch = function(input, init) {
return new Promise((resolve, reject) => {
// Send request details to main thread
self.postMessage({
type: '__FETCH_PROXY_REQUEST__',
input: input,
init: init
});
// Listen for response from main thread
self.addEventListener('message', function handler(e) {
if (e.data.type === '__FETCH_PROXY_RESPONSE__' && e.data.requestId === this.requestId) {
self.removeEventListener('message', handler);
if (e.data.error) {
reject(new Error(e.data.error));
} else {
resolve(new Response(e.data.body, e.data.options));
}
}
}.bind({ requestId: performance.now() + Math.random() }));
});
};
// ---- Injected Hook End ----
${scriptText}`;
});
// Step2: Create a blob URL for the modified worker
const blobUrl = modifiedScript.then(script => {
const blob = new Blob([script], { type: 'application/javascript' });
return URL.createObjectURL(blob);
});
// Step3: Create the worker with modified script
return blobUrl.then(url => new OriginalWorker(url));
};
// Handle worker's fetch requests in main thread
navigator.serviceWorker?.controller?.addEventListener('message', async (e) => {
if (e.data.type === '__FETCH_PROXY_REQUEST__') {
try {
const response = await fetch(e.data.input, e.data.init);
const body = await response.text();
e.source.postMessage({
type: '__FETCH_PROXY_RESPONSE__',
requestId: e.data.requestId,
body: body,
options: {
status: response.status,
headers: Object.fromEntries(response.headers.entries())
}
});
} catch (error) {
e.source.postMessage({
type: '__FETCH_PROXY_RESPONSE__',
requestId: e.data.requestId,
error: error.message
});
}
}
});
})();
</script>
]]
-- Inject before </body>
local body = ngx.arg[1]
if body then
ngx.arg[1] = string.gsub(body, "</body>", inject_code .. "</body>")
end
}
}
(2) WebWorker 原始代码(被修改前)
假设原始的 Worker (worker.js
)是这样的:
// Original worker.js without hook
self.onmessage = function(e) {
fetch("https://api.example.com/data")
.then(r => r.json())
.then(data => postMessage(data))
}
(3) Hook后的实际执行流程
- Worker调用
fetch()
→ 被劫持为向主线程发送消息。 - Main Thread收到消息 → 用真实
fetch()
发起请求。 - Main Thread返回结果 → 触发Worker内的Promise解析。
3. Key Points
- 唯一ID标识请求:用
performance.now() + Math.random()
生成唯一ID匹配请求/响应。 - Response对象模拟:在主线程构造与原生Response结构相同的对象。
- 错误处理:捕获网络错误并通过相同通道返回。
4. Limitations
- Headers限制:部分只读Headers可能需要特殊处理。
- Streaming响应:如果需要处理流式响应需额外逻辑。
需要更复杂的功能可以进一步扩展消息协议!
内容由零声教学AI助手提供,问题来源于学员提问