12月20号jsDelivr发生了网络事故,jsDelivr失去了大陆ICP牌照,也就是没有大陆节点了。期间中断了大概5个小时,还好我是个人博客,不然就是P0事故了
这样的事故其实已经遇到了多次handsome主题就已经遇到多次,今天来回顾一下
- 历史事故
handsome主题增加对使用到的公共库增加了公共cdn切换的功能,但这个时候没有本地化这些资源
bootcdn 国内某些节点出现故障
那一天bootcdn也发生的故障
handsome主题增加对公共库本地化选项
jsDelivr公共CDN 发生故障
虽然本地化是一个解决方法,但是无奈没有带宽呐。
现在jsDelivr已经恢复了,该用还是还是要用上,但是有没有一个办法能让它在出现问题的时候自动切换到本地呢?
- Service worker 方案
在 jsDelivr 的 china network error 的 issue 里面,看到有人推荐了这个项目:
GitHub - EtherDream/freecdn: A front-end CDN based on ServiceWorker
看了一眼,咦service worker,我不是一直在用的吗,但是这个项目太过庞大,我大致看了一下基本原理就是通过监听fetch event,拦截request,在里面判断当前请求的response是否成功,如果失败,则替换成另一个url接着请求。这样看起来很简单了,但实际上在判断请求是否请求成功这个地方没有那么简单。
一年之前写过了一篇service worker实践的文章 service work 关于更新用户本地缓存的方案
现在在回过来来读发现有两个问题:
- 首次安装sw的时候,同样会触发controllerchange,导致用户第一次访问博客就会提示「页面缓存更新建议刷新页面」,这个并不合适
- sw中fetch的请求,如果访问失败了,只要在cachelist 白名单中就会被缓存,而没有关系请求是否成功(比如404错误),这样会导致即使这个请求后续成功了,因为被缓存了错误的response导致一直显示错误(除非手动更新sw版本号)
第一个问题这里不再详细展开了,其实可以在regsiter的时候判断sw的状态是否是reg.active
,如果是说明之前已经安装好sw了,此时如果再出现controllerchange,那么才是需要出现提示框提示用户刷新页面。
第二个问题,判断一个请求是否成功,一开始想着是response.ok 这个返回值是否是true或者是response.status 判断是否是200。但是发现对于非本站点的请求,response.ok 一直是false,response.status 值为0。
后来才发现对于跨域请求的站点,由于安全隐私性,Google不会返回真正状态信息,这叫做opaque filtered response 不透明过滤响应,感兴趣可以戳这里。
这个问题目前来看没什么好办法(如果有好的方法欢迎在评论区告诉我),除非在fetch里面传入{"mode":"cors"}
表示我们以严格跨域方式进行请求,这个时候目标站点响应的header里面的Access-Control-Allow-Origin
不能是通配符*
,而需要指定包含我们的请求方的域名,否则浏览器会提示下面的错误:
The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
目前改进后的回退逻辑如下,response.status为0,则认为是加载成功了。
return fetch(event.request).then(function(response) {
return caches.open(CACHE_NAME).then(function(cache) {
if (response.status == 0 || response.ok) { //对于响应码为0,暂时无法进一步判断,只能全部认为加载成功
cache.put(event.request, response.clone());
} else {
const new_response = fetchLocal(event);
if (new_response) {
return new_response;
} else {
return response;
}
}
return response;
});
}).
catch(function(error) {
const response = fetchLocal(event);;
if (response) {
return response;
} else {
// console.log('Fetching request url ,' +event.request.url+' failed:', error);
// throw error;
}
});
其实一个请求失败分为两种,一种是服务器返回错误的状态码,比如404,502之类,另一种是服务器直接宕机。
第二种请求失败的情况是可以在try 语句的catch中捕获到了。
目前,我的博客以及部署了新的 service worker,如果 jsDelivr 宕机事故,sw 则会继续进行回退请求我服务器上的资源来完成降级的过程,该逻辑也会被包含在在 handsome 的后续更新中。
8 条评论
P0事故的话博主要祭天了
如果我没记错的话是ICP吧
修改了
我也看到这个项目了,真的是玩出花了。
我记得没错的话这个作者也开发了JSproxy的,属实是把service workers玩透了
那天公司的生产项目也出现了一样问题,临时换了个cdn
以jQuery为例,在jQuery下面判断是否实例化jQuey,如果没有实例化则加载失败,使用document.write把主题本地的jQuery输出出来加载。如果主题JS很多的话可以写个方法批处理一下。
是的,这样也可以,但是比较局限性,我更多的是jddelivr的图片加载问题,公共库实际上还好,因为可替换的选择很多