Mayx的博客

Logo

Mayx's Home Page

 

About me

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

用Service Worker实现一个反向代理

by mayx


AI摘要

正在生成中……



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

起因

前段时间在和群友聊天的时候,提到了我博客的分发方案,这么多年过去之后我已经在很多平台上分发了我的博客,不过这只是多重冗余,并不算去中心化(虽然我也有向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的复古静态网站托管平台,比如NeocitiesNekoweb。它们需要通过网页或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...


召唤伊斯特瓦尔