起源
众所周知,Vercel支持php的serverless部署,但Vercel的域名vercel.app和cname域名cname.vercel-dns.com在国内大部分时间都无法正常打开,所以想到了用Cloudflare进行一个反向代理服务。
探索过程
代理脚本网上有现成的,但对动态请求的支持不怎么友好,经过时间的流逝,这个问题的影响越来越大,所以构建了一个脚本,支持了动态请求。因为是代理,仅仅只是支持动态请求是不行的,里面的链接都指向的是源站地址,需要把他们替换为代理地址,这就是代理的工作之一,有些动态服务会使用Location请求头来重定向到指定子页面,但其中的hostname部分依旧是源站地址,也需要替换,所以headers也需要进行文本替换。经过长时间的修修补补,有了一个成型的代理脚本。
脚本功能
- 支持全动态访问与数据传输
- 支持自定义修改和移除指定的
Headers,正则匹配 - 支持自定义修改
body中的文本,正则匹配 - 支持手机端与电脑端地址分离(适用于手机端有专门网页地址的情况)
- 支持自定义代理请求源站的
Headers
脚本内容(_worker.js)
// Cloudflare Worker 通用代理脚本
// 构建日期:2023年10月2日
// 目标域名
let someHost = 'github.com';
// 手机版目标域名
let mobileHost = 'github.com';
// 代理域名
const ourHost = 'github.dancedreamnet.top';
// 代理要设置的header
const proxiedHeadersList = {
'Referer': 'https://github.com/',
}
// 返回结果中要替换或删除的headers名称
const targetHeadersList = {
'Referer': 'edit',
'Location': 'edit',
}
// body中要替换的文本
const editBodyList = {
'$somehost': '$ourhost',
}
// headers中要替换的文本
const editHeaderList = {
'$somehost': '$ourhost',
}
export default {
async fetch(request) {
const url = new URL(request.url);
const userAgent = request.headers.get('user-agent');
if (!await agentGet(userAgent)) {
someHost = mobileHost
}
url.hostname = someHost;
if (url.pathname.startsWith('/')) {
const requestInfo = new Request(url, request);
var targetone;
for (targetone in proxiedHeadersList) {
var targetContext = proxiedHeadersList[targetone];
requestInfo.headers.set(targetone, targetContext);
}
let response = await fetch(requestInfo);
let responseHeaders = response.headers;
let status = response.status;
let wheaders = await replaceResponseHeader(responseHeaders);
let editedBody = null;
const contentType = wheaders.get('content-type');
if (contentType == null) {
editedBody = response.body;
} else if (contentType.includes('text/html') && contentType.includes('UTF-8')) {
editedBody = await replaceResponseText(response)
} else {
editedBody = response.body
}
return new Response(editedBody, {
status,
headers: wheaders
})
}
return env.ASSETS.fetch(request);
}
};
async function replaceText(fixedText, targetList) {
let primaryText, finalText;
for (primaryText in targetList) {
finalText = targetList[primaryText];
if (primaryText == '$somehost') {
primaryText = someHost
} else if (primaryText == '$ourhost') {
primaryText = ourHost
}
if (finalText == '$somehost') {
finalText = someHost
} else if (finalText == '$ourhost') {
finalText = ourHost
}
let reduce = new RegExp(primaryText, 'g');
fixedText = fixedText.replace(reduce, finalText);
}
return fixedText
}
async function replaceResponseText(requestResponse) {
let body = await requestResponse.text()
body = await replaceText(body, editBodyList)
return body
}
async function replaceResponseHeader(targetHeaders) {
let targetHeaderName, actionName;
let funcHeaders = new Headers(targetHeaders);
for (targetHeaderName in targetHeadersList) {
actionName = targetHeadersList[targetHeaderName];
let waitEditHeader = funcHeaders.get(targetHeaderName);
if (waitEditHeader != null) {
if (actionName == 'edit') {
let editedHeader = await replaceText(waitEditHeader, editHeaderList);
funcHeaders.set(targetHeaderName, editedHeader);
} else {
funcHeaders.delete(targetHeaderName);
}
}
}
return funcHeaders
}
async function agentGet(agentInfo) {
var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
var flag = true;
for (var v = 0; v < agents.length; v++) {
if (agentInfo.indexOf(agents[v]) > 0) {
flag = false;
break;
}
}
return flag;
}