|
背景
在中国,访问 GitHub 速度受限,导致下载代码库和克隆项目缓慢。即使使用第三方镜像,也存在稳定性和可靠性问题。为了解决这一问题,我决定自部署一个 GitHub 镜像,并分享部署过程。
项目简介
我参考了前辈的镜像代码,但发现无法实现登录。因此,我使用 Cloudflare Workers 重新构建了镜像,通过 Cloudflare 全球 CDN 网络,将 GitHub 请求代理到最近的数据中心,从而加速访问速度。
下面是代码:
const upstream = 'github.com'
const upstream_path = '/'
const upstream_mobile = 'github.com'
const blocked_region = ['KP', 'SY', 'PK', 'CU']
const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
const https = true
const replace_dict = {
'$upstream': '$custom_domain',
'//github.com': '',
'https://github.com': ''
}
addEventListener('fetch', event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
const region = request.headers.get('cf-ipcountry').toUpperCase()
const ip_address = request.headers.get('cf-connecting-ip')
const user_agent = request.headers.get('user-agent')
if (blocked_region.includes(region)) {
return new Response('Access denied: WorkersProxy is not available in your region yet.', { status: 403 })
} else if (blocked_ip_address.includes(ip_address)) {
return new Response('Access denied: Your IP address is blocked by WorkersProxy.', { status: 403 })
}
const requestURL = new URL(request.url)
const path = requestURL.pathname
const destURL = new URL((https ? 'https://' : 'http://') + upstream + path)
// 添加原始查询参数
destURL.search = requestURL.search
const upstreamDomain = await device_status(user_agent) ? upstream : upstream_mobile
const newRequestHeaders = new Headers(request.headers)
newRequestHeaders.set('Host', upstreamDomain)
newRequestHeaders.set('Referer', 'https://' + upstreamDomain)
// 处理Origin头
const origin = request.headers.get('Origin')
if (origin) {
newRequestHeaders.set('Origin', 'https://' + upstreamDomain)
}
// 处理ZIP下载请求
if (path.endsWith('.zip') || path.includes('/archive/')) {
return handleZipDownload(destURL, newRequestHeaders)
}
// 处理git操作
if (path.includes('/info/refs') || path.includes('/git-upload-pack')) {
return handleGitOperation(destURL, request, newRequestHeaders)
}
let response = await fetch(destURL.href, {
method: request.method,
headers: newRequestHeaders,
body: request.body,
redirect: 'manual',
})
let newResponseHeaders = new Headers(response.headers)
newResponseHeaders.set('Access-Control-Allow-Origin', '*')
newResponseHeaders.set('Access-Control-Allow-Credentials', 'true')
newResponseHeaders.delete('Content-Security-Policy')
newResponseHeaders.delete('Content-Security-Policy-Report-Only')
newResponseHeaders.delete('Clear-Site-Data')
// 处理重定向
if ([301, 302, 307, 308].includes(response.status)) {
const location = newResponseHeaders.get('Location')
if (location) {
const redirectURL = new URL(location)
redirectURL.host = requestURL.host
newResponseHeaders.set('Location', redirectURL.href)
}
return new Response(null, {
status: response.status,
headers: newResponseHeaders
})
}
const content_type = newResponseHeaders.get('content-type')
let body = await response.text()
// 替换响应中的域名
for (let key in replace_dict) {
const replaceKey = key === '$upstream' ? upstreamDomain : key
const replaceValue = replace_dict[key] === '$custom_domain' ? requestURL.host : replace_dict[key]
body = body.replace(new RegExp(replaceKey, 'g'), replaceValue)
}
return new Response(body, {
status: response.status,
headers: newResponseHeaders
})
}
async function handleZipDownload(destURL, headers) {
const response = await fetch(destURL.href, {
headers: headers,
redirect: 'follow',
})
if (response.ok) {
const newHeaders = new Headers(response.headers)
newHeaders.set('Content-Disposition', response.headers.get('Content-Disposition') || 'attachment')
return new Response(response.body, {
status: response.status,
headers: newHeaders
})
} else {
return new Response('Failed to download ZIP file', { status: 404 })
}
}
async function handleGitOperation(destURL, request, headers) {
const response = await fetch(destURL.href, {
method: request.method,
headers: headers,
body: request.body,
redirect: 'follow',
})
if (response.ok) {
const newHeaders = new Headers(response.headers)
newHeaders.set('Cache-Control', 'no-cache')
return new Response(response.body, {
status: response.status,
headers: newHeaders
})
} else {
return new Response('Git operation failed', { status: response.status })
}
}
async function device_status(user_agent_info) {
const agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"]
return !agents.some(agent => user_agent_info.includes(agent))
}
|
|