You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为HTML页面所有HTTP请求前置拼接代理URL

页面全量HTTP请求前置拼接代理URL实现方案

由于Service Worker需要等待页面加载完成后才能注册生效,无法覆盖页面初始化阶段发出的请求,可选择以下两类方案实现加载前即生效的请求代理拼接:

方案1:页面头部阻塞脚本提前劫持原生请求API

  • 核心逻辑:在HTML文档<head>标签的最开头插入一段无async/defer属性的同步阻塞脚本,在所有其他资源、业务脚本加载执行前,覆写浏览器所有发起网络请求的原生入口,所有请求发出前自动拼接代理前缀。
  • 需要覆盖的请求入口:
    • 覆写window.fetch,拦截所有fetch发起的接口、资源请求
    • 覆写XMLHttpRequest.prototype.open,拦截所有XHR异步请求
    • 通过Object.defineProperty劫持各类资源标签的src/href属性赋值逻辑,覆盖img、script、link、iframe、音视频等标签的静态资源加载请求
  • 参考实现代码:
<!-- 必须放在head最顶部,所有其他meta、资源、脚本之前,保证最先执行 -->
<script>
// 替换为实际使用的代理地址前缀
const PROXY_URL_PREFIX = 'https://your-proxy-address/forward?target=';
// 过滤不需要走代理的特殊协议
const SKIP_PROTOCOL_PREFIX = ['data:', 'blob:', 'about:'];

// 拦截fetch请求
const rawFetch = window.fetch;
window.fetch = function(requestInit, config) {
  let targetUrl = typeof requestInit === 'string' ? requestInit : requestInit.url;
  const needProxy = !targetUrl.startsWith(PROXY_URL_PREFIX) 
    && !SKIP_PROTOCOL_PREFIX.some(prefix => targetUrl.startsWith(prefix));
  if (needProxy) {
    targetUrl = PROXY_URL_PREFIX + encodeURIComponent(targetUrl);
    requestInit = typeof requestInit === 'string' ? targetUrl : new Request(targetUrl, requestInit);
  }
  return rawFetch.call(this, requestInit, config);
};

// 拦截XHR请求
const rawXHROpen = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, ...restArgs) {
  const needProxy = !url.startsWith(PROXY_URL_PREFIX)
    && !SKIP_PROTOCOL_PREFIX.some(prefix => url.startsWith(prefix));
  if (needProxy) {
    url = PROXY_URL_PREFIX + encodeURIComponent(url);
  }
  return rawXHROpen.call(this, method, url, ...restArgs);
};

// 拦截标签静态资源请求
const resourceHookList = [
  [HTMLImageElement.prototype, 'src'],
  [HTMLScriptElement.prototype, 'src'],
  [HTMLLinkElement.prototype, 'href'],
  [HTMLIFrameElement.prototype, 'src'],
  [HTMLAudioElement.prototype, 'src'],
  [HTMLVideoElement.prototype, 'src'],
  [HTMLFormElement.prototype, 'action']
];
resourceHookList.forEach(([elementProto, property]) => {
  const originalPropDescriptor = Object.getOwnPropertyDescriptor(elementProto, property);
  Object.defineProperty(elementProto, property, {
    get() {
      return originalPropDescriptor.get.call(this);
    },
    set(newValue) {
      const needProxy = newValue 
        && !newValue.startsWith(PROXY_URL_PREFIX)
        && !SKIP_PROTOCOL_PREFIX.some(prefix => newValue.startsWith(prefix));
      const finalValue = needProxy ? PROXY_URL_PREFIX + encodeURIComponent(newValue) : newValue;
      return originalPropDescriptor.set.call(this, finalValue);
    },
    configurable: true
  });
});
</script>
  • 注意事项:这段脚本不能加任何异步加载属性,必须保证在文档解析的第一时间执行,否则会漏掉脚本执行前已经发起的请求。

方案2:网络层/服务端前置处理,零前端侵入

如果不想在前端写劫持逻辑,或者需要100%覆盖所有请求无遗漏,可以选择更上层的处理方案:

  • 服务端处理:托管页面的服务端在返回HTML文档时,直接把文档里所有的资源、接口URL提前替换为拼接好代理前缀的地址,不需要前端运行时做任何处理,页面加载时自然所有请求都走代理地址。
  • 全局网络代理:直接给浏览器、运行容器配置正向代理,所有网络请求从系统/浏览器网络层直接转发到代理服务,完全不依赖页面内的逻辑,不存在任何时机问题。

避坑提示:不要在DOMContentLoaded、load等页面事件触发后再做请求劫持,这时候页面初始化阶段的请求已经全部发出,和Service Worker的时机问题一致,无法达到提前生效的要求。

内容的提问来源于stack exchange,提问作者Eli Ozcan

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.26 10:42:50