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


    今天看到阮一峰老师的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
    3. 进入你fork的jsproxy项目的source(源代码)中,切换到gh-pages branch分支,新建一个index.html,内容为空即可。
    4. 访问你Github pages服务生成的网址,如果为白屏,没有报404错误的话,回到刚才的源代码,将index.html删除即可。3、4两部可以在Github里直接操作,也可以用git命令拉取到本地进行修改,这里不再赘述。
    5. 如果你想自定义页面的样式,可以修改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
    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
    279
    280
    281
    282
    283
    284
    285
    286
    287
    288
    289
    290
    '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: ' + 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
    4. 修改example.yourwebstie.com的DNS记录为cname记录,这条cname记录指向cloudflare分配给你的workers.dev下刚刚部署好的workers站点。

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

  • 相关阅读:
    PHP unset销毁变量并释放内存
    编码问题
    编程中关于对时区的理解(语言PHP)
    简单树结构的实现
    设计模式
    Selenium WebDriver +Python讲解
    PS入门基础-魔幻调色
    .NET Core、.NET Standard 、ASP.NET Core 和 .NET Framework 有什么不同?
    电商设计
    NPOI相关学习文档
  • 原文地址:https://www.cnblogs.com/jameswohu/p/14068326.html
Copyright © 2020-2023  润新知