前言 在免费使用 GitHub 私有仓库部署网站一文介绍了如何部署github pages,并实现了自定义域名的配置。本文介绍另一种自定义域名来访问github pages的方法, 特点是:
访客无法通过user.me 追踪到真实的user.github.io 通过cloudflare worker来实现 user.me 指向 user.github.io 整个访问流程如下:
用户访问 user.me ↓ Cloudflare DNS (指向 Worker) ↓ Worker 处理请求 (隐藏源站) ↓ Worker 访问 user.github.io ↓ GitHub Pages 返回内容 这种方案的优势:
安全性更高:完全隐藏GitHub Pages源站 配置简单:无需复杂的DNS设置 更少出错:避免DNS解析冲突 维护方便:集中化管理 注意:使用这种方法时,GitHub Pages只需保持默认的xxx.github.io域名即可,无需配置CNAME文件。
具体实现 1. 创建cf worker 首先创建一个名为hide-ghpage的Worker, 在设置中添加环境变量:
GITHUB_PAGES 填写 xxx.github.io CUSTOM_DOMAIN 填写 user.me WWW_DOMAIN 填写 www.user.me WORKER_DOMAIN 填写 hide-ghpage.user.workers.dev 然后编辑代码如下:
点击查看 ▼ function encodePathProperly(path) { // 先解码以防重复编码 const decodedPath = decodeURIComponent(path); // 替换特殊字符,之前被这个坑惨了。由于网页中包含邮件地址,特殊字符(如 @)在 URL 中无法正确处理,一直显示404. return decodedPath .split('/') .map(segment => segment .replace(/@/g, '%40') // 替换@ .replace(/#/g, '%23') .replace(/\?/g, '%3F') .replace(/\[/g, '%5B') .replace(/\]/g, '%5D') .replace(/=/g, '%3D') .replace(/&/g, '%26') ) .join('/'); } function initializeConfig(env) { console.log('Initializing with env vars:', { GITHUB_PAGES: env.GITHUB_PAGES, CUSTOM_DOMAIN: env.CUSTOM_DOMAIN, WWW_DOMAIN: env.WWW_DOMAIN, WORKER_DOMAIN: env.WORKER_DOMAIN }); const DOMAINS = { GITHUB: env.GITHUB_PAGES, CUSTOM: env.CUSTOM_DOMAIN, WWW: env.WWW_DOMAIN, WORKER: env.WORKER_DOMAIN }; return { domains: DOMAINS, githubPages: DOMAINS.GITHUB, allowedDomains: [DOMAINS.CUSTOM, DOMAINS.WWW, DOMAINS.WORKER], sensitiveHeaders: [ 'server', 'x-powered-by', 'x-github-request-id', 'x-fastly', 'x-proxy-cache', 'via', 'x-served-by', 'x-cache', 'x-cache-hits', 'x-timer', 'x-fastly-request-id', 'x-request-id', 'x-runtime', 'x-robots-tag', 'alt-svc', 'x-github', 'x-origin-cache', 'x-content-type-options' ], customHeaders: { 'server': 'cloudflare', 'X-Frame-Options': 'SAMEORIGIN', 'X-Content-Type-Options': 'nosniff', 'Referrer-Policy': 'strict-origin-when-cross-origin', 'Permissions-Policy': 'interest-cohort=()', 'Content-Security-Policy': "default-src 'self'; script-src 'self' 'unsafe-inline' 'unsafe-eval'; style-src 'self' 'unsafe-inline';", 'X-Powered-By': 'Cloudflare' }, debug: true }; } async function logError(error, request, extra = {}) { console.error('Error:', { message: error.message, stack: error.stack, url: request.url, method: request.method, ...extra }); } async function handleRequest(request, env) { const config = initializeConfig({ GITHUB_PAGES: env.GITHUB_PAGES, CUSTOM_DOMAIN: env.CUSTOM_DOMAIN, WWW_DOMAIN: env.WWW_DOMAIN, WORKER_DOMAIN: env.WORKER_DOMAIN }); const { domains } = config; try { const url = new URL(request.url); const currentDomain = url.hostname; // URL 编码处理 const encodedPath = encodePathProperly(url.pathname); console.log('URL encoding:', { originalPath: url.pathname, encodedPath: encodedPath }); // 详细的请求日志 console.log('Request details:', { url: url.toString(), domain: currentDomain, path: encodedPath, allowedDomains: config.allowedDomains, method: request.method, headers: Object.fromEntries(request.headers) }); // www 重定向到主域名 if (currentDomain === domains.WWW) { const newUrl = `https://${domains.CUSTOM}${encodedPath}${url.search}`; console.log('Redirecting www to non-www:', { from: url.toString(), to: newUrl }); return new Response(null, { status: 301, headers: { 'Location': newUrl, 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'server': 'cloudflare' } }); } // 验证域名 if (!config.allowedDomains.includes(currentDomain)) { console.warn('Invalid domain access:', { domain: currentDomain, allowedDomains: config.allowedDomains }); return new Response('Domain not allowed', { status: 403 }); } // 构建 GitHub URL const githubUrl = `https://${config.githubPages}${encodedPath}${url.search}`; console.log('Proxying to GitHub:', { from: url.toString(), to: githubUrl }); // 获取文件扩展名 const ext = encodedPath.split('.').pop().toLowerCase(); const isAsset = ['png', 'jpg', 'jpeg', 'gif', 'webp', 'css', 'js', 'svg', 'ico', 'woff', 'woff2', 'ttf'].includes(ext); // 处理请求头 let newHeaders = new Headers(request.headers); // 清理敏感请求头 ['referer', 'origin', 'x-real-ip', 'cf-connecting-ip', 'x-forwarded-for', 'x-forwarded-proto', 'cookie'].forEach(header => { newHeaders.delete(header); }); // 设置新的请求头 newHeaders.set('Host', config.githubPages); newHeaders.set('Accept-Encoding', 'gzip, deflate, br'); newHeaders.set('User-Agent', request.headers.get('User-Agent') || 'Cloudflare Worker'); // 创建 GitHub 请求 const githubRequest = new Request(githubUrl, { method: request.method, headers: newHeaders, redirect: isAsset ? 'follow' : 'manual', cf: { cacheEverything: false, cacheTtl: isAsset ? 86400 : 0, timeout: 30000 } }); // 发送请求到 GitHub Pages let response = await fetch(githubRequest); console.log('GitHub response:', { status: response.status, statusText: response.statusText, headers: Object.fromEntries(response.headers), url: githubUrl }); // 如果是 404,增加调试日志 if (response.status === 404) { console.log('404 Error details:', { githubUrl, originalUrl: request.url, headers: Object.fromEntries(response.headers), domain: currentDomain, githubPages: config.githubPages }); // 尝试直接访问 GitHub Pages try { const testResponse = await fetch(`https://${config.githubPages}${encodedPath}`); console.log('Direct GitHub test:', { status: testResponse.status, headers: Object.fromEntries(testResponse.headers) }); } catch (error) { console.error('Direct GitHub test failed:', error); } } // 处理资源文件 if (isAsset) { let newResponse = new Response(response.body, { status: response.status, statusText: response.statusText, headers: new Headers(response.headers) }); const allowedHeaders = [ 'content-type', 'content-length', 'cache-control', 'content-encoding', 'etag', 'last-modified' ]; for (const key of [...newResponse.headers.keys()]) { if (!allowedHeaders.includes(key.toLowerCase())) { newResponse.headers.delete(key); } } if (response.ok) { newResponse.headers.set('Cache-Control', 'public, max-age=31536000'); } return newResponse; } // 处理重定向 if ([301, 302, 307, 308].includes(response.status)) { const location = response.headers.get('Location'); console.log('Handling redirect:', { from: url.toString(), location: location }); if (location) { const redirectUrl = new URL(location.startsWith('http') ? location : `https://${config.githubPages}${location}`); const newLocation = `https://${currentDomain}${redirectUrl.pathname}${redirectUrl.search}`; console.log('New redirect location:', newLocation); return new Response(null, { status: response.status, headers: { 'Location': newLocation, 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'server': 'cloudflare' } }); } } // 读取响应体 let body = await response.text(); // 替换规则 const replacements = [ [new RegExp(`https?://${config.githubPages}`, 'g'), `https://${currentDomain}`], [`${config.githubPages}`, currentDomain], [/href="\//g, `href="https://${currentDomain}/`], [/src="\//g, `src="https://${currentDomain}/`], [/src="([^"]+)"/g, (match, p1) => { if (p1.startsWith('http')) { return match.replace(config.githubPages, currentDomain); } else if (p1.startsWith('/')) { return `src="https://${currentDomain}${p1}"`; } return match; }], [/srcset="([^ "]+)"/g, (match, p1) => { return `srcset="${p1.split(' , ').map(src =>, size] =, currentDomain)} ${size, ')} "`; }], [/<meta[^>]*?content="[^"]*?github\.io[^"]*?"[^>]*?>/g, (match) => { return match.replace(config.githubPages, currentDomain); }], [/<link[^>]*?rel="canonical"[^>]*?href="[^"]*?"[^>]*?>/g, (match) => { return match.replace(config.githubPages, currentDomain); }], [/<script type="application\/ld\+json">[^<]*?<\/script>/g, (match) => { return match.replace(config.githubPages, currentDomain); }] ]; // 应用替换规则 replacements.forEach(([pattern, replacement]) => { body = body.replace(pattern, replacement); }); // 创建最终响应 let newResponse = new Response(body, { status: response.status, statusText: response.statusText, headers: new Headers({ 'Content-Type': response.headers.get('Content-Type') || 'text/html;charset=UTF-8', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0' }) }); // 添加安全头部 config.sensitiveHeaders.forEach(header => { newResponse.headers.delete(header); }); Object.entries(config.customHeaders).forEach(([key, value]) => { newResponse.headers.set(key, value); }); return newResponse; } catch (err) { await logError(err, request); return new Response(`Error: ${err.message}`, { status: 500, headers: { 'Content-Type': 'text/plain', 'Cache-Control': 'no-cache, no-store, must-revalidate', 'Pragma': 'no-cache', 'Expires': '0', 'server': 'cloudflare' } }); } } export default { async fetch(request, env, ctx) { // 记录环境变量状态 console.log('Environment variables:', { GITHUB_PAGES: env.GITHUB_PAGES, CUSTOM_DOMAIN: env.CUSTOM_DOMAIN, WWW_DOMAIN: env.WWW_DOMAIN, WORKER_DOMAIN: env.WORKER_DOMAIN }); try { // 记录请求信息 console.log('Incoming request:', { url: request.url, method: request.method, headers: Object.fromEntries(request.headers) }); const response = await handleRequest(request, env); // 记录响应信息 console.log('Response details:', { status: response.status, headers: Object.fromEntries(response.headers) }); return response; } catch (error) { console.error('Fatal error:', error); return new Response('Internal Server Error', { status: 500 }); } } }; 2. 配置DNS解析 要将自定义域名指向 Worker,需要完成以下配置:
...