NiceLeeのBlog 用爱发电 bilibili~

关于Service Worker拦截并替换域名的入门研究与简单实现

2025-07-24
nIceLee

阅读:


Service Worker到底是个啥东西,具体能做什么呢?
不说那些佶屈聱牙晦涩难懂的标准回答,不懂看了还是不懂。
简单说下我的看法。
它更像一个中间层middleware,可以读取注册路径下的http请求,然后根据这些请求构造回复。
那么怎么回复的呢?

  • 最普遍的,直接原封不动请求,原封不动回复。
  • 网上提及最多的,符合条件的http请求,第一次原封不动请求、原封不动回复并缓存;后续命中缓存那么直接构造回复不再有网络请求。
  • 对http请求按需进行处理,更改请求头、url的host等。以下是两个经典运用场景:
    • jsProxy技术,懂的都懂。例如 https://a.com/xxx/https://www.google.com
    • 若A域名因为某些原因被阻断,替换为B域名穿透阻挠。例如含中国mjj数量最多的某论坛。本文研究的就是这个

前置

本文的实现需要A、B两个域名的实际控制权。
其中A域名是浏览器正常输入的域名,B域名是Service Worker工作时,浏览器实际访问的域名。

目标

用户在访问A域名注册激活service worker以后,所有访问A域名的HTTP请求实际上是走的B域名。

实现

  • network.html
<!DOCTYPE html>
<html>
<head>
  <title>Service Worker Example</title>
</head>
<body>
  <h1>Service Worker Example</h1>
  <script>
    if ('serviceWorker' in navigator) {
      navigator.serviceWorker.register('./worker.js')
        .then(registration => {
          console.log('Service Worker registered with scope:', registration.scope);
        })
        .catch(error => {
          console.error('Service Worker registration failed:', error);
        });
    }
  </script>
</body>
</html>
  • worker.js
const ORIGIN_SITE = "a.com";
const ALT_SITE = "b.com";
self.addEventListener("install", (event) => {
  event.waitUntil(self.skipWaiting());
});

self.addEventListener("activate", (event) => {
  event.waitUntil(self.clients.claim());
});

self.addEventListener("fetch", (event) => {
  const url = new URL(event.request.url);

  // 拦截 a.com 的请求
  if (url.hostname === ORIGIN_SITE) {
    // 构建新的 URL
    const newUrl = new URL(event.request.url.replace(ORIGIN_SITE, ALT_SITE));
    console.log(newUrl);
    // 处理请求
    event.respondWith(
      (async () => {
        try {
          const headers = new Headers(event.request.headers);
          headers.set("host", ALT_SITE);
          headers.delete("upgrade-insecure-requests");
          const newRequest = new Request(newUrl, {
            method: "GET",
            headers: headers,
            body: event.request.body, // 如果是 POST 请求,需要传递 body
            credentials: "include", // 避免跨域问题,如果b.com需要凭证,需要根据实际情况调整 omit
            mode: "cors", // 根据b.com服务器的CORS配置调整
            cache: "default", // 根据需要调整缓存策略
            redirect: "follow", // 根据需要调整重定向策略
          });
          // 发起新的请求
          const response = await fetch(newRequest);
          console.log(response);
          // 检查响应状态码
          if (!response.ok) {
            console.error("Fetch failed with status:", response.status);
            // 读取响应文本以获取更多信息
            const responseText = await response.text();
            console.error("Response Text:", responseText);
          }
          // 如果响应成功,直接返回
          return response;
        } catch (error) {
          console.error("Service Worker 拦截请求失败:", error);
          // 如果请求失败,返回一个错误响应,或者尝试 fallback
          return new Response("Network error happened", {
            status: 500,
            headers: { "Content-Type": "text/plain" },
          });
        }
      })()
    );
  }
});

扩展

一般来说,我们不应将配置写死在静态文件里,这样很容易暴露。
而且在测试的时候发觉serviceWorker还是有蛮多坑的。
Talk is cheap, show me the code.
https://github.com/nICEnnnnnnnLee/ns-service-worker


相似文章

内容
隐藏