const HTML = ` 加速服务
GitHub 文件加速
Docker 镜像加速

GitHub文件链接带不带协议头都可以,支持release、archive以及文件,右键复制出来的链接都是符合标准的,更多用法、clone加速请参考这篇文章

release、archive使用cf加速,文件会跳转至JsDelivr

注意,不支持项目文件夹

分支源码:https://github.com/hunshcn/project/archive/master.zip

release源码:https://github.com/hunshcn/project/archive/v0.1.0.tar.gz

release文件:https://github.com/hunshcn/project/releases/download/v0.1.0/example.zip

分支文件:https://github.com/hunshcn/project/blob/master/filename

为了加速镜像拉取,你可以使用以下命令设置registery mirror:

sudo tee /etc/docker/daemon.json <<EOF
{
    "registry-mirrors": ["https://{{host}}"]
}
EOF

为了避免 Worker 用量耗尽,你可以手动 pull 镜像然后 re-tag 之后 push 至本地镜像仓库:

docker pull {{host}}/library/alpine:latest # 拉取 library 镜像
docker pull {{host}}/coredns/coredns:latest # 拉取 library 镜像

Blog: zgcwkj

GitHub: hunshcn/gh-proxy

Docker: Doublemine/container-registry-worker

` const PREFIX = '/'; const Config = { jsdelivr: 0 }; const whiteList = []; 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; /** @type {RequestInit} */ const PREFLIGHT_INIT = { // @ts-ignore 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', }), }; /** * Create a new response. * @param {any} body * @param {number} [status=200] * @param {Object} headers * @returns {Response} */ function makeResponse(body, status = 200, headers = {}) { headers['access-control-allow-origin'] = '*'; return new Response(body, { status, headers }); } /** * Create a new URL object. * @param {string} urlStr * @returns {URL|null} */ function createURL(urlStr) { try { return new URL(urlStr); } catch (err) { return null; } } addEventListener('fetch', (event) => { event.respondWith(handleFetchEvent(event).catch(err => makeResponse(`cfworker error:\n${err.stack}`, 502))); }); /** * Handle the fetch event. * @param {FetchEvent} event * @returns {Promise} */ async function handleFetchEvent(event) { const req = event.request; const url = new URL(req.url); if (url.pathname.startsWith('/v2/')) { return handleDockerProxy(req, url); } if (url.pathname.startsWith(PREFIX)) { return handleGitHubProxy(req, url); } return makeResponse('Not Found', 404); } /** * Handle token requests and Docker proxy. * @param {Request} req * @param {URL} url * @returns {Promise} */ async function handleDockerProxy(req, url) { const path = url.pathname; const originalHost = req.headers.get("host"); const registryHost = "registry-1.docker.io"; const headers = new Headers(req.headers); headers.set("host", registryHost); const registryUrl = `https://${registryHost}${path}`; const registryRequest = new Request(registryUrl, { method: req.method, headers: headers, body: req.body, // redirect: "manual", redirect: "follow", }); const registryResponse = await fetch(registryRequest); const responseHeaders = new Headers(registryResponse.headers); responseHeaders.set("access-control-allow-origin", originalHost); responseHeaders.set("access-control-allow-headers", "Authorization"); return new Response(registryResponse.body, { status: registryResponse.status, statusText: registryResponse.statusText, headers: responseHeaders, }); } /** * Handle GitHub proxy requests. * @param {Request} req * @param {URL} url * @returns {Promise} */ async function handleGitHubProxy(req, url) { let path = url.searchParams.get('q'); if (path) { return Response.redirect('https://' + url.host + PREFIX + path, 301); } path = url.href.substr(url.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://'); if (checkUrl(path)) { 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 { return makeResponse(HTML.replace(/{{host}}/g, url.host), 200, { "content-type": "text/html" }); } } /** * Check if the URL matches GitHub patterns. * @param {string} url * @returns {boolean} */ function checkUrl(url) { return [exp1, exp2, exp3, exp4, exp5, exp6].some(exp => url.search(exp) === 0); } /** * Handle HTTP redirects. * @param {Request} req * @param {string} location * @returns {Promise} */ async function handleHttpRedirect(req, location) { const url = createURL(location); if (!url) { return makeResponse('Invalid URL', 400); } return proxyRequest(url, req); } /** * Handle HTTP requests. * @param {Request} req * @param {string} pathname * @returns {Promise} */ async function httpHandler(req, pathname) { if (req.method === 'OPTIONS' && req.headers.has('access-control-request-headers')) { return new Response(null, PREFLIGHT_INIT); } const headers = new Headers(req.headers); let flag = !whiteList.length; for (const i of whiteList) { if (pathname.includes(i)) { flag = true; break; } } if (!flag) { return new Response('blocked', { status: 403 }); } if (pathname.search(/^https?:\/\//) !== 0) { pathname = 'https://' + pathname; } const url = createURL(pathname); return proxyRequest(url, { method: req.method, headers, body: req.body }); } /** * Proxy a request. * @param {URL} url * @param {RequestInit} reqInit * @returns {Promise} */ async function proxyRequest(url, reqInit) { const response = await fetch(url.href, reqInit); const responseHeaders = new Headers(response.headers); if (responseHeaders.has('location')) { const location = responseHeaders.get('location'); if (checkUrl(location)) { responseHeaders.set('location', PREFIX + location); } else { reqInit.redirect = 'follow'; return proxyRequest(createURL(location), reqInit); } } responseHeaders.set('access-control-expose-headers', '*'); responseHeaders.set('access-control-allow-origin', '*'); responseHeaders.delete('content-security-policy'); responseHeaders.delete('content-security-policy-report-only'); responseHeaders.delete('clear-site-data'); return new Response(response.body, { status: response.status, headers: responseHeaders, }); }