本文来自@Dreamy.WJY投稿!博主编辑部分内容!
JSProxy 一个基于浏览器端 JS 实现的在线代理,这里不多介绍!
本文主要介绍一下利用CloudFlare Worker
来搭建一个JSProxy服务。
CloudFlare Worker
是 CloudFlare 的边缘计算服务。开发者可通过 JavaScript 对 CDN 进行编程,从而能灵活处理 HTTP 请求。这使得很多任务可在 CDN 上完成,无需自己的服务器参与。
CFW免费服务,支持每天10 万次免费请求!基本也够用了!
项目介绍
项目地址:https://github.com/EtherDream/jsproxy
准备工作
Cloudflare 账号一个
使用教程
1)打开 https://workers.cloudflare.com,登陆上你的 Cloudflare 账号激活 Workers 服务
然后创建一个 Workers【Create a Worker】
2)修改一下子域名,创建出来的域名格式 自定义的内容.Cloudflare用户名.workers.dev
3)复制 https://raw.githubusercontent.com/EtherDream/jsproxy/master/cf-worker/index.js 的内容到左侧代码(Script)区域
文章最下方有代码备份!!
4)先点击【Run】右侧看执行效果,再点击 【Save and deploy】 部署代码
5)届时你可以访问你的站点https://xxx.子域名.workers.dev查看效果
进入站点后将线路选择切换为当前站点即可使用
特别提示:浏览网站的时候,有时候会提示加载不安全脚本,点击允许即可!
演示站点
此处内容需要 登录 才可见
代码备份
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 |
'use strict' /** * static files (404.html, sw.js, conf.js) */ const ASSET_URL = 'https://zjcqoo.github.io' const JS_VER = 8 const MAX_RETRY = 1 const PREFLIGHT_INIT = { status: 204, headers: new Headers({ 'access-control-allow-origin': '*', 'access-control-allow-methods': 'GET,POST,PUT,PATCH,TRACE,DELETE,HEAD,OPTIONS', 'access-control-allow-headers': '--raw-info,--level,--url,--referer,--cookie,--origin,--ext,--aceh,--ver,--type,--mode,accept,accept-charset,accept-encoding,accept-language,accept-datetime,authorization,cache-control,content-length,content-type,date,if-match,if-modified-since,if-none-match,if-range,if-unmodified-since,max-forwards,pragma,range,te,upgrade,upgrade-insecure-requests,x-requested-with,chrome-proxy,purpose', 'access-control-max-age': '1728000', }), } /** * @param {string} message * @param {number} status * @param {any} headers */ function makeRes(message, status = 200, headers = {}) { headers['cache-control'] = 'no-cache' headers['vary'] = '--url' headers['access-control-allow-origin'] = '*' return new Response(message, {status, headers}) } addEventListener('fetch', e => { const ret = fetchHandler(e) .catch(err => makeRes('cfworker error:' + err, 502)) e.respondWith(ret) }) function fetchHandler(e) { const req = e.request const urlStr = req.url const urlObj = new URL(urlStr) if (urlObj.protocol === 'http:') { urlObj.protocol = 'https:' return makeRes('', 301, { 'strict-transport-security': 'max-age=99999999; includeSubDomains; preload', 'location': urlObj.href, }) } switch (urlObj.pathname) { case '/http': return httpHandler(req) case '/ws': return makeRes('not support', 400) case '/works': return makeRes('it works') default: // static files return fetch(ASSET_URL + urlObj.pathname) } } /** * @param {Request} req */ async function httpHandler(req) { const reqHdrRaw = req.headers if (reqHdrRaw.has('x-jsproxy')) { return Response.error() } // preflight if (req.method === 'OPTIONS' && reqHdrRaw.has('access-control-request-headers') ) { return new Response(null, PREFLIGHT_INIT) } let urlObj = null let extHdrs = null let acehOld = false let rawSvr = '' let rawLen = '' let rawEtag = '' const reqHdrNew = new Headers(reqHdrRaw) reqHdrNew.set('x-jsproxy', '1') for (const [k, v] of reqHdrRaw.entries()) { if (!k.startsWith('--')) { continue } reqHdrNew.delete(k) const k2 = k.substr(2) switch (k2) { case 'url': urlObj = new URL(v) break case 'aceh': acehOld = true break case 'raw-info': [rawSvr, rawLen, rawEtag] = v.split('|') break case 'level': case 'mode': case 'type': break case 'ext': extHdrs = JSON.parse(v) break default: if (v) { reqHdrNew.set(k2, v) } else { reqHdrNew.delete(k2) } break } } if (extHdrs) { for (const [k, v] of Object.entries(extHdrs)) { reqHdrNew.set(k, v) } } if (!urlObj) { return makeRes('missing url param', 403) } /** @type {RequestInit} */ const reqInit = { method: req.method, headers: reqHdrNew, } if (req.method === 'POST') { reqInit.body = req.body } return proxy(urlObj, reqInit, acehOld, rawLen, 0) } /** * * @param {URL} urlObj * @param {RequestInit} reqInit * @param {number} retryTimes */ async function proxy(urlObj, reqInit, acehOld, rawLen, retryTimes) { const res = await fetch(urlObj.href, reqInit) const resHdrOld = res.headers const resHdrNew = new Headers(resHdrOld) let expose = '*' let vary = '--url' for (const [k, v] of resHdrOld.entries()) { if (k === 'access-control-allow-origin' || k === 'access-control-expose-headers' || k === 'location' || k === 'set-cookie' ) { const x = '--' + k resHdrNew.set(x, v) if (acehOld) { expose = expose + ',' + x } resHdrNew.delete(k) } else if (k === 'vary') { vary = vary + ',' + v } else if (acehOld && k !== 'cache-control' && k !== 'content-language' && k !== 'content-type' && k !== 'expires' && k !== 'last-modified' && k !== 'pragma' ) { expose = expose + ',' + k } } if (acehOld) { expose = expose + ',--s' resHdrNew.set('--t', '1') } // verify if (rawLen) { const newLen = resHdrOld.get('content-length') || '' const badLen = (rawLen !== newLen) if (badLen) { if (retryTimes < MAX_RETRY) { urlObj = await parseYtVideoRedir(urlObj, newLen, res) if (urlObj) { return proxy(urlObj, reqInit, acehOld, rawLen, retryTimes + 1) } } return makeRes('error', 400, { '--error': 'bad len:' + newLen }) } if (retryTimes > 1) { resHdrNew.set('--retry', retryTimes) } } let status = res.status resHdrNew.set('access-control-expose-headers', expose) resHdrNew.set('access-control-allow-origin', '*') resHdrNew.set('vary', vary) resHdrNew.set('--s', status) resHdrNew.set('--ver', JS_VER) resHdrNew.delete('content-security-policy') resHdrNew.delete('content-security-policy-report-only') if (status === 301 || status === 302 || status === 303 || status === 307 || status === 308 ) { status = status + 10 } return new Response(res.body, { status, headers: resHdrNew, }) } /** * @param {URL} urlObj */ function isYtUrl(urlObj) { return ( urlObj.host.endsWith('.googlevideo.com') && urlObj.pathname.startsWith('/videoplayback') ) } /** * @param {URL} urlObj * @param {number} newLen * @param {Response} res */ async function parseYtVideoRedir(urlObj, newLen, res) { if (newLen > 2000) { return null } if (!isYtUrl(urlObj)) { return null } try { const data = await res.text() urlObj = new URL(data) } catch (err) { return null } if (!isYtUrl(urlObj)) { return null } return urlObj } |
自荐我写的开源软件:CloudflareSpeedTest
测试 Cloudflare CDN 延迟和速度,获取最快 IP (IPv4+IPv6)!
Go 语言编写,使用简单无依赖,可以用来自选 IP。我邮箱投稿过,但是石沉大海。。。
已经收到投稿。因为最近我再研究另一个类似的项目是网页版的。到时候一并更新文章
原来如此!先谢啦~
后面的那个部署代码的步骤根本无法实现,原来的代码删不掉,并且下面也没有run字样,这个怎么回事?
这个如何绑定www域名呢?
配置好了 https://dash.cloudflare.com 上内嵌的浏览器 可以打开 ,用浏览器打开 报错 (使用了不受支持的协议。
ERR_SSL_VERSION_OR_CIPHER_MISMATCH)
同样的问题
workers.api.error.not_found (Code: 10007)
同样的问题
换一个浏览器就行
谢谢分享!