演示
更新
- 2019-05-30 更新 cfworker,对 ytb 视频进行了优化(推荐选 1080p+,不会增加服务器压力)
- 2019-05-29 nginx 增加静态资源服务,可同时支持代理接口和首页访问
- 2019-05-27 增加 nio.io、sslip.io 后备域名,减少申请失败的几率
- 2019-05-26 安装时自动申请证书(使用 xip.io 域名),安装后即可预览
安装
curl https://raw.githubusercontent.com/EtherDream/jsproxy/master/i.sh | bash
- 自动安装目前只支持 Linux x64,并且需要 root 权限
- 安装过程中 80 端口能被外网访问(申请 HTTPS 证书)
https://服务器IP.xip.io:8443
(具体参考脚本输出)部署
gh-pages
分支,编辑 conf.js
文件:- 节点列表(
node_map
字段,包括节点 id 和节点主机) - 默认节点(
node_default
字段,指定节点 id)
https://用户名.github.io/jsproxy
预览。维护
# 切换到 jsproxy 用户
su - jsproxy
# 重启服务
./run.sh reload
# 关闭服务(参数和 nginx -s 相同)
./run.sh quit
# 启动服务
./run.sh
# 查看代理日志
tail server/nginx/logs/proxy.log
禁止外链
github.io
子站点调用,这可能导致不必要的流量消耗。allowed-sites.conf
。(重启服务生效)安全策略
setup-ipset.sh
:/home/jsproxy/server/setup-ipset.sh
需要 root 权限,依赖ipset
命令
jsporxy
用户访问保留 IP 段(针对 TCP)。nginx 之外的程序也生效,但不影响其他用户。项目特点
服务端开销低
API 虚拟化
document.domain
得到的仍是 a.com。这可能导致某些业务逻辑出现问题。location
,本代理会将代码中字面出现的 location
替换成 __location
,从而将操作转移到自定义对象上。当然对于非字面的情况(例如 this['lo' + 'cation']
),目前还无法处理。类似项目
zmirror
php-proxy
项目意义
- 网站镜像 / 沙盒化
- 钓鱼网站检测技术
- 前端资源访问加速
手动安装
创建用户
jsproxy
用户(nobody
组),并切换:groupadd nobody
useradd jsproxy -g nobody --create-home
su - jsproxy
为什么要创建用户?因为使用低权限运行服务可减少风险。另外在防 SSRF 脚本setup-ipset.sh
中,是通过 iptalbes 的uid-owner
策略阻止jsprxoy
这个特定用户访问内网的。
安装 nginx
cd $(mktemp -d)
curl -O https://www.openssl.org/source/openssl-1.1.1b.tar.gz
tar zxf openssl-*
curl -O https://ftp.pcre.org/pub/pcre/pcre-8.43.tar.gz
tar zxf pcre-*
curl -O https://zlib.net/zlib-1.2.11.tar.gz
tar zxf zlib-*
curl -O https://openresty.org/download/openresty-1.15.8.1.tar.gz
tar zxf openresty-*
cd openresty-*
export PATH=$PATH:/sbin
./configure \
--with-openssl=../openssl-1.1.1b \
--with-pcre=../pcre-8.43 \
--with-zlib=../zlib-1.2.11 \
--with-http_v2_module \
--with-http_ssl_module \
--with-pcre-jit \
--prefix=$HOME/openresty
make
make install
configure
的参数 --prefix
指定 nginx 安装路径,这里为方便设为用户主目录。注意编译后的 nginx 程序不能改变位置,否则会启动失败
~/openresty/nginx/sbin/nginx -h
安装代理程序
jsproxy
用户的主目录:cd ~
git clone --depth=1 https://github.com/EtherDream/jsproxy.git server
www
目录:cd server
rm -rf www
git clone -b gh-pages --depth=1 https://github.com/EtherDream/jsproxy.git www
./run.sh
申请域名
- 临时测试:
服务器IP.xip.io
nip.io
、sslip.io
,自动安装脚本默认使用 xip.io
。申请证书
[浏览器] --- https ---> [CloudFlare] --- http ---> [服务器]
为什么一定要用 HTTPS?因为本项目使用了浏览器 Service Worker 技术,该 API 只能在安全环境使用,除了 localhost、127.0.0.0/8 站点可以使用 HTTP,其他必须 HTTPS。
支持系统
简介
CloudFlare Worker
是 CloudFlare 的边缘计算服务。开发者可通过 JavaScript 对 CDN 进行编程,从而能灵活处理 HTTP 请求。这使得很多任务可在 CDN 上完成,无需自己的服务器参与。部署
Start building
,取一个子域名,Create a Worker
。Save and deploy
。如果正常,右侧应显示首页。https://xxxx.子域名.workers.dev
,以后可直接访问。计费
overview
页面可参看使用情况。免费版每天有 10 万次免费请求,对于个人通常足够。conf.js
中配置多线路负载均衡。或者升级到 $5 的高级版本,每月可用 1000 万次请求(超出部分 $0.5/百万次请求)。加速功能
。修改配置
https://zjcqoo.github.io
反向代理,可通过代码中 ASSET_URL
配置,从而可使用自定义的 conf.js
配置。加速功能
conf.js
的 cfworker
节点 lines
配置。lua/http-enc-res-hdr.lua
的 114-116 行 注释打开,重启服务生效。存在问题
- WebSocket 代理尚未实现
- 外链限制尚未实现
- 未充分测试,以后再完善
利用CloudFlare Workers搭建JSproxy
- 搭建博客(就是本博客了)
- 搭建网页代理(就是我接下去要讲的。)
- and so on…… (等待大佬开发了。)
- 登录https://github.com
- 去https://github.com/EtherDream/jsproxy 这里Fork大佬的项目
- 打开https://workers.cloudflare.com/ ,登陆后如果没玩过worker的会让你创建一个域名类似:xxx.workers.dev,想好这个“xxx”因为无法修改
- 创建一个项目,复制
https://github.com/XXX/jsproxy/blob/master/cf-worker/index.js
的代码到Workers工作台 - 注意修改链接XXX为自己的Github用户名
- 进入
gh-pages
分支,编辑conf.js
文件然后修改默认那个cfworker
的url
为Workers
应用的链接 - 再去
https://github.com/XXX/jsproxy/blob/master/lua/http-enc-res-hdr.lua#L114
打开上下三行的注释 - 同样注意修改链接XXX为自己的Github用户名
- 访问访问
https://XXX.github.io/jsproxy/index.html
预览。
Cloudflare Workers 初探——以 G2WW 作为例子转发 Grafana 报警到企业微信
Cloudflare Blog 发布了一篇文章:「Everyone can now run JavaScript on Cloudflare with Workers」,标志着 Cloudflare 的 “Serverless” 平台的 “GA”,作为一个标榜 “Edge computing for everyone” 的产品,Cloudflare Workers 或许是我们能接触到的最 “亲民” 的 Serverless 产品。
什么是 Serverless
Wiki 对于 Serverless Computing 的解释是:
Serverless computing is a cloud computing execution model in which the cloud provider runs the server, and dynamically manages the allocation of machine resources. Pricing is based on the actual amount of resources consumed by an application, rather than on pre-purchased units of capacity.
我对于 Serverless 的理解是:
- 尽可能使用云服务商的资源(不一定是服务器,主要是云函数和对象存储)
- 编写业务逻辑与云服务商的服务(例如数据库)交互.
对于静态内容来说,可以直接通过 S3 (搭配 Cloudfront)进行输出,对于动态的请求来说,只需要自己写好业务逻辑,去请求后端的服务即可,然后,无需考虑自己服务器部署,维护等内容,且真正按量计费。
什么是 Cloudflare Workers
相比较传统的 Serverless 框架来说,Cloudflare Workers 更近了一步,传统的 Serverless 结构还是有一个中心化的部署(比如应用就部署在 us-west 区域),甚至某些厂商认为自己的 Managed Docker 也是 Serverless,非常迷惑,前面是服务商的 CDN,而 Cloudflare 的函数部分是全球化的,计算逻辑直接跑在边缘节点上,依托他们的全球 Anycast 网络。这一点类似 Amazon 的 Lambda@Edge
Cloudflare 对于自己的 Workers 是这么介绍的:
You write code. We handle the rest.
以及:
Deploy serverless code to data centers across 200 cities in 95 countries to give it exceptional performance and reliability.
相对比传统的业务逻辑,我们将应用部署在自己的服务器上,并使用 Cloudflare 进行 CDN 加速(或者减速),终端用户的所有请求(一般来说就是 HTTP 请求啦)会先通过 Cloudflare 的网络,然后再由 Cloudflare 走公网反向代理到自己的应用服务器上,等应用服务器完成处理后请求再通过 Cloudflare 的边缘节点重新返回给用户(这个过程称为:回源)。
这么一来有两个问题:
- 回源需要时间
对于一个源站在日本的服务器来说,如果一个欧洲用户访问,那么用户的访问请求会通过 Cloudflare 位于欧洲的服务器返回到日本的源站上,期间直接走公网,如果你对 Cloudflare 回源机制还不熟悉的话,可以参考我的博文「搭建 Cloudflare 背后的 IPv6 AnyCast 网络」,内部讲解了 Cloudflare 回源机制,以及 Cloudflare 的增值服务——Argo。
- 计算的压力全部落在了源站上
由于是一个中心化的源站(当然,如果你使用了负载均衡器等另说),来自不同地区的访客的计算压力会全部落在自己的架构上。
Cloudflare Workers 的出现可以解决以上两个问题,Workers 的工作模式是在 Cloudflare 的 200+ 个数据中心(边缘节点)上直接运行用户的 Javascript 代码,所有的计算全部在边缘进行,此外,为了保证 Stateful 的服务,Workers 还提供一个全球低延迟(利用 Cloudflare 内网)的 KV 键值对数据库用于边缘应用的读写。
除了这些以外,还有一些小特性值得关注:
- 小于 5ms 的代码冷启动时间
- 15 秒内推送到 Cloudflare 的所有数据中心(边缘节点)
- 毫秒级的运行速度
Cloudflare Workers 效果
别的不多说,我们先给一个对比图,以下是 webp-sh/webp_server_go,的官网: https://webp.sh 的内容,我们观察两种不同的使用 Cloudflare 的方式:
Cloudflare 直接反向代理
结构如下,网站的静态内容放置在 GCP 的日本 Osaka,使用的 Premium 网络,即使用了「冷土豆路由」,保证服务器到访客之间尽可能多的在 Google 内网内,此外,前端加入 Cloudflare 反向代理。
我们从 ping 值会发现,由于 Cloudflare 有 Anycast,在海外部分 ping 的延迟符合 Cloudflare 的预期.
这是因为我们的 ICMP 包在 Cloudflare 的边缘节点就已经有了 echo,然而对于网页访问来说是 HTTP 的 GET 请求,这里评判用户看到网页速度的要素不只是 ping 值,还有 TTFB:
TTFB measures the duration from the user or client making an HTTP request to the first byte of the page being received by the client’s browser. This time is made up of the socket connection time, the time taken to send the HTTP request, and the time taken to get the first byte of the page. Although sometimes misunderstood as a post-DNS calculation, the original calculation of TTFB in networking always includes network latency in measuring the time it takes for a resource to begin loading.
可以看到,在日本地区 TTFB 最小(因为距离最短,日本访客 -> Cloudflare 日本节点 -> 日本源站),而在德国,TTFB 就大的多了。
Cloudflare Workers Site
这里直接使用了 Cloudflare Workers Site 的功能,将 webp.sh 的网页直接推到 Workers 的 KV 存储中,并使用一个临时域名进行展示,可以看到,由于直接从 Cloudflare 边缘节点进行输出(没有回源的开销),这个时候的 TTFB 就已经大幅度减少.
Cloudflare Workers 怎么用
相信看到上面的例子你应该已经大概明白了 Cloudflare Workers 是啥,以及它所能带来的好处了,现在我们看看 Cloudflare Workers 怎么用,可惜的是,目前在 Google 上搜索「Cloudflare Workers」相关除了看到 Sukka 的一篇「将 Hexo 部署到 Cloudflare Workers Site 上的趟坑记录」以外就只能看到大家都在用 EtherDream 写的 JSProxy 做代理翻墙…
我们来看看 Cloudflare Workers 怎么用吧。
安装 Wrangler
这一步其实比较简单,在安装好 NPM 后直接按照 Quick Start 安装就好:
npm install -g @cloudflare/wrangler
然后就可以
wrangler generate my-router-app
创建一个 APP 了,这里建议先通篇阅读一下上述的 Quick Start.index.js
在安装好 APP 之后,你就有
开局一个index.js
可以用了,内容一般是这样的:addEventListener('fetch', event => { event.respondWith(handleRequest(event.request)) }) /** * Respond with hello worker text * @param {Request} request */ async function handleRequest(request) { return new Response('Hello worker!', { headers: { 'content-type': 'text/plain' }, }) }
警告:由于我没有学过 Javascript ,以下纯属瞎掰,仅供娱乐使用
这里的
addEventListener
是整个程序的入口,目前 Cloudflare Workers 只支持FetchEvent
,这一种。G2WW
好了,标题中我们提到了 G2WW,这里就以 G2WW 作为例子进行演示 Worker 的一个用法,我们知道 Grafana 的报警 Channel 中是没有「企业微信」的(但是有 DingDing,很奇怪),所以为了保证企业微信用户也可以通过 Grafana 进行报警,我们需要做一点改装。
企业微信的一个"接口"就是「机器人」,它支持 Webhook 调用,只需要按照他们的规范 POST 一个数据即可,他们的规范如下:
{ "msgtype": "news", "news": { "articles": [ { "title": "这里是标题", "description": "你的服务掉线了", "url": "https://status.xxx.xxx/grafana/xxx", "picurl": "https://kongbu.de.diaoxian.tupian/1.jpg" } ] } }
Grafana 支持一个 Webhook 报警,Example 如下:
{ "dashboardId":1, "evalMatches":[ { "value":1, "metric":"Count", "tags":{} } ], "imageUrl":"https://grafana.com/static/assets/img/blog/mixed_styles.png", "message":"Notification Message", "orgId":1, "panelId":2, "ruleId":1, "ruleName":"Panel Title alert", "ruleUrl":"http://localhost:3000/d/hZ7BuVbWz/test-dashboard?fullscreen\u0026edit\u0026tab=alert\u0026panelId=2\u0026orgId=1", "state":"alerting", "tags":{ "tag name":"tag value" }, "title":"[Alerting] Panel Title alert" }
那么我们的需求就很明确了,就是需要将 Grafana Webhook 的报警内容提取需要的字段并转发到企业微信的接口上即可,该怎么做呢?
首先我们需要区分请求是 GET 请求还是 POST 请求:
addEventListener('fetch', event => { const { request } = event if (request.method === 'POST') { return event.respondWith(postWeChatUrl(request)) } else if (request.method === 'GET') { return event.respondWith(new Response(banner_content, { headers: { 'content-type': 'text/html' }, })) } })
对于 POST 请求,我们对内容进行处理,并转发到企业微信的 API 地址上:
async function postWeChatUrl(request) { var key = request.url.replace(/^https:\/\/.*?\//gi, "") // 这里我们拼接一下,对于访问 https://g2ww-serverless.nova.moe/xxxxxx-xxxxxx 的请求就自动发到 https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=xxxxxx-xxxxxx 下 var wechat_work_url = "https://qyapi.weixin.qq.com/cgi-bin/webhook/send?key=" + key var json_obj = await request.json() // 重新构造 JSON var template = { "msgtype": "news", "news": { "articles": [ { "title": json_obj['title'], "description": json_obj['message'], "url": json_obj['ruleUrl'], "picurl": json_obj['imageUrl'] } ] } } const init = { body: JSON.stringify(template), method: 'POST', headers: { 'Content-Type': 'application/json', }, } // 发出请求 const response = await fetch(wechat_work_url, init) // 准备结果 const results = await gatherResponse(response) return new Response(results, init) }
最后处理一下返回值并返回给客户端就可以了:
async function gatherResponse(response) { const { headers } = response const contentType = headers.get('content-type') || '' if (contentType.includes('application/json')) { return JSON.stringify(await response.json()) } else { return await response.text() } }
然后一个 Serverless 的 G2WW 就这么做好了,非常简单是不是?
我们把代码推到 Cloudflare 上:
wrangler publish
然后配置一下 Grafana 的报警 Channel 并看看效果如何.
配置个地址,图片中使用了
https://g2ww.nova.moe/<key>
,其实可以使用https://g2ww-serverless.nova.moe/<key>
,然后点一下 「Send Test」。一个可用的成品 Demo + 使用方法: https://g2ww-serverless.nova.moe
当然,这个只是一个 Hack,最终还是希望 Grafana 可以直接原生支持企业微信,我已经给他们提了一个 PR Add a new notifier : WeChat Work(企業微信) #26109,希望他们能快点合并吧。
尾声
以上通过一个简单的例子大致走了一遍 Cloudflare Workers 的基本用法,当然,Cloudflare Workers 的玩法绝对不止这么点,尤其是在 KV 的加持下,一定会有更多的有意思的项目兴起,比如他们的「Built with Workers」就有很多的优秀的项目,从各个角度发觉 Cloudflare Workers 的用法。
想到之前在某论坛上看到的一句话:
想法太多,我们却很少停下来看看真正的需求是什么,并对自己的技能进行提升和学习,一直忙于低头走路,以至于越走越累了。
除了已有的项目以外,我们还能利用它来做点什么呢,我想这是一个值得思考的问题。
References
将博客部署到CF Workers Site
前几天Cloudflare将Workers KV增加了免费额度,还不得搞起来?
利用Workers KV存储网页内容,通过Workers将内容返回给用户,就等于将自己的网站直接部署到CF成千上万的边缘节点当中,全球访问速度和TTFB都应该不错
https://blog.cloudflare.com/workers-sites/
安装Wrangler
Wrangler有两种安装方式,通过NPM或者Cargo安装都可以,任选其一即可
准备好NodeJS和NPM环境,然后执行下面命令,NPM方式是下载预编译好的二进制程序,安装速度比较快.
npm i @cloudflare/wrangler -g
或者准备好Rust环境,然后执行下面命令,Cargo方式是在本机从源码编译,安装速度比较慢:
cargo install wrangler
使用系统OpenSSL库,生成的二进制会小一些:
cargo install wrangler --features sys-openssl
部署
我自己博客使用的是Hugo,下面所有内容都是按照Hugo的方式来,其他静态站点生成器方法类似.
登录:
wrangler login
# 手动设置token
wrangler config
初始化
进入自己站点的目录,执行下面命令进行初始化。这里Wrangler会自动安装cargo-generate工具,在本目录下创建一个workers-site
项目目录,然后生成一个wrangler.toml
配置文件.
wrangler init --site
打开wrangler.toml
文件,按照自己的信息进行修改.
account_id
和zone_id
都可以从Cloudflare官网上找到,route
是路由到Workers的规则,这里写你需要绑定的域名,不要忘记后面的/*
bucket
是网站的目录,因为我用的是Hugo,所以这个目录默认是public.
entry-point
是部署到Workers的js代码目录,这里不需要修改,因为刚刚初始化的时候生成的项目目录名已经自动填写上了.
name = "blog"
type = "webpack"
account_id = "eu5d123456789987456321aabcddgeh"
workers_dev = true
route = "mydomain.com/*"
zone_id = "fhidag8u98f43h93fhiohr929c8d59efhauh"
[site]
bucket = "public"
entry-point = "workers-site"
预览:
wrangler preview --watch
发布
在Cloudflare中增加一条DNS记录,需要打开CF代理:
执行下面命令进行部署:
wrangler publish
使用Github Actions持续集成
Cloudflare提供了官方的Wrangler GitHub Action,可以直接用Github Actions将博客内容部署到CF Workers Site.
添加认证信息
在github仓库设置一个secret,名字为CF_API_TOKEN
,值为Wrangler的token:
Workflows:
name: hugo
on:
push:
branches:
- master
jobs:
deploy:
runs-on: ubuntu-18.04
steps:
- uses: actions/checkout@v2
with:
submodules: true
fetch-depth: 0
- name: Setup Hugo
uses: peaceiris/actions-hugo@v2
with:
hugo-version: '0.78.2'
extended: true
- name: Build
run: hugo --minify
- name: Deploy
uses: peaceiris/actions-gh-pages@v3
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
publish_dir: ./public
- name: Publish
uses: cloudflare/wrangler-action@1.3.0
with:
apiToken: ${{ secrets.CF_API_TOKEN }}
No comments:
Post a Comment