ZBLOG

如何通过 postMessage/onmessage 将 WebWorker 的请求转发到主线程,由主线程统一处理。

要通过 postMessageonmessage 将 WebWorker 中的 fetch 请求转发到主线程处理,需要以下步骤:


1. 核心思路

  1. 劫持 WebWorker 的 fetch:在 Worker 脚本中覆盖原生 fetch,改为通过 postMessage 将请求参数发送给主线程。
  2. 主线程代理请求:主线程监听 Worker 的消息,收到请求后用自己的 fetch 发起真实请求,再将结果返回给 Worker。
  3. Worker 接收响应:Worker 监听主线程返回的结果,模拟原生 fetch 的 Promise 行为。

2. 完整实现代码

(1) OpenResty Lua 注入脚本

修改返回的 HTML,动态替换 Worker 的原始逻辑:

  1. location /your-path {
  2. body_filter_by_lua_block {
  3. local inject_code = [[
  4. <script>
  5. // Main thread logic
  6. (function() {
  7. // Store original Worker constructor
  8. const OriginalWorker = window.Worker;
  9. // Override Worker to inject our hook
  10. window.Worker = function(workerUrl) {
  11. // Step1: Fetch and modify the worker script
  12. const modifiedScript = fetch(workerUrl)
  13. .then(res => res.text())
  14. .then(scriptText => {
  15. // Inject our hook code into the worker script
  16. return `
  17. // ---- Injected Hook Start ----
  18. const originalFetch = fetch;
  19. fetch = function(input, init) {
  20. return new Promise((resolve, reject) => {
  21. // Send request details to main thread
  22. self.postMessage({
  23. type: '__FETCH_PROXY_REQUEST__',
  24. input: input,
  25. init: init
  26. });
  27. // Listen for response from main thread
  28. self.addEventListener('message', function handler(e) {
  29. if (e.data.type === '__FETCH_PROXY_RESPONSE__' && e.data.requestId === this.requestId) {
  30. self.removeEventListener('message', handler);
  31. if (e.data.error) {
  32. reject(new Error(e.data.error));
  33. } else {
  34. resolve(new Response(e.data.body, e.data.options));
  35. }
  36. }
  37. }.bind({ requestId: performance.now() + Math.random() }));
  38. });
  39. };
  40. // ---- Injected Hook End ----
  41. ${scriptText}`;
  42. });
  43. // Step2: Create a blob URL for the modified worker
  44. const blobUrl = modifiedScript.then(script => {
  45. const blob = new Blob([script], { type: 'application/javascript' });
  46. return URL.createObjectURL(blob);
  47. });
  48. // Step3: Create the worker with modified script
  49. return blobUrl.then(url => new OriginalWorker(url));
  50. };
  51. // Handle worker's fetch requests in main thread
  52. navigator.serviceWorker?.controller?.addEventListener('message', async (e) => {
  53. if (e.data.type === '__FETCH_PROXY_REQUEST__') {
  54. try {
  55. const response = await fetch(e.data.input, e.data.init);
  56. const body = await response.text();
  57. e.source.postMessage({
  58. type: '__FETCH_PROXY_RESPONSE__',
  59. requestId: e.data.requestId,
  60. body: body,
  61. options: {
  62. status: response.status,
  63. headers: Object.fromEntries(response.headers.entries())
  64. }
  65. });
  66. } catch (error) {
  67. e.source.postMessage({
  68. type: '__FETCH_PROXY_RESPONSE__',
  69. requestId: e.data.requestId,
  70. error: error.message
  71. });
  72. }
  73. }
  74. });
  75. })();
  76. </script>
  77. ]]
  78. -- Inject before </body>
  79. local body = ngx.arg[1]
  80. if body then
  81. ngx.arg[1] = string.gsub(body, "</body>", inject_code .. "</body>")
  82. end
  83. }
  84. }

(2) WebWorker 原始代码(被修改前)

假设原始的 Worker (worker.js)是这样的:

  1. // Original worker.js without hook
  2. self.onmessage = function(e) {
  3. fetch("https://api.example.com/data")
  4. .then(r => r.json())
  5. .then(data => postMessage(data))
  6. }

(3) Hook后的实际执行流程

  1. Worker调用 fetch() → 被劫持为向主线程发送消息。
  2. Main Thread收到消息 → 用真实 fetch() 发起请求。
  3. Main Thread返回结果 → 触发Worker内的Promise解析。

3. Key Points

  • 唯一ID标识请求:用 performance.now() + Math.random()生成唯一ID匹配请求/响应。
  • Response对象模拟:在主线程构造与原生Response结构相同的对象。
  • 错误处理:捕获网络错误并通过相同通道返回。

4. Limitations

  • Headers限制:部分只读Headers可能需要特殊处理。
  • Streaming响应:如果需要处理流式响应需额外逻辑。

需要更复杂的功能可以进一步扩展消息协议!


内容由零声教学AI助手提供,问题来源于学员提问

本站部分文章来源于网络,版权归原作者所有,如有侵权请联系站长删除。
转载请注明出处:https://golang.0voice.com/?id=23195

分享:
扫描分享到社交APP
上一篇
下一篇
发表列表
游客 游客
此处应有掌声~
评论列表

还没有评论,快来说点什么吧~

联系我们

在线咨询: 点击这里给我发消息

微信号:3007537140

上班时间: 10:30-22:30

关注我们
x

注册

已经有帐号?