/ 技术 / 2587浏览

使用Cloudflare Workers免费加速访问任意网站

文章目录
  • Cloudflare介绍
  • 准备工作
  • 搭建教程
  • Cloudflare介绍

    Cloudflare成立于2009年,是一家美国的跨国科技企业,总部位于旧金山,在英国伦敦亦设有办事处。Cloudflare以向客户提供网站安全管理、性能优化及相关的技术支持为主要业务。通过基于反向代理的内容分发网络(CDN, Content Delivery Network)、任播(Anycast)技术 [1] 、基于nginx+lua架构的Web应用防火墙(WAF, Web Application Firewall) [2] 及分布式域名解析服务(Distributed Domain Name Server)等技术,Cloudflare可以帮助受保护站点抵御包括分布式拒绝服务攻击(DDoS, Distributed Denial of Service)在内的大多数网络攻击,确保该网站长期在线,同时提升网站的性能、访问速度以改善访客体验。

    官方网站:https://www.cloudflare.com/

    Cloudflare Workers是Cloudfare的边缘计算服务,它可以将更多个性化和交互性纳入静态 HTML 页面,并在边缘运行动态请求。将更多操作流程和请求处理转移到边缘,以提高缓存命中率并降低带宽成本。我们可以使用Cloudflare Workers 通过 JavaScript 对 CDN 进行编程,从而能灵活处理 HTTP 请求,最终达到加速静态资源, 加速任意网站的目的, 不需要购买服务器, 还可以绑定自己的自定义域名。如果域名是备案域名的话,还可以使用国内的CDN加速体验更好。推荐使用百度云加速,免费且支持https,国内百度云加速是Cloudfare的中国合作商。

    准备工作

    Cloudflare 账号,可以到官网进行注册。

    搭建教程

    打开https://workers.cloudflare.com/登录账号并激活并创建workers,可选择创建免费版和收费版,免费版每天有10万次请求,对于个人来说完全够用!

    创建一个Workers子域,这个子域创建之后是不能再修改的。

    然后进入控制页面,在左上角可以修改访问的域名。

    强大的全局加速方式

    项目地址:https://github.com/EtherDream/jsproxy/tree/master/cf-worker

    代码

    'use strict'
    
    /**
     * static files (404.html, sw.js, conf.js)
     */
    const ASSET_URL = 'https://etherdream.github.io/jsproxy'
    
    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
    }

    部署

    复制index.js里的代码(或者复制上面的代码)粘贴到代码框里。

    ①处修改子域名。

    ②是粘贴代码的地方。

    ③是进行预览调试的区域。

    最后点击保存并部署就行了

    定向加速版

    项目地址: https://github.com/Siujoeng-Lau/Workers-Proxy/blob/master/src/index.js

    代码

    // Website you intended to retrieve for users.
    const upstream = 'www.google.com'
    
    // Custom pathname for the upstream website.
    const upstream_path = '/'
    
    // Website you intended to retrieve for users using mobile devices.
    const upstream_mobile = 'www.google.com'
    
    // Countries and regions where you wish to suspend your service.
    const blocked_region = ['CN', 'KP', 'SY', 'PK', 'CU']
    
    // IP addresses which you wish to block from using your service.
    const blocked_ip_address = ['0.0.0.0', '127.0.0.1']
    
    // Whether to use HTTPS protocol for upstream address.
    const https = true
    
    // Replace texts.
    const replace_dict = {
        '$upstream': '$custom_domain',
        '//google.com': ''
    }
    
    addEventListener('fetch', event => {
        event.respondWith(fetchAndApply(event.request));
    })
    
    async function fetchAndApply(request) {
    
        const region = request.headers.get('cf-ipcountry').toUpperCase();
        const ip_address = request.headers.get('cf-connecting-ip');
        const user_agent = request.headers.get('user-agent');
    
        let response = null;
        let url = new URL(request.url);
        let url_hostname = url.hostname;
    
        if (https == true) {
            url.protocol = 'https:';
        } else {
            url.protocol = 'http:';
        }
    
        if (await device_status(user_agent)) {
            var upstream_domain = upstream;
        } else {
            var upstream_domain = upstream_mobile;
        }
    
        url.host = upstream_domain;
        if (url.pathname == '/') {
            url.pathname = upstream_path;
        } else {
            url.pathname = upstream_path + url.pathname;
        }
    
        if (blocked_region.includes(region)) {
            response = new Response('Access denied: WorkersProxy is not available in your region yet.', {
                status: 403
            });
        } else if (blocked_ip_address.includes(ip_address)) {
            response = new Response('Access denied: Your IP address is blocked by WorkersProxy.', {
                status: 403
            });
        } else {
            let method = request.method;
            let request_headers = request.headers;
            let new_request_headers = new Headers(request_headers);
    
            new_request_headers.set('Host', url.hostname);
            new_request_headers.set('Referer', url.hostname);
    
            let original_response = await fetch(url.href, {
                method: method,
                headers: new_request_headers
            })
    
            let original_response_clone = original_response.clone();
            let original_text = null;
            let response_headers = original_response.headers;
            let new_response_headers = new Headers(response_headers);
            let status = original_response.status;
    
            new_response_headers.set('access-control-allow-origin', '*');
            new_response_headers.set('access-control-allow-credentials', true);
            new_response_headers.delete('content-security-policy');
            new_response_headers.delete('content-security-policy-report-only');
            new_response_headers.delete('clear-site-data');
    
            const content_type = new_response_headers.get('content-type');
            if (content_type.includes('text/html') && content_type.includes('UTF-8')) {
                original_text = await replace_response_text(original_response_clone, upstream_domain, url_hostname);
            } else {
                original_text = original_response_clone.body
            }
    
            response = new Response(original_text, {
                status,
                headers: new_response_headers
            })
        }
        return response;
    }
    
    async function replace_response_text(response, upstream_domain, host_name) {
        let text = await response.text()
    
        var i, j;
        for (i in replace_dict) {
            j = replace_dict[i]
            if (i == '$upstream') {
                i = upstream_domain
            } else if (i == '$custom_domain') {
                i = host_name
            }
    
            if (j == '$upstream') {
                j = upstream_domain
            } else if (j == '$custom_domain') {
                j = host_name
            }
    
            let re = new RegExp(i, 'g')
            text = text.replace(re, j);
        }
        return text;
    }
    
    
    async function device_status(user_agent_info) {
        var agents = ["Android", "iPhone", "SymbianOS", "Windows Phone", "iPad", "iPod"];
        var flag = true;
        for (var v = 0; v < agents.length; v++) {
            if (user_agent_info.indexOf(agents[v]) > 0) {
                flag = false;
                break;
            }
        }
        return flag;
    }

    部署

    需要修改1,2处的域名为自己需要加速的域名,2处的是手机访问加速的域名。3处是屏蔽的地区。修改完成后保存部署即可完成。

    v1
    这里荒芜寸草不生 后来你来这走了一遭 奇迹般万物生长 这里是我的心

    4

    1. Space520

      留个言,码住啦,说不定以后自己会用到,感谢博主分享。

      1. LAL

        @Space520Cloudflare现在的访问速度很慢,不推荐使用了😂。

    2. Bensz

      这个定向加速,是不是意味着也可以定向加速自己布署在国外VPS上的chevereto图床?方案似乎不错!

      1. LAL

        @Bensz是的,其实用途也可以代理那些不可访问的网站 :flower-heihei: ,就是现在cf的cdn速度在国内不太行了,效果没那么好了😂

    发表回复

    您的邮箱地址不会被公开。 必填项已用 * 标注