Service Worker到底是个啥东西,具体能做什么呢?
不说那些佶屈聱牙晦涩难懂的标准回答,不懂看了还是不懂。
简单说下我的看法。
它更像一个中间层middleware,可以读取注册路径下的http请求,然后根据这些请求构造回复。
那么怎么回复的呢?
- 最普遍的,直接原封不动请求,原封不动回复。
- 网上提及最多的,符合条件的http请求,第一次原封不动请求、原封不动回复并缓存;后续命中缓存那么直接构造回复不再有网络请求。
- 对http请求按需进行处理,更改请求头、url的host等。以下是两个经典运用场景:
- jsProxy技术,懂的都懂。例如
https://a.com/xxx/https://www.google.com
- 若A域名因为某些原因被阻断,替换为B域名穿透阻挠。例如含中国mjj数量最多的某论坛。本文研究的就是这个
- jsProxy技术,懂的都懂。例如
前置
本文的实现需要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