567 字
3 分钟
使用 Cloudflare Workers 搭建一个自用的 gh-proxy
NOTE直接把下方代码粘贴到 Worker,部署后访问 Worker对应的域名即可。
'use strict'
/**
* Static files (404.html, sw.js, conf.js)
*/
const ASSET_URL = 'https://hunshcn.github.io/gh-proxy/'
// Prefix, if custom route is example.com/gh/*, change PREFIX to '/gh/' (Ensure trailing slash)
const PREFIX = '/'
// Toggle jsDelivr mirror for branch files, 0 = off (default)
const Config = {
jsdelivr: 0
}
const whiteList = [] // Whitelist, only allow paths containing specific substrings, e.g., ['/username/']
/** Preflight response for CORS requests */
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',
}),
}
// Regular expressions for URL matching
const regexPatterns = [
/^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:releases|archive)\/.*$/i,
/^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:blob|raw)\/.*$/i,
/^(?:https?:\/\/)?github\.com\/.+?\/.+?\/(?:info|git-).*$/i,
/^(?:https?:\/\/)?raw\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+?\/.+$/i,
/^(?:https?:\/\/)?gist\.(?:githubusercontent|github)\.com\/.+?\/.+?\/.+$/i,
/^(?:https?:\/\/)?github\.com\/.+?\/.+?\/tags.*$/i,
]
/**
* Helper function to create a Response
* @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 })
}
/**
* Safely create a new URL object
* @param {string} urlStr
*/
function newUrl(urlStr) {
try {
return new URL(urlStr)
} catch (err) {
return null
}
}
addEventListener('fetch', e => {
const ret = fetchHandler(e).catch(err => {
console.error('Error occurred:', err)
return makeRes(`cfworker error: ${err.message}\nStack: ${err.stack}`, 502)
})
e.respondWith(ret)
})
/**
* Check if the URL matches allowed patterns
* @param {string} url
*/
function checkUrl(url) {
return regexPatterns.some(pattern => pattern.test(url))
}
/**
* Fetch handler for incoming requests
* @param {FetchEvent} e
*/
async function fetchHandler(e) {
const req = e.request
const urlStr = req.url
const urlObj = new URL(urlStr)
let path = urlObj.searchParams.get('q')
// Redirect if 'q' parameter exists
if (path) {
return Response.redirect(`https://${urlObj.host}${PREFIX}${path}`, 301)
}
// Normalize path
path = urlObj.href.substr(urlObj.origin.length + PREFIX.length).replace(/^https?:\/+/, 'https://')
// Handle requests matching specific patterns
if (regexPatterns.some(pattern => pattern.test(path))) {
return httpHandler(req, path)
} else if (path.match(regexPatterns[1])) { // Handle blob/raw paths
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 {
return fetch(ASSET_URL + path)
}
}
/**
* Handle HTTP requests to the proxied resource
* @param {Request} req
* @param {string} pathname
*/
function httpHandler(req, pathname) {
const reqHdrRaw = req.headers
// Handle preflight CORS requests
if (req.method === 'OPTIONS' && reqHdrRaw.has('access-control-request-headers')) {
return new Response(null, PREFLIGHT_INIT)
}
const reqHdrNew = new Headers(reqHdrRaw)
let flag = whiteList.length === 0 // Allow all if whitelist is empty
for (let item of whiteList) {
if (pathname.includes(item)) {
flag = true
break
}
}
if (!flag) {
return new Response("blocked", { status: 403 })
}
// Normalize GitHub URLs
if (pathname.startsWith('github')) {
pathname = 'https://' + pathname
}
const urlObj = newUrl(pathname)
/** Request options */
const reqInit = {
method: req.method,
headers: reqHdrNew,
redirect: 'manual',
body: req.body
}
return proxy(urlObj, reqInit)
}
/**
* Proxy the request to the destination URL
* @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)
// Handle redirects
if (resHdrNew.has('location')) {
const location = resHdrNew.get('location')
if (checkUrl(location)) {
resHdrNew.set('location', PREFIX + location)
} else {
reqInit.redirect = 'follow'
return proxy(newUrl(location), reqInit)
}
}
// Adjust headers for response
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: res.status,
headers: resHdrNew,
})
}
使用 Cloudflare Workers 搭建一个自用的 gh-proxy
https://devniko.com/posts/cloudflare-ghproxy/