12月20号jsDelivr发生了网络事故,jsDelivr失去了大陆ICP牌照,也就是没有大陆节点了。期间中断了大概5个小时,还好我是个人博客,不然就是P0事故了

这样的事故其实已经遇到了多次handsome主题就已经遇到多次,今天来回顾一下

  • 历史事故

公共CDN事故时间线
2017年10月15日

handsome主题增加对使用到的公共库增加了公共cdn切换的功能,但这个时候没有本地化这些资源 git记录

2018年5月17日

bootcdn 国内某些节点出现故障 故障通知

2018年10月1日

那一天bootcdn也发生的故障 故障通知

2019年2月15日

handsome主题增加对公共库本地化选项 故障通知

2021年12月20日

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 的后续更新中。

最后修改:2022 年 04 月 29 日
喜欢我的文章吗?
别忘了点赞或赞赏,让我知道创作的路上有你陪伴。