前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >利用cloudflare-works边缘计算搭建在线网页代理

利用cloudflare-works边缘计算搭建在线网页代理

作者头像
jamesjiang
发布2022-11-20 14:41:09
1.7K0
发布2022-11-20 14:41:09
举报
文章被收录于专栏:james技术专栏james技术专栏

今天看到阮一峰老师的Twitter发的“关于Cloudflare 正式发布 workers 功能”,搜索了一下关于 workers 功能使用教程,找了一篇文章(Xiaomage’s Blog 利用cloudflare works边缘计算搭建在线网页代理)还不错,先码后续继续研究。

工具

  1. 开源项目jsproxy
  2. 一个cloudflare账号
  3. 一个Github账号,或者一台服务器+域名

一点说明:

要利用cloudflare works边缘计算搭建在线网页代理,需要用到大神EtherDream的开源项目jsproxy

这个项目使用了Service Worker,它能让 JS 拦截网页产生的请求,并能自定义返回内容,相当于在浏览器内部实现一个反向代理。这使得绝大部分的内容处理都可以在浏览器上完成,服务器只需纯粹的转发流量。

你可以使用Github pages服务,快速搭建起页面前端,从而做到真正的serverless。当然,如果你有一台服务器+域名,你也可以把服务器放在自己的服务器上。这一步只是给cloudflare一个回源服务器,用户访问的一切流量都要经过cloudflaer服务器,而不是Github或者你的服务器。所以服务器位置并不会影响网页代理的速度,而是用户到所连接到的cloudflare服务器的速度。建议使用Github pages的服务即可,下面的教程也将演示利用Github pages搭建此代理的过程。

操作步骤

GitHub方面

  1. 登录你的Github账号,fork jsproxy项目到你的仓库中
  2. 进入你fork的jsproxy项目的setting中,启用下方的Github pages,其中项目分支选择gh-pages branch分支即可,配置见下图:
图1
图1
  1. 进入你fork的jsproxy项目的source(源代码)中,切换到gh-pages branch分支,新建一个index.html,内容为空即可。
  2. 访问你Github pages服务生成的网址,如果为白屏,没有报404错误的话,回到刚才的源代码,将index.html删除即可。3、4两部可以在Github里直接操作,也可以用git命令拉取到本地进行修改,这里不再赘述。
  3. 如果你想自定义页面的样式,可以修改gh-pages branch分支中的index_v3.html

Cloudflare方面

  1. https://dash.cloudflare.com/登录你的cloudflare账号,点击右侧大大的workers进入workers控制面板。
  2. 第一次使用workers功能,需要完成一个新手引导教程。第一步,需要选择一个cloudflare提供的*.workers.dev的二级域名,根据自己的喜好填写,按照提示next就可以了。
  3. 新手教程第二步会让你选择plan,我们白嫖党当然要选择Free Plan啦,每天有100000个请求配额,个人使用绰绰有余。
  4. 下一步可能要验证邮箱,到注册cloudflare的邮箱里点击链接激活一下就可以。
  5. 完成新手引导后,回到workers面板,点击蓝色的Create a Worker按钮,新建一个worker。
  6. 这时会打开一个带有代码编辑器的新标签页,在左侧选择Script标签,粘贴以下内容:注意在第六行里填写好你Github pages的网址,即https://xxx.github.io/jsproxy
图2
图2
代码语言:javascript
复制
'use strict'

/**
 * static files (404.html, sw.js, conf.js)
 */
const ASSET_URL = 'https://xxx.github.io/jsproxy'//这里填写你Github pages的网址!

const JS_VER = 10
const MAX_RETRY = 1

/** @type {RequestInit} */
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-max-age': '1728000',
  }),
}

/**
 * @param {any} body
 * @param {number} status
 * @param {Object<string, string>} headers
 */
function makeRes(body, status = 200, headers = {}) {
  headers['--ver'] = JS_VER
  headers['access-control-allow-origin'] = '*'
  return new Response(body, {status, headers})
}


/**
 * @param {string} urlStr 
 */
function newUrl(urlStr) {
  try {
    return new URL(urlStr)
  } catch (err) {
    return null
  }
}


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


/**
 * @param {FetchEvent} e 
 */
async function fetchHandler(e) {
  const req = e.request
  const urlStr = req.url
  const urlObj = new URL(urlStr)
  const path = urlObj.href.substr(urlObj.origin.length)

  if (urlObj.protocol === 'http:') {
    urlObj.protocol = 'https:'
    return makeRes('', 301, {
      'strict-transport-security': 'max-age=99999999; includeSubDomains; preload',
      'location': urlObj.href,
    })
  }

  if (path.startsWith('/http/')) {
    return httpHandler(req, path.substr(6))
  }

  switch (path) {
  case '/http':
    return makeRes('请更新 cfworker 到最新版本!')
  case '/ws':
    return makeRes('not support', 400)
  case '/works':
    return makeRes('it works')
  default:
    // static files
    return fetch(ASSET_URL + path)
  }
}


/**
 * @param {Request} req
 * @param {string} pathname
 */
function httpHandler(req, pathname) {
  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 acehOld = false
  let rawSvr = ''
  let rawLen = ''
  let rawEtag = ''

  const reqHdrNew = new Headers(reqHdrRaw)
  reqHdrNew.set('x-jsproxy', '1')

  // 此处逻辑和 http-dec-req-hdr.lua 大致相同
  // https://github.com/EtherDream/jsproxy/blob/master/lua/http-dec-req-hdr.lua
  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')
  }

  // cfworker 会把路径中的 `//` 合并成 `/`
  const urlStr = pathname.replace(/^(https?):\/+/, '$1://')
  const urlObj = newUrl(urlStr)
  if (!urlObj) {
    return makeRes('invalid proxy url: ' + urlStr, 403)
  }

  /** @type {RequestInit} */
  const reqInit = {
    method: req.method,
    headers: reqHdrNew,
    redirect: 'manual',
  }
  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 = '*'
  
  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 (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(res.body, 400, {
        '--error': `bad len: ${newLen}, except: ${rawLen}`,
        'access-control-expose-headers': '--error',
      })
    }

    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('--s', status)
  resHdrNew.set('--ver', JS_VER)

  resHdrNew.delete('content-security-policy')
  resHdrNew.delete('content-security-policy-report-only')
  resHdrNew.delete('clear-site-data')

  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
}

之后点击下方的save and deploy部署就生效啦!记下cloudflare分配给你的workers.dev的三级域名,这就是你部署好的在线代理网址。

如果你正巧有托管在Cloudflare或它旗下的Partner的话,你可以就可以自定义网页代理的域名,不必记下冗长的三级域名,方法如下:

  1. 进入你的域名控制台,点击控制台顶部的workers标签,进入对应域名的workers设置。
  2. 点击右侧的Add route按钮,部署一条新规则。
  3. 在弹出的对话框中:Route中填写 example.yourwebstie.com/* ,其中example是网页代理的二级域名,可以自定义,Worker选择你刚刚部署的worker。
图3
图3
  1. 修改example.yourwebstie.com的DNS记录为cname记录,这条cname记录指向cloudflare分配给你的workers.dev下刚刚部署好的workers站点。

Then, 在你使用没有FQ软件的电脑时,也能利用这个网页代理随心上谷歌看油管咯~enjoy it!

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2020-12-01,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 工具
  • 操作步骤
    • GitHub方面
      • Cloudflare方面
      相关产品与服务
      访问管理
      访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
      领券
      问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档