用Service Worker实现一个反向代理 | Mayx的博客
Author: mayx
Description: 现代浏览器真是强大，可以替代一些服务器的功能了！
Date: 2025-08-01T00:00:00+08:00

1 August 2025 - 字数统计：4085 - 阅读大约需要14分钟 - Hits: Loading...

# 用Service Worker实现一个反向代理

by mayx

这是一篇创建于 天前的文章，其中的信息可能已经有所发展或是发生改变。

AI摘要

这篇文章介绍了作者如何利用Service Worker在现代浏览器中实现一个简单的反向代理功能，以提供博客的备份和离线访问。作者原本希望通过Service Worker在用户浏览器中运行一个Web服务器来存储博客副本，但发现 tar.gz 文件的处理需要第三方库且难以找到适用的解决方案，尤其是对于tar文件的处理。作者最终选择使用Service Worker作为反向代理，将请求转发到其他网站，如GeoCities风格的静态网站托管平台，实现了类似的效果。这个过程中，作者体验到了浏览器功能的强大，并认为Service Worker在离线场景中有更大的潜力，尽管在他的例子中并没有充分展示这种优势。

- 起因
- 研究实现方案
- 使用Service Worker进行反向代理
- 感想

现代浏览器真是强大，可以替代一些服务器的功能了！

#
起因

前段时间在和群友聊天的时候，提到了我博客的分发方案，这么多年过去之后我已经在很多平台上分发了我的博客，不过这只是多重冗余，并不算去中心化（虽然我也有向IPFS同步，不过IPFS还得pin，也不太可靠）……所以这么看来，我的博客似乎还不算极其可靠😂？但其实不完全是这样。因为除了向不同平台的分发，我的博客还有一个全文搜索的功能。更重要的是，之前做文章推荐功能时，会把整个博客所有文章的文字存到访客浏览器的localStorage中。这么说来，只要有人访问了我博客的文章，他们的浏览器中就会保存一份我博客文章的完整文本副本。从这个角度看，可靠性应该算是相当高了吧？

不过我之前的分发方案里还记录了一点，在GitHub Pages以外的平台我还打包了一份全站生成后的代码，之所以要全站打包，也是希望我的博客能尽可能的分发，考虑到几乎所有的Linux发行版一定有tar，而不一定有zip，所以我最终打包成了tgz格式。如果能让访客下载这个全站打包好的副本，相比于浏览器里只存储了文章文字的全文数据，这应该是一个更好的备份方式吧？毕竟我的博客本身也是我的作品……所以这个压缩包到底有什么地方可以用到呢？

这时候我想起来，现代的浏览器功能已经非常强大了，甚至在浏览器里直接运行一个Web服务器也完全没问题。如果能让访客在浏览器里下载那个压缩包并运行一个Web服务器，那就相当于在他们本地设备上部署了一份我的博客副本。这样一来，除了我自己搭建的网站之外，这些访客的本地也运行着一个我的博客实例😆（当然，这份副本只有访客自己能看到）。

#
研究实现方案

想要在浏览器上运行Web服务器其实很简单，那就是使用Service Worker，它可以完全离线在浏览器上工作。格式的话和以前写过的Cloudflare Worker非常相似，毕竟Cloudflare Worker就是模仿Service Worker的方式运行啊😂，所以我要是想写Service Worker应该很简单。

有了执行的东西之后就是存储，在Service Worker上存储可以用Cache Storage，用它的话不仅可以保存文件的内容，还可以保存响应头之类的东西，用来和Service Worker配合使用非常的方便，不过既然是Cache，它的可靠性就不能保证了，浏览器很可能在需要的时候清除缓存内容，所以相比之下用IndexedDB应该会更可靠一些。

那么接下来就该处理我的tgz文件了，tgz的本质是tar文件被gzip压缩之后的东西。浏览器解压gzip倒是简单，可以用Compression Stream API，但它也只能处理gzip了……对于tar的处理似乎就必须用第三方库。而tar的库在网上搜了搜似乎很少，网上找了个tarjs库，文档写的也看不懂，⭐️也很少，看来是有这个需求的人很少啊，而且还要用现代JS那种开发方式，要用什么npm之类的。在上一篇文章我就说过我不是专门写前端的，对在自己电脑上安装Node.js之类的东西很反感。后来问AI也完全写不出能用的代码，估计这个功能还是太小众了……另外又想到除了这个问题之外还要处理网站更新的时候该怎么通知Service Worker之类乱七八糟的事情……所以只好作罢😅。

#
使用Service Worker进行反向代理

这么看来离线运行我的博客似乎有点麻烦，不过既然都研究了一下Service Worker，不如想想其他能做的事情……比如当作反向代理？虽然在浏览器上搞反向代理好像意义不是很大……但值得一试。我之前见过一个项目叫做jsproxy，它是用Service Worker实现的正向代理，这给了我一些启发。我在之前研究分发方案的时候发现了一些模仿GeoCities的复古静态网站托管平台，比如Neocities和Nekoweb。它们需要通过网页或API才能上传网站，不太方便使用CI/CD的方式部署。但是我又觉得它们的社区很有意思，所以想用Service Worker的方式反代到我的网站，显得我的网站是部署在它们上面一样。

这个做起来非常简单，其实就和我以前用Cloudflare Worker搭建反代几乎完全一样，遇到请求之后直接通过Fetch获取内容然后再返回就行，唯一不同的就是浏览器存在跨域策略，在跨域时只有对应网站存在合适的响应头才可以成功请求，还好我用的Pages服务大多都允许跨域。但是在我实际测试的时候发现这个允许跨域的等级不太一样，比如GitHub Pages的响应头里包含`Access-Control-Allow-Origin: *`，但是不允许OPTIONS方式请求，另外如果要修改请求头，在响应头里还要一一允许相应的请求头才行……当然对于这种问题解决起来很简单，就和我之前写的订阅源预览一样，用cloudflare-cors-anywhere搭建的CORS代理就可以，有了这个就可以轻松使用Service Worker反代其他网站了。

当然对我来说其实有`Access-Control-Allow-Origin: *`就够了，我也不需要花里胡哨的请求方式，也不需要在请求头和请求体里加什么莫名其妙的东西，所以对我来说直接请求我的某一个镜像站就可以，于是代码如下：

index.html

    <!DOCTYPE html>
    <html>

    <head>
        <meta charset="UTF-8" />
        <title>Mayx的博客</title>
    </head>

    <body>
        <script>
            // 注册 Service Worker
            if ('serviceWorker' in navigator) {
                navigator.serviceWorker.register('/sw.js')
                    .then(registration => {
                        console.log('Service Worker 注册成功:', registration.scope);
                        // 刷新网页
                        location.reload();
                    })
                    .catch(error => {
                        console.error('Service Worker 注册失败:', error);
                        location="https://mabbs.github.io";
                    });
            } else {
                location="https://mabbs.github.io";
            }
        </script>
        <h1>Redirecting&hellip;</h1>
        <a href="https://mabbs.github.io">Click here if you are not redirected.</a>
    </body>

    </html>

sw.js

    const TARGET_SITE = '被反代的网站'; //也可以用CORS代理

    self.addEventListener('install', event => {
        // 强制立即激活新 Service Worker
        event.waitUntil(self.skipWaiting());
    });

    self.addEventListener('activate', event => {
        // 立即控制所有客户端
        event.waitUntil(self.clients.claim());
    });

    self.addEventListener('fetch', event => {
        if (new URL(event.request.url).origin == self.location.origin) {
            event.respondWith(handleProxyRequest(event.request));
        }
    });

    async function handleProxyRequest(request) {
        try {
            // 构建目标 URL
            const targetUrl = new URL(request.url);
            const proxyUrl = TARGET_SITE + targetUrl.pathname + targetUrl.search;

            // 创建新请求（复制原请求属性）
            const proxyRequest = new Request(proxyUrl, {
                method: request.method,
                // headers: request.headers,
                // body: request.body
            });

            // 发送代理请求
            const response = await fetch(proxyRequest);

            // 返回修改后的响应
            return new Response(response.body, {
                status: response.status,
                statusText: response.statusText,
                headers: response.headers
            });

        } catch (error) {
            console.error('Proxy error:', error);
            return new Response('Proxy failed', { status: 500 });
        }
    }

最终的实际效果： https://mayx.nekoweb.org

#
感想

虽然折腾了半天没能增强我博客的可靠性……但是体会到了现代浏览器的强大之处，难怪前几年会提出ChromeOS和PWA之类的东西，原来浏览器功能还是相当强大的，用了Service Worker以后即使是纯前端也可以有和使用服务器一样的体验，在过去的浏览器中要是想实现这样的功能……好像也不是不可能😂，用AJAX加服务器使用伪静态策略其实是可以做到的……其实Service Worker的功能更多还是在离线时使用的，我这个例子好像没体现它的优势😆。

但总的来说相比以前想要实现这种反代的功能代码还是更清晰，也更简单了，也许以后如果有机会我又有心思让博客在访客浏览器上离线运行，那就可以体现Service Worker真正的优势了🤣。

tags: 浏览器 - Service Worker - Worker - 反向代理 查看原始文件

#### 推荐文章

Loading...

上一篇：使用Cloudflare制作自动更新的网站预览图
下一篇：在Tilde社区的游玩体验
想问这篇文章
召唤伊斯特瓦尔