MHuiG

「看庭前花开花落,望天上云卷云舒」

Github

友链

关于

Travelling

# 初探 Cloudflare Workers 边缘计算

MHuiG

字数：2.2k 字

时长：9 分钟

Edge Computing 尝试在边缘运行 JavaScript

## 什么是边缘计算

边缘运算（Edge computing），又译为边缘计算，是一种分散式运算的架构，将应用程序、数据资料与服务的运算，由网络中心节点，移往网络逻辑上的边缘节点来处理。边缘计算将原本完全由中心节点处理大型服务加以分解，切割成更小与更容易管理的部分，分散到边缘节点去处理。边缘节点更接近于用户终端装置，可以加快资料的处理与传送速度，减少延迟。在这种架构下，资料的分析与知识的产生，更接近于数据资料的来源，因此更适合处理大数据。

OpenStack（是一个由 NASA 和 Rackspace 合作研发并发起的，以 Apache 许可证授权的自由软件和开放源代码项目）社区的定义概念：

“边缘计算是为应用开发者和服务提供商在网络的边缘侧提供云服务和 IT 环境服务；目标是在靠近数据输入或用户的地方提供计算、存储和网络带宽”。

## 章鱼理论秒懂边缘计算

章鱼理论：用 “ 脚 “ 解决问题的边缘计算！

作为无脊椎动物中智商最高的一种动物，章鱼拥有巨量的神经元，但 60% 分布在章鱼的八条腿（腕足）上，脑部仅有 40%。

也就是说：

警告：本文从以下开始跑题。

== 需要 M03-GLORIA 程序特殊访问权限 ==

正在读取资料。请稍等…….

[拒绝访问]
[该数据已被删除]

正在读取备份。请稍等…….

## 本博客边缘计算的搭建方案

本博客目前采用 Vercel、Cloudflare、GithubPages 多线部署，未来可能会考虑添加██████等其他线路。笔者在此解释 Cloudflare 线路的边缘计算部署方案。

正如你所见，静态页面托管存储在 Github 仓库██████中，并开启 GitHub Pages 服务，使用 Cloudflare Workers 实现边缘计算处理逻辑，LeanCloud 数据库存储网站动态数据。

笔者在这里只谈部署思路，由于███████████████████████████████████，相信应该不会有人愿意开源公开自己的后端程序，安全策略以及██████████████████████等等。

### 实现网站镜像

目前 Cloudflare 线路即为 GithubPages 的镜像站，我不说相信你也可以看出来，当然我也可以随时切换为 Vercel 或者██████或者██████的镜像站。

在网络边缘拦截用户请求，并修改请求中的 `request.url.hostname`, 将对用户请求的域解析替换为原始真实 URL，实现隐藏真实网络基础设施的目的。

具体实现可以查阅相关文档

    /**
     * An object with different URLs to fetch
     * @param {Object} ORIGINS
     */
    const ORIGINS = {
      "starwarsapi.yourdomain.com": "swapi.co",
      "google.yourdomain.com": "google.com",
    }

    async function handleRequest(request) {
      const url = new URL(request.url)
      // Check if incoming hostname is a key in the ORIGINS object
      if (url.hostname in ORIGINS) {
        const target = ORIGINS[url.hostname]
        url.hostname = target
        // If it is, proxy request to that third party origin
        return fetch(url.toString(), request)
      }

      // Otherwise, process request as normal
      return fetch(request)
    }

    addEventListener("fetch", event => {
      event.respondWith(handleRequest(event.request))
    })

### 隐藏前端 API 密钥

由于调用数据库 API 需要使用 AppID 和 AppKey：

而直接将他们写在前端页面存在安全风险，因此笔者将数据库 API 密钥直接写在了 Cloudflare Worker 中。

以本站的评论系统为例，在前端页面插入伪造的 API 密钥以作混淆，下图一看就知道是伪造的密钥。

在网络边缘拦截用户请求，判断请求合法性，动态更改请求标头替换为真实的 API 密钥，并向后端数据库转发请求，实现数据交互时隐藏 API 密钥。

具体实现可以查阅相关文档

### 自定义防火墙规则

从 CF-IPCountry HTTP 标头检索 IP 地理位置信息，以判断访问者合法性██████████████████，对于非法访问者████████████████████████████。笔者配合使用████████████数据库实现动态████████████████████████。

How does Cloudflare handle HTTP Request headers?

Configuring Cloudflare IP Geolocation

### 记录请求日志

由于目前 Cloudflare Workers 没有███████████的功能██████████████████，因此笔者使用 Leancloud 数据库的 REST API 发送 HTTP 请求来与 数据库 进行交互。

Cloudflare Workers 默认在事件的 `response` 发送完成后结束边缘的相关进程，这里笔者使用 `event.waitUntil(promise Promise)`, 延长事件的生存期，而不会阻止事件的 `response` 发送。使用此方法可以通知运行时以等待需要比正常的发送响应时间更长的时间运行的任务（例如，日志记录，对第三方服务的分析，流和缓存）。

将所有请求记录到数据库中，以便需要时调查取证或者█████████。

### HTTP2 server push

多路复用，是 HTTP/2 众多协议优化中最令人振奋的特性，它大大降低了网络延迟对性能的影响，而对于资源之间的依赖关系导致的 “延迟”，Server Push 则提供了手动优化方案。

通常，只有在浏览器请求某个资源的时候，服务器才会向浏览器发送该资源。Server Push 则允许服务器在收到浏览器的请求之前，主动向浏览器推送资源。比如说，网站首页引用了一个 CSS 文件。浏览器在请求首页时，服务器除了返回首页的 HTML 之外，可以将其引用的 CSS 文件也一并推给客户端。

本站在返回的请求中添加 HTTP 标头实现 HTTP2 server push。

    if(request.headers.get("content-type")=="text/html; charset=utf-8"){
       response.headers.set("Link", "</css/style.css>; rel=preload; as=style,</js/app.js>; rel=preload; as=script, <https://static.mhuig.top>; rel=preconnect; crossorigin, <https://static.mhuig.top>; rel=dns-prefetch")
    }

具体实现可以查阅相关文档

### CORS header proxy

由于██████████████████需要前端实现跨域，可以直接添加 HTTP 标头实现。

    const corsHeaders = {
      "Access-Control-Allow-Origin": "*",
      "Access-Control-Allow-Methods": "GET, HEAD, POST, OPTIONS",
      "Access-Control-Allow-Headers": "Content-Type",
    }

具体实现可以查阅相关文档

## 其他应用

更多 Examples

[文件已删除]

### 搭建其他镜像站

上面介绍镜像自己的博客，如法炮制，你也可以镜像其他网站，例如 Github、█████████、█████████、█████████等，请勿用于非法用途，否则后果自负。

下面是一个随便在百度中搜索到的脚本，几乎随便一搜都可以找到它，大家不会都用一个吧。

    // 你想镜像的站点
    const upstream = 'zh.wikipedia.org'

    // 针对移动端适配站点，没有就和保持和上面一致
    const upstream_mobile = 'zh.wikipedia.org'

    // 禁止某些地区访问
    const blocked_region = []

    // 禁止自访问
    const blocked_ip_address = ['0.0.0.0', '127.0.0.1']

    // 你想镜像的站点
    const replace_dict = {
        '$upstream': '$custom_domain',
        '//zh.wikipedia.org': ''
    }

    // 剩下的就不用管了
    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_host = url.host;

        if (url.protocol == 'http:') {
            url.protocol = 'https:'
            response = Response.redirect(url.href);
            return response;
        }

        if (await device_status(user_agent)) {
            upstream_domain = upstream
        } else {
            upstream_domain = upstream_mobile
        }

        url.host = upstream_domain;

        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', upstream_domain);
            new_request_headers.set('Referer', url.href);

            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_host);
            } 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;
    }

### Cloudflare workers blog

cloudflare-worker-blog

一个在 Github 上的开源实例，██████████████████████████████████████████████████
██████████████████████████████████Cloudflare workers + Github 实现的动态博客系统█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████。
。

### jsproxy

jsproxy 一个基于浏览器端 JS 实现的在线代理，████████████████████████████████████████

█████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████。

项目主要用于以下技术的研究：

- 网站镜像 / 沙盒化
- 钓鱼网站检测技术
- 前端资源访问加速

█████████████████████████████，否则█████████████。

### 加速 Google Analytics

cloudflare-workers-async-google-analytics

The Cloudflare Workers implementation of an async Google Analytics.

██████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████████。

以上代码，请勿将其用于非法用途，否则后果自负。

严禁未经授权的人员进行访问

肇事者将被监控，定位并处理

初探 Cloudflare Workers 边缘计算
https://blog.mhuig.top/p/4a0f523a/
本文作者
MHuiG
发布于
2020年8月31日
许可协议
CC BY-NC-SA 4.0
署名-非商业性使用-相同方式共享 4.0 国际。
边缘计算 边缘计算

边缘计算

边缘计算

发布于：2020年8月31日

更新于：2020年8月31日

How to Setup Your Python Environment for Machine Learning With Anaconda

In this tutorial, we will cover the following steps: 1.Download Anaconda 2.Install Anaconda 3.St...

Spark 系列专题

.fa-secondary{opacity:.4} Spark Spark 开始阅读

推荐阅读
Django添加新的路由 Django将发布内容动态显示到页面上 Django带文件的表单上传 JavaScript 反调试 Nginx配置 开放 API 接口

留言区

Are You A Robot?