如何拦截应用全场景Fetch请求且兼容File URL?
解决方案:全局拦截所有Fetch请求(含Web Worker)并适配File URL
你的需求确实有点棘手——既要覆盖主线程、所有模块/第三方库的Fetch,还要支持file://协议(Electron和Android WebView场景),之前的几个方案各自有局限性:重写window.fetch碰不到Worker,Service Worker不支持本地文件,提前启动Worker又没法影响其他Worker的作用域。
核心思路:重写Worker构造函数,自动注入Fetch拦截逻辑
每个Worker都有独立的WorkerGlobalScope,所以要拦截所有Worker的Fetch,必须在每个Worker启动时自动注入重写代码。通过替换全局的Worker构造函数,我们可以拦截所有新建Worker的请求,不管是你自己写的还是第三方库创建的。
完整实现代码
第一步:重写主线程的Fetch
先搞定主线程的所有Fetch请求:
// 保存原生Fetch const originalWindowFetch = window.fetch; // 重写主线程Fetch window.fetch = function(input, init) { // 这里添加你的拦截逻辑,比如打印请求、修改参数等 console.log('[主线程] Fetch被拦截:', input); // 调用原生Fetch return originalWindowFetch.apply(this, arguments); };
第二步:重写Worker构造函数,自动注入拦截代码
这是关键部分,确保每个新建的Worker都会加载我们的Fetch重写逻辑:
// 保存原生Worker构造函数 const OriginalWorker = window.Worker; // 替换全局Worker构造函数 window.Worker = function(scriptURL, options) { // 定义要注入到Worker中的Fetch重写代码 const fetchOverrideCode = ` // 保存Worker原生Fetch const originalWorkerFetch = self.fetch; // 重写Worker的Fetch self.fetch = function(input, init) { console.log('[Worker] Fetch被拦截:', input); // 这里添加你的拦截处理逻辑 return originalWorkerFetch.apply(this, arguments); }; `; // 构造包装脚本:先执行拦截代码,再加载原Worker脚本 let wrapperScript; if (typeof scriptURL === 'string') { // 处理普通脚本URL(包括file://协议) wrapperScript = ` ${fetchOverrideCode} // 加载原Worker脚本 importScripts('${scriptURL}'); `; } else if (scriptURL instanceof Blob) { // 处理Blob形式的Worker脚本 const blobUrl = URL.createObjectURL(scriptURL); wrapperScript = ` ${fetchOverrideCode} importScripts('${blobUrl}'); // 清理临时Blob URL URL.revokeObjectURL('${blobUrl}'); `; } // 将包装脚本转为Blob URL const wrapperBlob = new Blob([wrapperScript], { type: 'application/javascript' }); const wrapperBlobUrl = URL.createObjectURL(wrapperBlob); // 创建原生Worker实例 const worker = new OriginalWorker(wrapperBlobUrl, options); // 监听Worker生命周期,清理Blob URL避免内存泄漏 const cleanup = () => URL.revokeObjectURL(wrapperBlobUrl); worker.addEventListener('error', cleanup); worker.addEventListener('messageerror', cleanup); worker.addEventListener('terminate', cleanup); return worker; };
第三步:(可选)处理SharedWorker
如果你的应用用到了SharedWorker,用同样的逻辑重写即可:
const OriginalSharedWorker = window.SharedWorker; window.SharedWorker = function(scriptURL, options) { // 复用上面的fetchOverrideCode和wrapperScript逻辑 const fetchOverrideCode = ` const originalWorkerFetch = self.fetch; self.fetch = function(input, init) { console.log('[SharedWorker] Fetch被拦截:', input); return originalWorkerFetch.apply(this, arguments); }; `; let wrapperScript; if (typeof scriptURL === 'string') { wrapperScript = ` ${fetchOverrideCode} importScripts('${scriptURL}'); `; } else if (scriptURL instanceof Blob) { const blobUrl = URL.createObjectURL(scriptURL); wrapperScript = ` ${fetchOverrideCode} importScripts('${blobUrl}'); URL.revokeObjectURL('${blobUrl}'); `; } const wrapperBlob = new Blob([wrapperScript], { type: 'application/javascript' }); const wrapperBlobUrl = URL.createObjectURL(wrapperBlob); const sharedWorker = new OriginalSharedWorker(wrapperBlobUrl, options); const cleanup = () => URL.revokeObjectURL(wrapperBlobUrl); sharedWorker.port.addEventListener('error', cleanup); sharedWorker.port.addEventListener('messageerror', cleanup); return sharedWorker; };
关键注意事项
- 执行顺序优先:这段重写代码必须在所有其他脚本(包括第三方库)加载之前执行,比如放在HTML的
<head>最顶部,或者在Electron的preload脚本中运行(preload脚本会在渲染进程加载前执行)。 - File URL适配:因为整个逻辑都是基于本地脚本注入,不需要Service Worker,所以完全支持
file://协议,不管是Electron还是Android WebView都能正常工作。 - 第三方库兼容性:只要第三方库是通过标准
new Worker()方式创建Worker,都会被拦截到——不需要修改任何第三方库的代码。 - 内存泄漏防范:记得在Worker的错误、终止等事件中清理创建的Blob URL,避免内存占用过高。
为什么之前的方案不行?
- 直接重写
window.fetch:只能覆盖主线程的Fetch,Worker有独立的全局作用域,根本看不到主线程的window对象。 - Service Worker:本身就不支持
file://协议(浏览器安全限制),而且需要HTTPS或localhost环境,不符合你的需求。 - 提前启动自定义Worker:每个Worker的作用域是完全隔离的,你创建的Worker只能修改自己的
self.fetch,无法影响其他Worker。
内容的提问来源于stack exchange,提问作者Anton Piliak
相关产品推荐
相关产品推荐

