前言
在免费使用 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 => {
const [url, size] = src.trim().split(' ');
if (url.startsWith('http')) {
return `${url.replace(config.githubPages, currentDomain)} ${size || ''}`;
} else if (url.startsWith('/')) {
return `https://${currentDomain}${url} ${size || ''}`;
}
return `${url} ${size || ''}`;
}).join(', ')}"`;
}],
[/<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,需要完成以下配置:
- 进入 Cloudflare Worker 设置页面
- 在"自定义域名"(Custom Domains)选项下
- 依次添加域名
user.me
和www.user.me
(Cloudflare 将自动创建对应的 DNS 记录) - 在"路由"(Zone Routes)中添加以下区域:
user.me
, 路由:user.me/*
和www.user.me/*
最后结果如下:
workers.dev 子域名:hide-ghpage.user.workers.dev
自定义域:user.me
自定义域:www.user.me
路由:user.me/*
路由:www.user.me/*
自定义域名与区域路由的区别
- 自定义域名:声明某个域名可以使用该 Worker 服务
- 区域路由:定义 Worker 具体要处理的 URL 访问规则
举例说明:如果只配置了自定义域名而没有添加路由规则,Worker 虽然知道它可以为 user.me
提供服务,但不清楚具体要处理哪些 URL 请求。
重要提醒 完成以上配置后,请务必:
- 删除所有指向 GitHub IP 的 A 记录
- 删除所有指向
user.github.io
的 CNAME 记录- 仅保留指向 Worker 的 DNS 记录
这样可以确保所有流量都经过 Worker 处理,从而实现完整的域名代理功能。
3. 配置SSL/TLS
为确保安全访问,需要正确配置SSL:
SSL/TLS > 概述
- 选择"完全"(Full)模式
SSL/TLS > 边缘证书
- ✅ 启用"始终使用HTTPS"
- ✅ 启用"自动HTTPS重写"
这样可以确保:
- HTTP自动跳转HTTPS
- 所有子域名启用HTTPS
- 自动修复混合内容问题
4. 配置缓存规则
为提升访问速度,建议配置以下缓存规则:
- 创建新规则
规则名称:静态资源缓存
匹配条件:所有传入请求
- 设置缓存条件
字段:URI 完整
运算符:通配符
值:https://user.me/*.(css|js|jpg|jpeg|png|gif|ico|svg|woff|woff2|ttf)
- 配置缓存参数
缓存资格:符合缓存条件
边缘TTL:4小时
浏览器TTL:4小时
缓存配置说明:
- 静态资源默认缓存4小时
- 同时在CDN节点和浏览器端缓存
- 可根据需求调整缓存时间
- 频繁更新的资源建议缩短时间
- 稳定资源可适当延长
通过以上配置,我们成功实现了:
- 隐藏GitHub Pages源站
- 提升访问速度
- 增强安全性
- 优化用户体验
注意事项
如果完成上述配置还无法正常访问,可以尝试:
- 尝试先通过
https://hide-ghpage.user.workers.dev
访问 - 尝试在浏览器的无痕模式访问
user.me
- 在cf上清除
user.me
和www.user.me
的dns 缓存 - 本地清除dns缓存
- 删除浏览器的浏览记录和cookies
- 如果设置了页面规则"从 WWW 重定向到根",请删除
总结
通过上述设置,应该可以实现Cloudflare Workers 代理 GitHub Pages,并隐藏原始的域名地址。