一、简介
freecdn 是一个纯前端的 CDN 解决方案,用于降低网站流量成本,同时提高网站稳定性、安全性,并且无需修改现有的业务逻辑。
互联网上有很多免费的公共库 CDN,例如 cdnjs
、jsdelivr
、unpkg
,但哪个最稳定,始终没有明确的答案。
有些国外的 CDN 虽然有大公司支持,但在国内无法确保网络稳定性;有些国内的 CDN 虽然网络稳定,但无法确保未来是否仍在维护。
这些 CDN 一旦出现问题,轻则网站打开变慢,重则功能损坏,经济损失甚至超过节省的费用。因此,不少人认为免费的才是最贵的,最终选择自己购买 CDN。
任何一个东西,既要有超低成本,又要有超高稳定性,显然是很难做到的。但是,既然成本很低,那不妨多准备几个,组成可容错的冗余系统,这样整体稳定性就变高了。
假设单个免费 CDN 的稳定性只有 90%
,但事先准备 10 个,这时整体稳定性可达 99.99999999%
。
如何实现这样的冗余系统?这就是 freecdn 要做的事!
FreeCDN 开源地址:https://github.com/EtherDream/freecdn
1.1 实现原理
freecdn 的原理并不复杂,其核心使用了 HTML5 中一个重要的 API —— Service Worker。它是一种浏览器后台服务,能拦截当前站点产生的 HTTP 请求,并能控制返回结果,相当于给网站加了一层反向代理。有了这个黑科技,我们可以把传统 CDN 的功能搬到前端,例如负载均衡、故障切换等,通过 JS 灵活处理各种请求。
同时,网站开发者提供一个清单,记录用到的公共库以及备用 URL。
当 Service Worker 加载公共库出现问题时,可以不返回错误给上层页面,而是继续加载备用 URL,直到获得正确结果才返回。
因此,只要有一个备用节点正常,资源就不会加载失败。稳定性大幅提高。
甚至,你可以选择 更激进的加载策略 —— 同时加载多个备用 URL,哪个先完成就用哪个,实现带宽换时间的效果!
对于常见的公共库,你无需自己收集备用 URL,通过工具可自动生成清单。
二、接入FreeCDN流程
2.1 引入前端脚本
-
安装 freecdn 命令行工具
npm install freecdn -g
-
创建前端脚本,将在项目中生成
freecdn-loader.min.js
和freecdn-internal
目录,需要将生成的这些文件放在网站的根目录下freecdn js --make
-
在网页的
head
中引用。为了让用户访问任意界面都可以安装FreeCDN
,应该在每个界面都引入,且位置越靠前越好。<head> <script src="/freecdn-loader.min.js"></script> ...
至此,博客中已经成功接入了 FreeCDN
的相关前端脚本,但是这样还不够,还需生成一个指定哪些文件有哪些访问路径的 freecdn-manifest.txt
清单文件,生成并将这个文件放在网站根目录就可以使用 FreeCDN
了。
2.2 最简方式配置
-
生成清单文件
freecdn find --save
得到清单文件
freecdn-manifest.txt
,内容大致如下:/assets/chrome.png https://ajax.cdnjs.com/ajax/libs/browser-logos/27.0.0/chrome/chrome_512x512.png https://cdnjs.cloudflare.com/ajax/libs/browser-logos/27.0.0/chrome/chrome_512x512.png hash=qRgUkISo5k/bgIWNHGfLsC8WmasnE7jYdCZvthIFLno= /assets/edge.png https://ajax.cdnjs.com/ajax/libs/browser-logos/62.1.4/edge/edge_512x512.png https://cdnjs.cloudflare.com/ajax/libs/browser-logos/62.1.4/edge/edge_512x512.png hash=e/QvJoMnfQFLXXrHe6ZC7v8IVc6cNuL7MqTY4h/L4ZQ= ...
格式很简单,每个原始文件对应多个备用文件,以及相应的参数。
生成清单文件后,将该文件一同放在网站根目录,这时访问即可看到网站已经被成功代理,效果图如下:
2.3 加速自己的脚本文件
生成清单文件后可以看到大部分文件都不会出现在清单文件中,这是源于有些文件是我们自己编写的,在开源库中没有对应的 CDN 可以访问,还有就是 FreeCDN
的公共 CDN 库不是很齐全,一些不是很知名的开源包也会出现没找到 CDN 的情况。
这种情况可以将我们的脚本上传到 Github
因为GitHub 提供了 raw.githubusercontent.com
站点,可通过 HTTP 访问仓库中的文件,此外,还有一些第三方 CDN 也可以加速 GitHub 文件,例如 jsdelivr
。
由于 CDN 缓存时间很长,因此每次更新必须设置 tag,例如 0.0.1、0.0.2 递增,从而可使用不同的 URL 避开缓存。
但是自己上传的脚本文件是没办法在 FreeCDN
的公共 CDN 库中找到的,所以我们需要创建一个记录这些文件的 hash 和 url 的文件,然后将它导入到 FreeCDN
提供的本地数据库。
本文提供两种生成 hash 和 url 列表文件的方法:
通过 sh 脚本生成 hash-url.txt 文件
#!/bin/bash
USER=zjcqoo
REPO=test
VER=0.0.1
files=$(find * -type f ! -path "freecdn-*" ! -name ".*")
list=""
for file in $files; do
hash=$(openssl dgst -sha256 -binary $file | openssl base64 -A)
list="$list
$hash https://raw.githubusercontent.com/$USER/$REPO/$VER/$file
$hash https://cdn.jsdelivr.net/gh/$USER/$REPO@$VER/$file"
done
echo "$list" > hash-url.txt
通过 gulp
生成 hash-url.txt 文件
const fs = require("fs");
const crypto=require('crypto');
const version = '0.0.1';
task("freecdn", (done) => {
if (version == null) {
console.log(`[FreeCDN] No '--version' parameters are specified`)
done();
return;
}
const tempFileName = 'hash-url.txt'
const readFile = (dir, ignoreFiles) => {
let files = fs.readdirSync(dir, "utf-8");
files.forEach((file, i) => {
let filePath = dir + "/" + file
let states = fs.statSync(filePath);
if (ignoreFiles.length !== 0 && ignoreFiles.includes(filePath)) {
return;
}
if (states.isDirectory()) {
readFile(filePath, ignoreFiles)
} else {
let hash = crypto.createHash('SHA256').update(fs. readFileSync(filePath)).digest('base64')
fs.appendFileSync(tempFileName, `${hash} https://raw.githubusercontent.com/nineya/halo-theme-dream/${version}/${filePath}\n`)
fs.appendFileSync(tempFileName, `${hash} https://cdn.jsdelivr.net/gh/nineya/halo-theme-dream@${version}/${filePath}\n`)
}
})
}
readFile("./", ["./freecdn-internal", "./freecdn-loader.min.js"])
done();
});
通以上步骤我们得到了一个 hash 和 url 的列表文件 hash-url.txt
,文件内容大致如下:
XTZpbfuMkxViYSW2q370udBiH6h2xPPsbA9GLxfBfBg= https://raw.githubusercontent.com/nineya/halo-theme-dream/0.0.1/assets/video/test.mp4
XTZpbfuMkxViYSW2q370udBiH6h2xPPsbA9GLxfBfBg= https://cdn.jsdelivr.net/gh/nineya/halo-theme-dream@0.0.1/assets/video/test.mp4
L4HFRlSKt3ggTXAKWFWe5OQCyXsn+1ZWBIGdGE2g/MQ= https://raw.githubusercontent.com/nineya/halo-theme-dream/0.0.1/assets/css/main.css
L4HFRlSKt3ggTXAKWFWe5OQCyXsn+1ZWBIGdGE2g/MQ= https://cdn.jsdelivr.net/gh/nineya/halo-theme-dream@0.0.1/assets/css/main.css
将文件导入到 FreeCDN
的数据库
freecdn db --import < hash-url.txt
查看本地数据库,确定已经有了文件映射的内容
freecdn db --list
更多关于数据库内容维护的命令可查看帮助
freecdn db --help
确定 hash-url.txt
文件成功导入到数据库后,创建清单文件:
freecdn find --save
这时候再看清单文件已经有了我们自己写的脚本文件的相关映射了。
hash-url.txt
文件仅用于导入脚本文件到本地数据库,导入完成后即可删除。
2.4 加速外部资源
以上的方式加速的全部都是本地的资源文件,但是我们网站中可能引用了很多外部 CDN 的资源,我们也希望这些资源的访问能够被加速,或者说能够在网站失效时自动切换站点。
我们需要手动创建一个 urls.txt
文件,将这些外部资源的路径记录在这个文件中,并在创建清单文件时指定。这样,在创建清单文件时将会去访问这些文件,然后获取这些文件的 sha256
摘要,一同写入到清单文件中。
访问远程的文件也导致了创建清单文件的速度降低,还是建议将这些远程文件都下载到本地。
urls.txt
文件中一行一个资源路径,内容模板如下:
https://ajax.cdnjs.com/ajax/libs/jquery/3.2.1/jquery.js
https://ajax.cdnjs.com/ajax/libs/twitter-bootstrap/4.6.0/css/bootstrap.min.css
https://ajax.cdnjs.com/ajax/libs/twitter-bootstrap/4.6.0/js/bootstrap.min.js
准备好 urls.txt
文件后,创建清单文件,通过 --with-urls
参数指定 urls.txt
文件
freecdn find --save --with-urls urls.txt
urls.txt
文件仅用于生成清单文件,不需要将文件加入到站点根目录下。
2.5 其他
除了以上介绍的功能, FreeCDN
还具有 并行加载文件
等功能,本文不再介绍,可前往 FreeCDN
中查看相关的教程。
https://github.com/EtherDream/freecdn