本文参与「玩透 ESA」有奖征文活动,活动详情见
https://event.alibabacloud-esa.com/
01 「100kb/s」:国内开发者绕不开的 GitHub 下载之痛
相信国内的开发者,对以下这个流程或多或少都有些“既视感”:
你(或者说,昨天的我)兴奋地发现了一个新的开源软件,准备大展拳脚。你熟练地搜索,找到它的 GitHub Release 页面,点击下载。浏览器左下角弹出了下载进度条,速度赫然显示着:
100kb/s。
这个数字也就是某网盘的 10 倍而已。尽管如此,事实是,这不是“慢”,这是几乎“不可用”。
于是,标准流程开始了:
取消下载 → 搜索“GitHub 加速” → 找到一个 gh-proxy 公共实例 → 把链接粘贴进去 → 「当天 API 请求次数用尽」。
这几乎是一个死循环。
核心问题很清楚:GitHub 直连速度无法忍受,而公共服务又极不稳定。
最终,所有开发者都会被这种沮丧推向那个结论:
“决定自己部署 gh-proxy。”
02 初探 Serverless:在 Cloudflare Workers 上自建 gh-proxy
既然要自建,那么 hunshcn/gh-proxy 就是最好的选择,它天生支持 Serverless,尤其 Cloudflare Workers。
当时的我,对 CF Workers 的理解就是:
- 全球 CDN
- V8 Isolate
- 零冷启动
部署过程也确实非常简单:
- 注册、登录 CF
- Create a Worker
- 打开 gh-proxy 的 index.js
- 粘贴 → Save and Deploy
部署完成后,我以为我终于可以告别“100kb/s 之痛”了。
然而,我高兴得太早了。
03 「延迟放大器」:Cloudflare 免费计划的性能陷阱
部署后很快出现了各种红温时刻:
.workers.dev国内可用性不稳定- 换上自有域名后速度依然慢得离谱
“这是全球 CDN 啊!怎么变成减速器了?”
直到我在 CF 社区看到一句关键回复:
Cloudflare is probably serving your China customers from the LA PoP instead of Hong Kong.
从此我发现了真相:
Cloudflare 的国内节点是企业专属。Free/Pro 用户全部绕道美国洛杉矶。
流量路径变成:
我 →(艰难出海)→ 洛杉矶 PoP → GitHub → 洛杉矶 →(再次艰难出海)→ 我
跨太平洋两次,还两次穿过 GFW。
这比直连更慢,甚至导致 gh-proxy 成了 “减速器”。
所谓的“优选 IP”本质是赌路由,治标不治本。
04 柳暗花明:初识 ESA 边缘安全加速平台
就在我几乎放弃时,我收到了 阿里云 ESA 邀测通知。
文档中两个关键特性直接吸引了我:
- 基于 V8 Isolate
- 国内平台,国内节点
这意味着:
- 架构现代化,与 Cloudflare Workers 一样的 V8 隔离沙箱
- 更重要:网络路径正确了!
现代架构(V8) + 国内加速节点(无跨洋) = 解决 CF 的全部痛点
我立刻开始迁移。
05 迁移实录:从 Service Worker 到现代 ES Module
迁移内容主要两部分:
1. 语法现代化:Service Worker → ES Module
Cloudflare Workers 使用:
addEventListener('fetch', e => e.respondWith(...))
ESA 边缘函数使用 ESM:
export default {
async fetch(request, env, ctx) { ... }
}
我直接把结构全部换成现代模块风格。
2. 代码适配:零依赖单文件
我将首页 HTML 直接内联到 JS:
const HOME_PAGE = `<!DOCTYPE html>...</html>`;
并根据逻辑判断根路径 / 直接返回这个页面。
这样整个 gh-proxy 变成一个零依赖单 JS 文件,无需 CLI,无需静态资源。
部署比 CF 简单得多。
06 丝滑部署与「速度老快了」的惊艳体验
在 Cloudflare,我经历过:
- 优选 IP
- 支付失败
- 节点绕路
- 速度玄学
在 ESA 的体验是:
我直接打开控制台,把 js 粘贴进去→绑定域名→部署完成。
没有任何阻碍。
我直接测试下载 100MB 文件:
“—— 完成,速度老快了。”
它稳定跑满了我的带宽。
那一刻,“100kb/s” 的时代彻底结束。
07 最后
如果你仍在 Cloudflare 的绕路延迟里挣扎,不妨试试 ESA。
它不是“魔法优选”,不是“群友玄学”,而是:
一个真正为国内网络环境设计的现代化边缘平台。
08 源代码
'use strict'
// 前缀,如果自定义路由为example.com/gh/*,将PREFIX改为 '/gh/',注意,少一个杠都会错!
const PREFIX = '/'
// 分支文件使用jsDelivr镜像的开关,1为开启
const Config = {
jsdelivr: 1
}
const whiteList = [] // 白名单,路径里面有包含字符的才会通过,e.g. ['/username/']
/**
* 嵌入式前端页面
*/
const HOME_PAGE = `
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>GitHub Proxy</title>
<style>
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", "Roboto", "Oxygen", "Ubuntu", "Cantarell", "Fira Sans", "Droid Sans", "Helvetica Neue", sans-serif;
line-height: 1.6;
color: #333;
max-width: 800px;
margin: 20px auto;
padding: 0 15px;
}
h1, h2 {
border-bottom: 2px solid #eaecef;
padding-bottom: .3em;
}
input[type="text"] {
width: 100%;
padding: 8px;
margin-bottom: 10px;
box-sizing: border-box;
border: 1px solid #ccc;
border-radius: 4px;
}
.btn {
display: inline-block;
padding: 8px 16px;
font-size: 14px;
font-weight: 500;
line-height: 20px;
white-space: nowrap;
vertical-align: middle;
cursor: pointer;
user-select: none;
border: 1px solid rgba(27,31,35,.2);
border-radius: 6px;
appearance: none;
color: #24292e;
background-color: #fafbfc;
}
.btn:hover {
background-color: #f3f4f6;
}
.note {
background-color: #fffbdd;
padding: 10px 15px;
border-left: 4px solid #ffc107;
margin: 15px 0;
}
code {
font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, Courier, monospace;
background-color: rgba(27,31,35,.05);
padding: .2em .4em;
margin: 0;
font-size: 85%;
border-radius: 3px;
}
pre {
padding: 16px;
overflow: auto;
font-size: 85%;
line-height: 1.45;
background-color: #f6f8fa;
border-radius: 3px;
}
pre code {
padding: 0;
margin: 0;
font-size: 100%;
background-color: transparent;
border: 0;
}
a {
color: #0366d6;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
ul {
padding-left: 20px;
}
</style>
</head>
<body>
<h1>GitHub 文件加速</h1>
<p>这是一个用于代理 GitHub 资源的阿里云ESA边缘函数。GitHub文件链接带不带协议头都可以,支持release、archive以及文件,右键复制出来的链接都是符合标准的。</p>
<div class="note">
<p>本项目基于<b>hunshcn/gh-proxy</b>修改而来。</p>
<p><b>不支持</b>对项目文件夹的代理。</p>
</div>
<input type="text" id="urlInput" placeholder="请输入 GitHub 资源链接..." onkeydown="if(event.keyCode==13){submit()}">
<button class="btn" onclick="submit()">生成加速链接</button>
<h2>用法示例</h2>
<ul>
<li><b>分支源码:</b> <code><span class="host"></span>/https://github.com/hunshcn/project/archive/master.zip</code></li>
<li><b>Release 源码:</b> <code><span class="host"></span>/https://github.com/hunshcn/project/archive/v0.1.0.tar.gz</code></li>
<li><b>Release 文件:</b> <code><span class="host"></span>/https://github.com/hunshcn/project/releases/download/v0.1.0/example.zip</code></li>
<li><b>分支文件 (将跳转CDN):</b> <code><span class="host"></span>/https://github.com/hunshcn/project/blob/master/filename</code></li>
<li><b>仓库克隆:</b>
<pre><code>git clone <span class="host"></span>/https://github.com/user/repo.git</code></pre>
</li>
</ul>
<script>
const hostUrl = location.origin + (location.pathname === '/' ? '' : location.pathname);
document.querySelectorAll('.host').forEach(el => el.textContent = hostUrl);
function submit() {
let url = document.getElementById('urlInput').value;
if (url) {
if (!url.startsWith('https://') && !url.startsWith('http://')) {
url = 'https://' + url;
}
window.open(hostUrl + '/' + url);
}
}
</script>
</body>
</html>
`;
/** @type {ResponseInit} */
const PREFLIGHT_INIT = {
status: 204,
headers: new Headers({
'access-control-allow-origin': '*',
'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS',
'access-control-max-age': '1728000',
}),
}
const exp1 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i
const exp2 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob|raw)\/.*$/i
const exp3 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i
const exp4 = /^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+?\/.+$/i
const exp5 = /^(?:https?:\/\/)?gist\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+$/i
const exp6 = /^(?:https?:\/\/)?github\.com\/.+?\/.+?\/tags.*$/i
export default {
async fetch(request, env, ctx) {
try {
return await fetchHandler(request);
} catch (err) {
return makeRes('cfworker error:\n' + err.stack, 502);
}
}
};
/**
* @param {any} body
* @param {number} status
* @param {Object<string, string>} headers
*/
function makeRes(body, status = 200, headers = {}) {
headers['access-control-allow-origin'] = '*'
return new Response(body, {status, headers})
}
/**
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr)
} catch (err) {
return null
}
}
function checkUrl(u) {
for (let i of [exp1, exp2, exp3, exp4, exp5, exp6]) {
if (u.search(i) === 0) {
return true
}
}
return false
}
/**
* @param {Request} req
*/
async function fetchHandler(req) {
const urlStr = req.url
const urlObj = new URL(urlStr)
let path = urlObj.searchParams.get('q')
if (path) {
return Response.redirect('https://' + urlObj.host + PREFIX + path, 301)
}
const path_ = urlObj.pathname.replace(PREFIX, '/').substring(1);
// cfworker 会把路径中的 `//` 合并成 `/`
path = urlObj.href.substr(urlObj.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://')
if (path.search(exp1) === 0 || path.search(exp5) === 0 || path.search(exp6) === 0 || path.search(exp3) === 0 || path.search(exp4) === 0) {
return httpHandler(req, path)
} else if (path.search(exp2) === 0) {
if (Config.jsdelivr) {
const newUrl = path.replace('/blob/', '@').replace(/^(?:https?:\/\/)?github\.com/, 'https://cdn.jsdelivr.net/gh')
return Response.redirect(newUrl, 302)
} else {
path = path.replace('/blob/', '/raw/')
return httpHandler(req, path)
}
} else if (path.search(exp4) === 0) {
const newUrl = path.replace(/(?<=com\/.+?\/.+?)\/(.+?\/)/, '@$1').replace(/^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com/, 'https://cdn.jsdelivr.net/gh')
return Response.redirect(newUrl, 302)
} else {
// 如果路径为空,则返回主页
if (!path_ || path_ === '/') {
return new Response(HOME_PAGE, {
headers: {
'Content-Type': 'text/html; charset=utf-8'
}
});
}
// 否则,返回 404
return new Response('Not Found', { status: 404 })
}
}
/**
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers
// preflight
if (req.method === 'OPTIONS' &&
reqHdrRaw.has('access-control-request-headers')
) {
return new Response(null, PREFLIGHT_INIT)
}
const reqHdrNew = new Headers(reqHdrRaw)
let urlStr = pathname
let flag = !Boolean(whiteList.length)
for (let i of whiteList) {
if (urlStr.includes(i)) {
flag = true
break
}
}
if (!flag) {
return new Response("blocked", {status: 403})
}
if (urlStr.search(/^https?:\/\//) !== 0) {
urlStr = 'https://' + urlStr
}
const urlObj = newUrl(urlStr)
/** @type {RequestInit} */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'manual',
body: req.body
}
return proxy(urlObj, reqInit)
}
/**
*
* @param {URL} urlObj
* @param {RequestInit} reqInit
*/
async function proxy(urlObj, reqInit) {
const res = await fetch(urlObj.href, reqInit)
const resHdrOld = res.headers
const resHdrNew = new Headers(resHdrOld)
const status = res.status
if (resHdrNew.has('location')) {
let _location = resHdrNew.get('location')
if (checkUrl(_location))
resHdrNew.set('location', PREFIX + _location)
else {
reqInit.redirect = 'follow'
return proxy(newUrl(_location), reqInit)
}
}
resHdrNew.set('access-control-expose-headers', '*')
resHdrNew.set('access-control-allow-origin', '*')
resHdrNew.delete('content-security-policy')
resHdrNew.delete('content-security-policy-report-only')
resHdrNew.delete('clear-site-data')
return new Response(res.body, {
status,
headers: resHdrNew,
})
}
1万+

被折叠的 条评论
为什么被折叠?



