起源

众所周知,Vercel支持php的serverless部署,但Vercel的域名vercel.app和cname域名cname.vercel-dns.com在国内大部分时间都无法正常打开,所以想到了用Cloudflare进行一个反向代理服务。

探索过程

代理脚本网上有现成的,但对动态请求的支持不怎么友好,经过时间的流逝,这个问题的影响越来越大,所以构建了一个脚本,支持了动态请求。因为是代理,仅仅只是支持动态请求是不行的,里面的链接都指向的是源站地址,需要把他们替换为代理地址,这就是代理的工作之一,有些动态服务会使用Location请求头来重定向到指定子页面,但其中的hostname部分依旧是源站地址,也需要替换,所以headers也需要进行文本替换。经过长时间的修修补补,有了一个成型的代理脚本。

脚本功能

  1. 支持全动态访问与数据传输
  2. 支持自定义修改和移除指定的Headers,正则匹配
  3. 支持自定义修改body中的文本,正则匹配
  4. 支持手机端与电脑端地址分离(适用于手机端有专门网页地址的情况)
  5. 支持自定义代理请求源站的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;
}

视频样例