让 gh-proxy 更快,更现代化 —— 从 Cloudflare Workers 迁移到 ESA 边缘安全加速

部署运行你感兴趣的模型镜像

本文参与「玩透 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
  • 零冷启动

部署过程也确实非常简单:

  1. 注册、登录 CF
  2. Create a Worker
  3. 打开 gh-proxy 的 index.js
  4. 粘贴 → 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,
    })
}

您可能感兴趣的与本文相关的镜像

GPT-oss:20b

GPT-oss:20b

图文对话
Gpt-oss

GPT OSS 是OpenAI 推出的重量级开放模型,面向强推理、智能体任务以及多样化开发场景

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值