前情提要
Github 的下载一直都非常令人诟病,虽然这锅并不能让 Github 或者微软来背,但总体来说对国内的用户还是非常不友好的。
jsdelivr 有一个对 Github 的 CDN 加速,但尝试了半天似乎并不能够直接下载 Release 里附带的文件(assets),只能下载仓库里的代码文件。虽然说把二进制文件 push 到仓库里也可以曲线救国,但 push 的过程如果没有魔法的话,也着实令人觉得有些痛苦。
CloudFlare Workers
CloudFlare Workers 顾名思义,是另一个 CDN 服务商 CloudFlare 提供的一个可以部署无服务器的 JavaScript 应用的服务。
但我估计这项服务的本来用意,应当是利用边缘服务器的计算能力来降低源服务器负载的。将一些普通而简单的处理直接在边缘服务器完成,也可以提高响应速度。
说正题
这次要说的就是利用 Workers 的功能来对 Github 的文件进行转发,虽然在某些地区 CloudFlare 的网速也是拉跨,但对于移动用户来说还是比较福音的。而且目前每天的免费配额是 10w 次每天,非常富裕了。
总体来说就以下几步:
- 根据访问链接获取参数
- 根据参数获取目标文件链接
- 下载(缓存)并转发
那么一步步来。
1. 根据访问链接获取参数
首先创建一个 Worker 后,会给出以下默认代码。
1 2 3 4 5 6 7 8 9 10 11
| addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) })
async function handleRequest(request) { return new Response('hello world', {status: 200}) }
|
其中 handleRequest 就是我们用于处理请求的函数了,接下来我们的工作就是对这个函数进行修改。
首先我想要最终用于加速的 URL 链接和 jsdelivr 类似,应当形如 https://domain.com/gh/JustArchiNET/ArchiSteamFarm/ASF-generic.zip
一样(此处以 ASF 的下载链接为例)。
格式大约为 https://<domain>/gh/<user>/<repo>[/tag]/<file>
。
其中 /tag
部分为可选,如果有就是下载指定 tag 下的文件,没有就是从最新的 Release 中下载文件。
那么把链接以 /
分割开来,去掉域名用的那个之后,剩下都是我们需要的参数了。
然后再请求 Github 的 WebAPI,就可以顺利获取文件的下载地址了。
所以对函数稍作修改如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| async function handleRequest(request) { var pathname = (new URL(request.url)).pathname.split('/'); if (pathname[1] == 'gh') { var jsonurl, targetfile; if (pathname.length == 5) { jsonurl = `https://api.github.com/repos/${pathname[2]}/${pathname[3]}/releases/latest`; targetfile = pathname[4]; } else if (pathname.length == 6) { jsonurl = `https://api.github.com/repos/${pathname[2]}/${pathname[3]}/releases/tags/${pathname[4]}`; targetfile = pathname[5]; } } return new Response('hello world', {status: 200}); }
|
这样就可以处理完所有 URL 参数了,不过因为没有访问接口,所以并没有获取到文件链接。
那么接下来要做的是第二步。
2. 根据参数获取目标文件链接
已经根据参数拼接好 API 地址之后,接下来就是从 API 获取文件链接了。
不过一开始写的时候由于对 fetch 函数不熟悉浪费了不少时间。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
| var jdata = await fetch(jsonurl, {headers: {'User-Agent': 'CloudFlare-Workers'}}); jdata = await jdata.json();
if ('assets' in jdata) { var assets = []; for (let i in jdata.assets) { let ass = jdata.assets[i];
if (targetfile == ass.name) {
var downloadLink = ass.browser_download_url;
} }
return new Response('file not found', {status: 404}); }
|
这一段没什么好说的,进入第三步。
3. 下载(缓存)并转发
其实转发简单的很,直接 return 一个 fetch 即可。至于文件缓存,只要去查看一下 CloudFlare Workers 的文档即可。
即只要给 fetch 加上 {cf: { cacheEverything: true, cacheTtl: 3600 }}
的参数即可。
1
| return fetch(downloadLink, {cf: { cacheEverything: true, cacheTtl: 3600 }});
|
稍微综合一下代码:
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
| if (pathname[1] == 'gh') { var jsonurl, targetfile; if (pathname.length == 5) { jsonurl = `https://api.github.com/repos/${pathname[2]}/${pathname[3]}/releases/latest`; targetfile = pathname[4]; } else if (pathname.length == 6) { jsonurl = `https://api.github.com/repos/${pathname[2]}/${pathname[3]}/releases/tags/${pathname[4]}`; targetfile = pathname[5]; } var jdata = await fetch(jsonurl, {headers: {'User-Agent': 'CloudFlare-Workers'}}); jdata = await jdata.json(); if ('assets' in jdata) { var assets = []; for (let i in jdata.assets) { let ass = jdata.assets[i]; if (targetfile == ass.name) { return fetch(ass.browser_download_url, {cf: { cacheEverything: true, cacheTtl: 3600 }}); } } return new Response('File Not Found!', { status: 404 }); } else { return new Response(JSON.stringify(jdata), { status: 200 }); } }
|
大功告成,具体的源码可以访问此处查看。