☰
使用 CloudFlare Worker 免费部署 jsproxy 在线代理节点:cf-worker 方案实操与源码解析
2026/9/25 3:24:53 网站建设 项目流程
  • 后端
  • 网络
  • 通信

【免费下载链接】jsproxy

An online proxy based on ServiceWorker

项目地址:https://gitcode.com/gh_mirrors/js/jsproxy
点击查看免费下载

本文围绕 jsproxy 仓库中的cf-worker模块展开:先按 cf-worker/README.md 的步骤在 CloudFlare 上免费部署一套无需自有服务器的在线代理节点,再逐段剖析 cf-worker/index.js 的路由分发、请求头还原、CORS 转义与 YouTube 视频重定向修复等核心实现,并对照 nginx 侧的 api.conf 与 OpenResty 脚本说明两条代理链路如何保持协议一致。读完你可以独立完成一个零服务器成本的代理节点部署,并理解它与服务端 nginx 版在接口协议上的对应关系。

一、CloudFlare Worker 简介

CloudFlare Worker是 CloudFlare 提供的边缘计算服务。开发者可通过 JavaScript 对 CDN 进行编程,从而灵活处理 HTTP 请求,使得很多任务可以在 CDN 上完成,而无需自己的服务器参与。

对 jsproxy 而言,这正是它"无服务器版"(cfworker 节点)的载体:把代理逻辑压缩进一个单文件 Worker 脚本,直接跑在 CloudFlare 的边缘节点上。仓库根目录 README 的更新日志(README.md)中,2019-06-22 发布的"cfworker 无服务器版"即指本模块,并建议长期使用演示服务的读者迁移到该版本。

jsproxy 的整体架构是"前端 ServiceWorker + 后端纯转发":浏览器中的 ServiceWorker 拦截并改写页面请求,把目标 URL 打包进Referer请求头、以/http/目标URL的形式打到代理节点;节点(nginx 版或 cf-worker 版)只负责转发流量并改写 HTTP 头。cf-worker 就是这条链路中"可完全托管在 CDN 上"的节点实现。

二、部署步骤

部署入口是 CloudFlare 官方的 Workers 控制台(workers.cloudflare.com,可从 CloudFlare 官网进入)。操作流程如下:

  1. 注册并登录 CloudFlare 账号;
  2. 点击Start building,为自己的 Worker 取一个子域名;
  3. 选择Create a Worker创建新 Worker;
  4. 把本仓库 cf-worker/index.js 的全部内容复制到控制台左侧的代码框;
  5. 点击Save and deploy完成部署。

部署成功后,右侧应显示 Worker 的首页。收藏地址栏中的https://xxxx.子域名.workers.dev形式的访问地址,以后可直接访问。

部署后的可用端点

Worker 的路由逻辑集中在 fetchHandler 中,与仓库 test/works.txt(内容为ok)的命名相呼应,部署后可验证以下路径:

路径行为源码依据
/works返回文本it works,用于连通性自检cf-worker/index.js#L78-L79
/http/...HTTP(S) 代理主接口,见后文解析cf-worker/index.js#L69-L71
/http返回"请更新 cfworker 到最新版本!",作为版本探测接口cf-worker/index.js#L73-L75
/ws返回not support(400),WebSocket 代理尚未实现cf-worker/index.js#L76-L77
其他路径作为静态资源从ASSET_URL反向代理cf-worker/index.js#L80-L83

另外,所有以http:协议的请求会被 301 重定向到https:,并附带strict-transport-security头(见 cf-worker/index.js#L61-L67),因此实际使用时统一走 HTTPS。

三、计费与扩容

回到控制台的overview页面可以查看用量。关键计费事实(以 cf-worker/README.md 为准):

  • 免费版:每天 10 万次免费请求,对个人使用通常足够;
  • 扩容方式一:注册多个 Worker(多个免费子域名),在前端conf.js中配置多线路负载均衡,把流量摊到多个节点;
  • 扩容方式二:升级到 $5/月 的高级版本,每月可用 1000 万次请求,超出部分 $0.5/百万次请求。

多节点方案在 changelogs/v0.1.0.md 中有配套的权重设计:前端conf.js的node_map(节点 id 与节点主机)与node_default(默认节点)之外,v0.1.0 为每条线路增加了权重配置,"命中比例 = 当前值 / 总值"。演示案例中 cfworker 节点即采用"1 个收费版 + 多个免费版"的组合——由于免费版有访问频率限制,通过更低权重减少其负载。也就是说,用多个免费 Worker 堆量的策略是官方文档明确支持的用法。

四、修改配置:ASSET_URL 与自定义 conf.js

Worker 脚本顶部的常量直接决定前端资源从哪里加载:

// cf-worker/index.js const ASSET_URL = 'https://etherdream.github.io/jsproxy' const JS_VER = 10 const MAX_RETRY = 1
  • ASSET_URL:默认情况下,Worker 首页所需的静态资源(404.html、sw.js、conf.js等,对应 nginx 版部署中的 www 目录内容)从https://etherdream.github.io/jsproxy反向代理。修改这个常量指向自己的静态资源站(如自己的 GitHub Pages),即可使用自定义的conf.js——也就是配置自己的node_map节点列表、node_default默认节点与线路权重。Worker 对非代理路径的处理就是一行return fetch(ASSET_URL + path)(cf-worker/index.js#L82),因此ASSET_URL站点必须能正常提供这些静态文件。
  • JS_VER:当前脚本版本号(值为 10),会随响应头--ver下发给前端,用于前端判断代理端是否为最新版本(/http端点的"请更新 cfworker"提示即服务于这个探测机制)。
  • MAX_RETRY:内容长度校验失败时的最大重试次数(后文详述)。

需要注意的是,v0.1.0 起后端代理与 cfworker 的接口做过调整以修复缓存失效问题(详见 changelogs/v0.1.0.md),服务端与 cfworker 的版本需配套,不要混用旧接口。

五、源码解析:一次 /http/ 请求的完整链路

cf-worker/index.js全文约 290 行,从源码结构看可分为入口分发、请求头还原、代理转发、响应头改写四层。

5.1 入口与全局错误处理

addEventListener('fetch', e => { const ret = fetchHandler(e) .catch(err => makeRes('cfworker error:\n' + err.stack, 502)) e.respondWith(ret) })

所有请求统一进入 fetchHandler,任何未捕获异常都以 502 返回错误栈,便于排查。makeRes辅助函数(cf-worker/index.js#L26-L30)会给每个响应补上--ver版本头与access-control-allow-origin: *。

5.2 防循环依赖与 CORS preflight

httpHandler 开头有两道检查:

if (reqHdrRaw.has('x-jsproxy')) { return Response.error() }

请求头中一旦带有x-jsproxy,说明流量来自 jsproxy 节点自身(nginx 版在转发前会注入proxy_set_header x-jsproxy 1,见 api.conf#L51),此时直接返回错误,防止 A→B 两个 jsproxy 节点互相代理形成环路。这与 nginx 版/http/location 中CIRCULAR_DEPENDENCY的检查逻辑一一对应(api.conf#L48-L50)。

其次是 OPTIONS 预检请求的处理:当方法为OPTIONS且携带access-control-request-headers时,直接用 PREFLIGHT_INIT 返回 204,允许全部常用方法与max-age: 1728000(20 天)的预检缓存,与 api.conf 中 /preflight 端点 的行为一致。

5.3 请求头还原:为什么参数藏在 Referer 里

这是整个代理协议最核心的设计。浏览器 fetch 对"非简单请求头"会触发 CORS preflight,而 preflight 的max-age是按 URL 记忆缓存的,jsproxy 每次请求的 URL(/http/目标URL)几乎都不同,预检缓存形同虚设。因此 v0.1.0 之后的方案是:把绝大部分请求头字段打包进Referer的 query 部分,因为Referer属于 CORS 安全头,不会触发 preflight。

Worker 侧的解析代码(cf-worker/index.js#L114-L143):

const refer = reqHdrNew.get('referer') const query = refer.substr(refer.indexOf('?') + 1) if (!query) { return makeRes('missing params', 403) } const param = new URLSearchParams(query) for (const [k, v] of Object.entries(param)) { if (k.substr(0, 2) === '--') { // 系统信息 switch (k.substr(2)) { case 'aceh': acehOld = true break case 'raw-info': [rawSvr, rawLen, rawEtag] = v.split('|') break } } else { // 还原 HTTP 请求头 if (v) { reqHdrNew.set(k, v) } else { reqHdrNew.delete(k) } } } if (!param.has('referer')) { reqHdrNew.delete('referer') }

规则可以概括为:

  • query 中--前缀的键是系统参数:--aceh标记前端浏览器不支持access-control-expose-headers: *通配符、需要回退到逐字段列表模式;--raw-info携带原始地址|长度|etag三段信息,用于节点切换时的内容校验;
  • 其余键是待还原的原始请求头:有值则set,空值则delete(前端借此表达"原始请求没有该头");
  • 若参数里没有referer键,说明原始请求不带 referer,把代理请求自带的 referer 删掉。

这段逻辑与 nginx 版的 lua/http-dec-req-hdr.lua 大致相同(cf-worker/index.js#L112 的注释即指向它),只是 Worker 版只实现了--aceh与--raw-info两个系统参数,nginx 版还多解析--ver、--type、--mode、--level(节点切换等级)。

URL 还原处有一个针对 CloudFlare Worker 平台行为的修正(cf-worker/index.js#L145-L150):cfworker 会把路径中的//合并成/,导致https://变成https:/,所以用replace(/^(https?):\/+/, '$1://')把协议后的斜杠补回来,解析失败则返回invalid proxy url(403)。

5.4 转发与响应头改写

proxy 以redirect: 'manual'发起真实请求,不跟随重定向,并只对POST透传请求体。响应处理包含三块逻辑:

1)敏感头转义。四个对浏览器 CORS 语义有"特殊意义"的响应头会被改名为--前缀的版本再下发,防止被浏览器直接执行:

if (k === 'access-control-allow-origin' || k === 'access-control-expose-headers' || k === 'location' || k === 'set-cookie' ) { resHdrNew.set('--' + k, v) ... resHdrNew.delete(k) }

nginx 版由 lua/http-enc-res-hdr.lua#L116-L148 完成同样的转义,且额外处理了重复头(多个Set-Cookie会被编码为1-set-cookie、2-set-cookie……)。前端 ServiceWorker 读到--前缀的头后还原为真实值,从而在浏览器内"重建"目标站点的响应。

2)--aceh回退模式。当请求带--aceh时,脚本会把所有非简单响应头(cache-control、content-type等六个浏览器默认可读的除外)逐一追加进access-control-expose-headers列表,并额外设置--t: 1作为"浏览器是否支持*通配"的探测标记——前端能否读到这个不属于 expose 列表的--t头,即判定是否支持通配。这与 lua/http-enc-res-hdr.lua#L21-L28 的注释逻辑完全对应。

3)内容长度校验与 YouTube 视频修复。若请求携带--raw-info中的期望长度rawLen,脚本会比对实际content-length:

if (badLen) { if (retryTimes < MAX_RETRY) { urlObj = await parseYtVideoRedir(urlObj, newLen, res) if (urlObj) { return proxy(urlObj, reqInit, acehOld, rawLen, retryTimes + 1) } } return makeRes(res.body, 400, { '--error': `bad len: ${newLen}, except: ${rawLen}`, ... }) }

长度不一致时,parseYtVideoRedir 会尝试一个特定修复:仅当 URL 是 YouTube 视频流(host 以.googlevideo.com结尾且路径以/videoplayback开头,见 isYtUrl)且响应体小于 2KB 时,把响应体当作一个新的跳转 URL 解析并重试一次(MAX_RETRY = 1)。这可以推断是针对 YouTubevideoplayback接口偶发二次重定向、导致缓存长度不匹配的问题做的兜底。校验失败则返回 400,错误信息通过--error头下发(配合 api.conf 中 error 端点的 expose 配置 思路一致)。

4)状态码转义。与 nginx 版 lua/http-enc-res-hdr.lua#L42-L52 相同的处理:真实状态码写入--s头(非 200 时),而对外状态码保留原始值用于控制台调试(例如 404 会显示红色);但 301/302/303/307/308 会统一+10变成 311/312/313/317/318——按 CORS 标准 fetch 不允许跟随这类重定向,转义后前端可以自行处理(cf-worker/index.js#L242-L249)。最后脚本还会删除content-security-policy、content-security-policy-report-only、clear-site-data三个头,避免目标站点的 CSP 干扰代理页面。

六、与 nginx 服务端链路的对照

同一套协议在仓库中有两份实现,对照关系如下:

职责cf-worker 实现nginx 实现
代理入口/http/前缀分支(cf-worker/index.js#L69-L71)location /http/(api.conf#L43-L74)
防循环依赖检测x-jsproxy头返回Response.error()重写为/error?msg=CIRCULAR_DEPENDENCY(api.conf#L48-L50)
请求头还原httpHandlerlua/http-dec-req-hdr.lua(多解析--ver/--type/--mode/--level)
响应头编码proxylua/http-enc-res-hdr.lua + lua/http-body-hash.lua
缓存无(依赖 CloudFlare 边缘缓存)proxy_cache my_cache(api.conf#L61)
WebSocket未实现(/ws返回 400)已实现(api.conf#L77-L83 + lua/ws-dec-req-hdr.lua)
外链白名单未实现allowed-sites.conf限制可调用站点(api.conf#L44-L47)

从源码结构看,Worker 版可以视为 nginx 版的"协议子集":它保留了 v0.1.0 新接口的全部关键要素(/http/目标URL路径式 URL、Referer 打包参数、--前缀转义头、--s状态码、30X 转义),但去掉了节点切换(nodeSwitched在 lua/http-enc-res-hdr.lua#L110-L113 中也仅处于注释测试状态)、缓存与白名单能力。

七、当前局限

cf-worker/README.md 明确列出了三个已知问题,与源码相互印证:

  1. WebSocket 代理尚未实现:/ws路径直接返回not support(cf-worker/index.js#L76-L77)。CloudFlare Worker 的 fetch 事件模型本身也不支持长连接透传,这类站点需要回退到 nginx 节点(api.conf#L77-L83)。
  2. 外链限制尚未实现:nginx 版可通过 allowed-sites.conf 限定可调用代理的github.io站点,Worker 版对所有来源一视同仁,access-control-allow-origin恒为*,因此公开部署的 Worker 节点应视为不对外提供服务的私有加速节点,流量风险由节点主自担。
  3. 未充分测试:作者自述以后再完善,生产环境使用前建议先用/works端点和真实站点做充分验证。

八、小结

cf-worker 方案把 jsproxy 的节点侧压缩成了单个可托管文件:约百行的部署流程换来一个每天 10 万请求免费额度的 HTTPS 代理节点;ASSET_URL一个常量即可切换前端配置来源,实现自定义conf.js与多线路负载均衡。其核心工程价值在于展示了如何用"Referer 参数打包 +--前缀头转义 + 状态码转义"这一套协议,在不触发 CORS preflight、不执行远端敏感响应头的前提下,让浏览器里的 ServiceWorker 完整接管目标站点的请求与响应语义。由于 WebSocket 与外链白名单暂未实现,对完整功能有要求的场景仍应选择 nginx 服务端部署(参考 docs/setup.md)。

  • 后端
  • 网络
  • 通信

【免费下载链接】jsproxy

An online proxy based on ServiceWorker

项目地址:https://gitcode.com/gh_mirrors/js/jsproxy
点击查看免费下载
上一篇:Ripple Node.js 适配器 @ripple-ts/adapter-node:用 Web Request/Response 标准 API 编写 Node 服务器
下一篇:Bottlerocket灾备方案:跨区域实例复制与数据备份

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询