clientworker

什么是 clientworker

Before we start….

  • ClientWorker能干什么?
    • 绕备,在域名不变动的情况下,其余用户所有请求均可以定向到你的其他服务器或者cdn,而首屏域名无需ICP备案。
    • 降本,你可以用廉价的家宽+公网ipv4/ipv6,即使是80/443被封锁,你也可以在不变动端口的情况下将用户流量引向家宽。
    • 白嫖,可以用免费的公网穿透服务,接近零成本托管你的服务。
    • 加速,将静态资源流量(乃至动态资源)并发到全球cdn,实现前端级负载均衡。
    • 绕禁,通过在前端修改标头的方式,修复被故意篡改的MIME,正常托管网站,绕过各大托管商对于网站部署的限制,可以毫无负担的使用阿里云、腾讯云等对象存储而不用开启网站模式,乃至GithubRaw无限流量(绕过GithubPage 100GB限制)。
    • 愈合:通过并发方式,辅助JSDelivr、Unpkg、cdnjs等大陆几乎不可达请求重定向至其他cdn,从而实现无修改、全球加速。
    • 不宕,即使首屏服务器离线或不可达,已访问过的用户依旧可以正常命中备用服务器。
    • 缓存,颗粒化控制缓存,多种情况不同选择,智能调度缓存和请求,避免有缓存时无返回、缓存无法及时更新问题,确保缓存在客户端工作的更顺畅。
    • 离线,可以迅速支撑普通离线应用,助力快速构建PWA。
    • 兼容,Webp无缝,可以通过判断标头来判断是否支持Webp,并且自动替换图片请求,为网站加速助力。
    • 审核,通过内置的规则可以屏蔽并替换、拦截敏感词汇,实现网站内容安全。
    • 无刷,你不需要刷新就可以激活ClientWorker
    • 热更,即使源站完全宕机,你也可以更新用户手中的ClientWorker与配置,确保网站正常运行。
    • 切片,对于一个请求发起多个切片以提高单文件下载速度
    • 叠速,专门为ClientWorker开发的KFCThursdayVW50引擎能在浏览器端切片并同时并发不同的镜像服务器,对于下载大文件可以带宽叠加的效果。
    • 均衡,对多个镜像并发,选择最优的镜像服务器,保证网站的响应速度,同时达到负载均衡的目的。
    • 高度自定义…更多玩法等你挖掘

首屏加载不在ClientWorker拦截范围内。

  • ServiceWorker是一个注册在指定源和路径下的事件驱动worker。而ClientWorker是利用规则全局驱动sw的插件。
  • ClientWorker目前涵盖了ServiceWorker的路由拦截路由劫持请求/响应(标头、状态、响应主体)修改缓存调控允许用户并发(双引擎),并且有一个自定义规则系统,可以自定义规则拦截请求修改响应缓存颗粒化等功能
  • ClientWorker目前不兼容原有的ServiceWorker,请通过修改Scope绕开相互的作用域。
  • ClientWorker需要在HTTPS环境下工作,HTTP将直接安装失败
  • 我们明确一下我们的目的,首先,我们要放置好ClientWorker原始代码;然后,我们要在用户需要安装cw的位置填入ClientWorker安装代码,将其安装到用户浏览器中;最后,我们要写入想要达到目的的配置,完成ClientWorker的接入。

to-do

通过cdn引入放置clientworker

    如果你的框架为hexo的话,那么,强烈建议在根目录的source文件夹创建一个cw.js的js文件。

    至于config.yaml,下文中会提到,在cw.js中引入以下内容,

1
2
3
4
/*
* 3.0.0-beta-3
*/
importScripts("https://lib.baomitu.com/clientworker/3.0.0-beta-3/dist/cw.js");

:因为cw.js是在根目录的source文件夹创立的,所以它不需要引入。

写入配置

    同样的,在根目录的source文件夹内创建一个config.yaml,为cw配置文件。
    我们最初先安装最简单的就行,后期会逐渐加大代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
name: ClientWorker 

catch_rules:
- rule: _
transform_rules:
- search: \#.+
searchin: url
replace: ''
- search: _
action: fetch
fetch:
engine: fetch
- search: (^4|^5)
searchin: status
action: return
return:
body: The GateWay is down!This Page is provided by ClientWorker!
status: 503

注意!Hexo会将yaml解析成json格式,这样的话,我们的cw也就失效了,如何避免这样的问题呢?
当然是解铃人还须系铃人了呀!
找到hexo的配置文件_config.yaml,找到skip_render,将我们的config.yaml放进去,防止hexo在generate的时候把yaml解析成json。

1
2
skip_render:
- 'config.yaml'

配置安装代码

    你有三种方式接入: 三文件全域安装自定义无刷新安装自定义刷新安装

    其中,

  • 全域安装,最简单,对SEO支持也最恶劣(Google会提示额外的计算开销,而百度完全没办法爬取)。比较适用于自用的、只追求速度的。
  • 自定义无刷新安装。则对你的HTML和JS水平有所要求,对于部分不遵守标准的浏览器兼容性较差,但是这种方法对SEO没有影响,比较适合于对seo注重的网站。
  • 自定义刷新安装对。seo略有影响,会在载入后阻断未经CW的请求并刷新一次,以便于CW及时托管,比较适合于网站提速

    因为我采用的第二种,所以这里讲解第二种说法。

自定义无刷新安装

    这种方式有一个重载的动作,即在无刷新的情况下将当前页面重新获取并填充。这可能会出现意外的兼容性错误,请慎行。 如果你不需要重载,请将下方重载标识框内的代码删除。不重载的后果就是用户首屏的大部分请求无法被CW拦截。如果你希望用户首屏进入就被托管,请使用自定义刷新安装。

    用户每次访问时都应该能运行接下来的脚本,如果你使用hexo等其他博客系统,可以在body或footer模板中添加这一段。我们强烈建议将这段代码加入在<head>标签中,越靠前越好,navigator.serviceWorker.register是异步函数不会阻塞页面加载。

    请不要使用window.stop(),这会导致重载失效
    修改网页的模板,添加一段html。如果是hexo用户,可以将这段代码通过引入的方式来引入博客。

    在根目录的source文件夹创建一个assets文件夹,再在这个文件夹内创建一个cw-worker.js文件,填入以下内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* install cw */
<script>
if (!!navigator.serviceWorker) {
navigator.serviceWorker.register('/cw.js?t=' + new Date().getTime()).then(async (registration) => {
if (localStorage.getItem('cw_installed') !== 'true') {
const conf = () => {
console.log('[CW] Installing Success,Configuring...');
fetch('/cw-cgi/api?type=config')
.then(res => res.text())
.then(text => {
if (text === 'ok') {
console.log('[CW] Installing Success,Configuring Success,Starting...');
localStorage.setItem('cw_installed', 'true');
//如果你不希望重载页面,请移除下面七行
//重载标识 - 开始
fetch(window.location.href).then(res => res.text()).then(text => {
document.open()
document.write(text);
document.close();
});
//重载标识 - 结束
} else {
console.warn('[CW] Installing Success,Configuring Failed,Sleeping 200ms...');
setTimeout(() => {
conf()
}, 200);
}
}).catch(err => {
console.log('[CW] Installing Success,Configuring Error,Exiting...');
});
}
setTimeout(() => {
conf()
}, 50);
}
}).catch(err => {
console.error('[CW] Installing Failed,Error: ' + err.message);
});
} else { console.error('[CW] Installing Failed,Error: Browser not support service worker'); }
</script>

:因为cw-worker.js是在根目录的source文件夹的assets文件夹创立的,它将最为第三方文件被引入。但由于它是执行clientworker的文件,所以直接在_config.anzhiyu.yml引入的话,它的渲染之后的位置就很靠后,就会适得其反。所以,这里有一个办法:在根目录创建scripts文件夹,在这个文件夹内再创建一个cw.js文件,内容如下:

1
2
3
4
5
6
7
8
9
/**
* cw.js
*/

'use strict';

const { filter } = hexo.extend;
const js = hexo.extend.helper.get('js').bind(hexo);
hexo.extend.injector.register('head_begin', () => { return js('/assets/cw-worker.js'); });

    记得加上注释,以便于以后增加代码量的时候知道它是干什么的。

    接下来,就可以部署啦!然后打开你的博客,打开f12,享受cw带来的愉悦的浏览体验吧!

扩展

CW前端竞速

    如下图,增加首屏cw安装动画的功能。

cw-racing

    同样的,在cw-worker.js文件,填入以下内容,加在上述install cw注释的上面:
    cw-worker.js文件的执行顺序是前端竞速 -> install cw

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
/* cw-racing */
(async () => {
if ('serviceWorker' in navigator) {
if (Number(window.localStorage.getItem('CiraosBlogHelper_Set')) < 1) {
setTimeout(async () => {
console.log('检测到您的浏览器没有安装CiraosBlogHelper_Set,开始注册')
window.stop()
window.localStorage.setItem('CiraosBlogHelper_Set', 1)
const replacehtml = await fetch('https://npm.elemecdn.com/chenyfan-blog@1.0.13/public/notice.html')
document.body.innerHTML = await replacehtml.text()
$('#info').innerText = '尝试安装CiraosBlogHelper...';
}, 0);
}
const $ = document.querySelector.bind(document);
navigator.serviceWorker.register(`/cw.js?time=${new Date().getTime()}`)
.then(async () => {
if (Number(window.localStorage.getItem('CiraosBlogHelper_Set')) < 2) {
setTimeout(() => {
$('#info').innerText = '安装成功,稍等片刻...';
}, 0);
setTimeout(() => {
window.localStorage.setItem('CiraosBlogHelper_Set', 2)
console.log('准备跳转')
window.location.reload()
}, 500)
}
})
.catch(err => console.error(`CiraosBlogHelper_Set:${err}`))
} else {
setTimeout(() => {
$('#info').innerText = '很抱歉,我们已不再支持您的浏览器.';
}, 0);
}
})()

cw热更新

    ClientWorker支持从外部加载和更新ClientWorker及其配置,避免在源站不可达时两者无法更新。热更新与默认更新方式不同,热更新允许你从除了源站的任何地方获取更新。

    同样的,这一段代码加在cw-worker.js文件的最后面,
    此时,cw-worker.js文件的执行顺序是前端竞速 -> install cw -> 热更新代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
/* cw-autoupdate */
; (async (updateSWDelay, updateConfigDelay) => {
const LSDB = {
read: (key) => {
return localStorage.getItem(key);
},
write: (key, value) => {
localStorage.setItem(key, value);
}
}
async function updateSW() {
if (navigator.serviceWorker) {
navigator.serviceWorker.getRegistrations().then(async registrations => {
for (let registration of registrations) {
await registration.unregister();
}
console.log(`Unregistered service workers`);
}).then(() => {
//register new service worker in /cw.js
navigator.serviceWorker.register('/cw.js').then(async registration => {
console.log(`Registered service worker`);
await registration.update();
LSDB.write('cw_time_sw', new Date().getTime());
})
})
}
};
async function updateConfig() {
await fetch('/cw-cgi/api?type=config').then(res => res.text()).then(res => {
if (res === 'ok') {
console.log(`Config updated`);
LSDB.write('cw_time_config', new Date().getTime());
} else {
console.log(`Config update failed`);
}
})
}
if (Number(LSDB.read('cw_time_sw')) < new Date().getTime() - updateSWDelay) {
await updateSW();
await updateConfig();
}
if (Number(LSDB.read('cw_time_config')) < new Date().getTime() - updateConfigDelay) {
await updateConfig();
}
setInterval(async () => {
await updateSW();
await updateConfig();
}, updateSWDelay);
setInterval(async () => {
await updateConfig()
}, updateConfigDelay);
})(1000 * 60 * 60 * 12, 1000 * 60);

config.yaml

有待挖掘,未完待续... ...