概述
通过 Workers 在边缘运行 JavaScript、Rust、C 和 C++ 等。
1.安装
设置代理
set http_proxy=http://127.0.0.1:10809
set https_proxy=http://127.0.0.1:10809
测试是否成功(别用 ping):
curl https://www.google.com
后来项目生成不了,只能曲线救国,安装了个Rust
从本质上讲,Workers应用包含两部分:
- 一个事件监听器,用于监听
FetchEvents
和 - 一个事件处理程序,返回一个Response对象,该对象传递给事件的
.respondWith()
方法。
当在Cloudflare的一台边缘服务器上接收到一个与Workers脚本匹配的URL的请求时,它将请求传递给Workers运行时,该运行时又在运行脚本的隔离中发出“获取”事件。
When a request is received on one of Cloudflare’s edge servers for a URL matching a Workers script, it passes the request in to the Workers runtime, which in turn emits a “fetch” event in the isolate where the script is running.
- 事件监听器
FetchEvent
告知脚本,以侦听任何传入到您的Worker的请求。向事件处理程序传递了event
对象,该对象包括event.request
,Request
它是触发FetchEvent的HTTP请求的表示。 - 通过调用,
.respondWith()
我们可以拦截请求,以便发送回自定义响应(在这种情况下,为纯文本“ Hello worker!”)。
路由和过滤请求
既然我们已经对所有请求运行了非常基本的脚本,那么您通常希望做的下一件事是根据Worker脚本正在接收的请求生成动态响应。这通常称为路由或过滤。
选项1:手动过滤请求
~/my-worker/index.js
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
async function handleRequest(request) {
let response
if (request.method === "POST") {
response = await generate(request)
} else {
response = new Response("Expected POST", { status: 500 })
}
// ...
}}
通常基于以下条件过滤请求:
-
request.method
—例如GET
或POST
。 -
request.url
—例如,根据查询参数或路径名进行过滤。 -
request.headers
—根据特定的标头进行过滤。
查看对象的所有属性的Request
列表。
除标准请求属性外,Workers平台还使用一个cf
对象填充请求,该对象包含许多有用的属性,例如region
或timezone
。
选项2:使用模板在URL上进行路由
对于更复杂的路由,使用库可能会有所帮助。该工人路由器启动打开外部链接 模板提供了类似于ExpressJS的API,用于基于HTTP方法和路径来处理请求:
wrangler generate my-worker-with-router https://github.com/cloudflare/worker-template-router
本教程中使用此启动程序来构建Slack Bot。
5c.利用运行时API
本指南中概述的示例只是一个起点。有许多Workers运行时API可用于操纵请求和生成响应。例如,您可以使用HTMLRewriter API即时分析和转换HTML,使用Cache API从Cloudflare缓存中检索数据并将数据放入Cloudflare缓存中,从边缘计算自定义响应,将请求重定向到另一个服务,还有更多。
要获取灵感,请访问“与工人共建”打开外部链接 展示项目。
6.预览您的项目
准备预览代码时,请运行Wrangler的preview
命令:
wrangler preview --watch
此命令将构建您的项目,将其上传到唯一的URL,然后在浏览器中打开一个选项卡以进行查看。这使您可以快速测试在实际Workers运行时上运行的项目,还可以选择甚至与其他人共享该项目。
该--watch
标志告诉Wrangler注意您的Workers项目目录是否有更改,并将自动使用最新的URL实时更新预览选项卡。
关于建筑的注意事项
正在运行wrangler preview
和wrangler publish
都wrangler build
自动自动运行,但是build
单独运行以检查错误可能很有用。运行wrangler build
将为您的项目安装必要的依赖项,并将其编译以使其可以预览或部署。了解有关牧马人的更多信息。
7.配置项目以进行部署
为了在Cloudflare的全球云网络上发布Workers项目,您需要wrangler.toml
使用Account ID配置您的项目。
如果在免费的worker.dev子域上进行部署,就是这样。如果要部署到自己的域中,则还需要使用Zone ID配置项目。
7a.获取您的帐户ID(和区域ID)
worker.dev
对于worker.dev域,您只需要帐户ID。获得它的最简单方法是运行wrangler whoami
。
但是,您还可以通过以下步骤在Cloudflare仪表板中找到您的帐户ID:
- 登录到您的Cloudflare帐户打开外部链接然后选择工人。
- 在右侧,查找“帐户ID”,然后单击“输入”下面的“单击以复制”。
注册移民
对于您在Cloudflare上注册的域,您需要两个ID:
- 登录到您的Cloudflare帐户打开外部链接 并选择所需的域。
- 选择导航栏上的概述选项卡。
- 向下滚动,直到在右侧看到区域ID和*帐户ID。
- 单击单击以将输入复制到每个输入下方。
7b.配置项目
要配置您的项目,我们需要wrangler.toml
在生成的项目根目录的文件中填写一些缺少的字段。该文件包含Wrangler连接到Cloudflare Workers API并发布代码所需的信息。
现在,仅account_id
使用在运行wrangler whoami
或从仪表板中查找到的值填写该字段。
wrangler.toml
name = "my-worker"
account_id = "$yourAccountId"
我们还要配置type
to "webpack"
,以告知Wrangler使用Webpack打包项目以进行部署。(了解有关type
配置的更多信息。)
wrangler.toml
name = "my-worker"
account_id = "$yourAccountId"
type = "webpack"
最后,我们需要告诉Wrangler我们要将项目部署到哪里。
配置用于部署到worker.dev
将workers_dev
密钥wrangler.toml
设置true
为时,Wrangler会将您的项目发布到您的workers.dev
子域中。
wrangler.toml
name = "my-worker"
account_id = "$yourAccountId"
type = "webpack"
workers_dev = true
部署到worker.dev子域时,名称字段将用作已部署脚本的辅助子域,例如my-worker.my-subdomain.workers.dev
。
(可选)配置为部署到注册域
要将应用程序发布到您拥有的域(而不是worker.dev子域)上,可以将route
密钥添加到wrangler.toml
。
牧马人的环境功能允许我们为应用程序指定多个不同的部署目标。让我们添加一个production
环境,传入zone_id
和route
:
wrangler.toml
name = "my-worker"
account_id = "$yourAccountId"
type = "webpack"
workers_dev = true
[env.production]
# The ID of the domain to deploying to
zone_id = "$yourZoneId"
# The route pattern your Workers application will be served at
route = "example.com/*"
route
这里的关键是路由模式,例如可以包含通配符。
如果将路由配置为主机名,则需要向Cloudflare添加DNS记录,以确保可以从外部解析该主机名。如果您的工作人员是您的来源(响应直接来自工作人员),则应输入一个指向的占位符(虚拟)AAAA记录100::
,该记录是保留的IPv6丢弃前缀。打开外部链接。
8.发布您的项目
配置好我们的项目后,就该发布它了。我们配置它的方式有两个可以发布到的部署目标。
要部署到我们的worker.dev子域,我们可以运行:
wrangler publish
注意:首次推送到worker.dev项目时,DNS传播时最初可能会看到523错误。一分钟左右后,它应该可以工作了。
要部署到我们在设置的“生产”环境中wrangler.toml
,可以将--env
标志传递给命令:
wrangler publish --env production
有关环境的更多信息,请查看Wrangler文档。
您还可以配置GitHub存储库以在每次时自动部署git push
。您可以使用Workers GitHub操作来执行此操作打开外部链接,或者编写您自己的GitHub操作并手动配置必要的GitHub机密打开外部链接。
2.官方示例
返回HTML
直接从Worker脚本内部的HTML字符串传递HTML页面。
const html = `<!DOCTYPE html>
<body>
<h1>Hello World</h1>
<p>This markup was generated by a Cloudflare Worker.</p>
</body>`
async function handleRequest(request) {
return new Response(html, {
headers: {
"content-type": "text/html;charset=UTF-8",
},
})
}
addEventListener("fetch", event => {
return event.respondWith(handleRequest(event.request))
})
返回JSON
直接从Worker脚本返回JSON,可用于构建API和中间件。
addEventListener("fetch", event => {
const data = {
hello: "world"
}
const json = JSON.stringify(data, null, 2)
return event.respondWith(
new Response(json, {
headers: {
"content-type": "application/json;charset=UTF-8"
}
})
)
})
获取HTML
Send a request to a remote server, read HTML from the response, and serve that HTML.
向远程服务器发送请求,从响应中读取 HTML,并提供该 HTML。
/**
* Example someHost at url is set up to respond with HTML
* Replace url with the host you wish to send requests to
*/
const someHost = "https://examples.cloudflareworkers.com/demos"
const url = someHost + "/static/html"
/**
* gatherResponse awaits and returns a response body as a string.
* Use await gatherResponse(..) in an async function to get the response body
* @param {Response} response
*/
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 if (contentType.includes("application/text")) {
return await response.text()
}
else if (contentType.includes("text/html")) {
return await response.text()
}
else {
return await response.text()
}
}
async function handleRequest() {
const init = {
headers: {
"content-type": "text/html;charset=UTF-8",
},
}
const response = await fetch(url, init)
const results = await gatherResponse(response)
return new Response(results, init)
}
addEventListener("fetch", event => {
return event.respondWith(handleRequest())
})
获取JSON
Send a GET request and read in JSON from the response. Use to fetch external data.
发送一个 GET 请求并从响应中读取 JSON。用于获取外部数据。
/**
* 示例 someHost被设置为接受JSON请求。
* 用您希望发送请求的主机替换为url。
* @param {string} someHost 要发送请求的主机。
* @param {string} url 发送请求的URL。
*/
const someHost = "https://examples.cloudflareworkers.com/demos"
const url = someHost + "/static/json"
/**
* gatherResponse等待并返回一个字符串形式的响应体。
* 在异步函数中使用 await gatherResponse(...)来获取响应体。
* @param {Response}响应。
*/
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 if (contentType.includes("application/text")) {
return await response.text()
}
else if (contentType.includes("text/html")) {
return await response.text()
}
else {
return await response.text()
}
}
async function handleRequest() {
const init = {
headers: {
"content-type": "application/json;charset=UTF-8",
},
}
const response = await fetch(url, init)
const results = await gatherResponse(response)
return new Response(results, init)
}
addEventListener("fetch", event => {
return event.respondWith(handleRequest())
})
var requestURL = 'https://mdn.github.io/learning-area/javascript/oojs/json/superheroes.json';
var request = new XMLHttpRequest();
request.open('GET', requestURL);
request.responseType = 'json';
request.send();
request.onload = function() {
var superHeroes = request.response;
populateHeader(superHeroes);
showHeroes(superHeroes);
}
访问 Cloudflare 对象
Access custom Cloudflare properties and control how Cloudflare features are applied to every request.
访问自定义 Cloudflare 属性并控制如何将 Cloudflare 特性应用于每个请求。
addEventListener("fetch", event => {
const data =
event.request.cf !== undefined ?
event.request.cf :
{ error: "The `cf` object is not available inside the preview." }
return event.respondWith(
new Response(JSON.stringify(data, null, 2), {
headers: {
"content-type": "application/json;charset=UTF-8"
}
})
)
})
Redirect/重定向
将请求从一个 URL 重定向到另一个 URL,或从一组 URL 重定向到另一组 URL。
1.Redirect all requests to one URL 将所有请求重定向到一个 URL
const destinationURL = "https://example.com"
const statusCode = 301
async function handleRequest(request) {
return Response.redirect(destinationURL, statusCode)
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})
2.Redirect requests from one domain to another 将请求从一个域重定向到另一个域
const base = "https://example.com"
const statusCode = 301
async function handleRequest(request) {
const url = new URL(request.url)
const { pathname, search, hash } = url
const destinationURL = base + pathname + search + hash
return Response.redirect(destinationURL, statusCode)
}
addEventListener("fetch", async event => {
event.respondWith(handleRequest(event.request))
})
回复另一个网站
Respond to the Worker request with the response from another website (example.com in this example).
用另一个网站的响应来响应工人的请求(例如本例中的.com)。
addEventListener("fetch", event => {
return event.respondWith(
fetch("https://example.com")
)
})
A/B测试
Set up an A/B test by controlling what response is served based on cookies.
通过控制基于 cookie 提供的响应来设置 a/b 测试。
function handleRequest(request) {
const NAME = "experiment-0"
// The Responses below are placeholders. You can set up a custom path for each test (e.g. /control/somepath ).
const TEST_RESPONSE = new Response("Test group") // e.g. await fetch("/test/sompath", request)
const CONTROL_RESPONSE = new Response("Control group") // e.g. await fetch("/control/sompath", request)
// Determine which group this requester is in.
const cookie = request.headers.get("cookie")
if (cookie && cookie.includes(`${NAME}=control`)) {
return CONTROL_RESPONSE
}
else if (cookie && cookie.includes(`${NAME}=test`)) {
return TEST_RESPONSE
}
else {
// If there is no cookie, this is a new client. Choose a group and set the cookie.
const group = Math.random() < 0.5 ? "test" : "control" // 50/50 split
const response = group === "control" ? CONTROL_RESPONSE : TEST_RESPONSE
response.headers.append("Set-Cookie", `${NAME}=${group}; path=/`)
return response
}
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
修改headers
Change the headers sent in a request or returned in a response.
更改请求中发送的标头或响应中返回的标头。
async function handleRequest(request) {
// Make the headers mutable by re-constructing the Request.
request = new Request(request)
request.headers.set("x-my-header", "custom value")
const URL = "https://examples.cloudflareworkers.com/demos/static/html"
// URL is set up to respond with dummy HTML
let response = await fetch(URL, request)
// Make the headers mutable by re-constructing the Response.
response = new Response(response.body, response)
response.headers.set("x-my-header", "custom value")
return response
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
汇总requests
向两个网址发送两个GET请求,并将响应汇总为一个响应。
/**
* someHost is set up to return JSON responses
* Replace url1 and url2 with the hosts you wish to send requests to
* @param {string} url the URL to send the request to
*/
const someHost = "https://examples.cloudflareworkers.com/demos"
const url1 = someHost + "/requests/json"
const url2 = someHost + "/requests/json"
const type = "application/json;charset=UTF-8"
/**
* gatherResponse awaits and returns a response body as a string.
* Use await gatherResponse(..) in an async function to get the response body
* @param {Response} response
*/
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 if (contentType.includes("application/text")) {
return await response.text()
}
else if (contentType.includes("text/html")) {
return await response.text()
}
else {
return await response.text()
}
}
async function handleRequest() {
const init = {
headers: {
"content-type": type,
},
}
const responses = await Promise.all([fetch(url1, init), fetch(url2, init)])
const results = await Promise.all([
gatherResponse(responses[0]),
gatherResponse(responses[1]),
])
return new Response(results.join(), init)
}
addEventListener("fetch", event => {
return event.respondWith(handleRequest())
})
Auth with headers
Allow or deny a request based on a known pre-shared key in a header. This is not meant to replace the WebCrypto API.
允许或拒绝基于标头中已知的预共享密钥的请求。这并不意味着要取代 WebCrypto API。
/**
* @param {string} PRESHARED_AUTH_HEADER_KEY Custom header to check for key
* @param {string} PRESHARED_AUTH_HEADER_VALUE Hard coded key value
*/
const PRESHARED_AUTH_HEADER_KEY = "X-Custom-PSK"
const PRESHARED_AUTH_HEADER_VALUE = "mypresharedkey"
async function handleRequest(request) {
const psk = request.headers.get(PRESHARED_AUTH_HEADER_KEY)
if (psk === PRESHARED_AUTH_HEADER_VALUE) {
// Correct preshared header key supplied. Fetch request from origin.
return fetch(request)
}
// Incorrect key supplied. Reject the request.
return new Response("Sorry, you have supplied an invalid key.", {
status: 403,
})
}
addEventListener("fetch", event => {
event.respondWith(handleRequest(event.request))
})
3.
论正则的作用
4.
5.实战——做个反代
《Cloudflare Workers反代实战(上)》这个人的博客美化可以学一下,感觉还不错,还是个高中生
《Cloudflare 新玩法利用 Workers 反向代理》
https://github.com/xiaoyang-liu-cs/booster.js
const config = {
basic: {
upstream: 'https://en.wikipedia.org/',
mobileRedirect: 'https://en.m.wikipedia.org/',
},
firewall: {
blockedRegion: ['CN', 'KP', 'SY', 'PK', 'CU'],
blockedIPAddress: [],
scrapeShield: true,
},
routes: {
TW: 'https://zh.wikipedia.org/',
HK: 'https://zh.wikipedia.org/',
FR: 'https://fr.wikipedia.org/',
},
optimization: {
cacheEverything: false,
cacheTtl: 5,
mirage: true,
polish: 'off',
minify: {
javascript: true,
css: true,
html: true,
},
},
};
async function isMobile(userAgent) {
const agents = ['Android', 'iPhone', 'SymbianOS', 'Windows Phone', 'iPad', 'iPod'];
return agents.any((agent) => userAgent.indexOf(agent) > 0);
}
async function fetchAndApply(request) {
const region = request.headers.get('cf-ipcountry') || '';
const ipAddress = request.headers.get('cf-connecting-ip') || '';
const userAgent = request.headers.get('user-agent') || '';
if (region !== '' && config.firewall.blockedRegion.includes(region.toUpperCase())) {
return new Response(
'Access denied: booster.js is not available in your region.',
{
status: 403,
},
);
} if (ipAddress !== '' && config.firewall.blockedIPAddress.includes(ipAddress)) {
return new Response(
'Access denied: Your IP address is blocked by booster.js.',
{
status: 403,
},
);
}
const requestURL = new URL(request.url);
let upstreamURL = null;
if (userAgent && isMobile(userAgent) === true) {
upstreamURL = new URL(config.basic.mobileRedirect);
} else if (region && region.toUpperCase() in config.routes) {
upstreamURL = new URL(config.routes[region.toUpperCase()]);
} else {
upstreamURL = new URL(config.basic.upstream);
}
requestURL.protocol = upstreamURL.protocol;
requestURL.host = upstreamURL.host;
requestURL.pathname = upstreamURL.pathname + requestURL.pathname;
let newRequest;
if (request.method === 'GET' || request.method === 'HEAD') {
newRequest = new Request(requestURL, {
cf: {
cacheEverything: config.optimization.cacheEverything,
cacheTtl: config.optimization.cacheTtl,
mirage: config.optimization.mirage,
polish: config.optimization.polish,
minify: config.optimization.minify,
scrapeShield: config.firewall.scrapeShield,
},
method: request.method,
headers: request.headers,
});
} else {
const requestBody = await request.text();
newRequest = new Request(requestURL, {
cf: {
cacheEverything: config.optimization.cacheEverything,
cacheTtl: config.optimization.cacheTtl,
mirage: config.optimization.mirage,
polish: config.optimization.polish,
minify: config.optimization.minify,
scrapeShield: config.firewall.scrapeShield,
},
method: request.method,
headers: request.headers,
body: requestBody,
});
}
const fetchedResponse = await fetch(newRequest);
const modifiedResponseHeaders = new Headers(fetchedResponse.headers);
if (modifiedResponseHeaders.has('x-pjax-url')) {
const pjaxURL = new URL(modifiedResponseHeaders.get('x-pjax-url'));
pjaxURL.protocol = requestURL.protocol;
pjaxURL.host = requestURL.host;
pjaxURL.pathname = pjaxURL.path.replace(requestURL.pathname, '/');
modifiedResponseHeaders.set(
'x-pjax-url',
pjaxURL.href,
);
}
return new Response(
fetchedResponse.body,
{
headers: modifiedResponseHeaders,
status: fetchedResponse.status,
statusText: fetchedResponse.statusText,
},
);
}
// eslint-disable-next-line no-restricted-globals
addEventListener('fetch', (event) => {
event.respondWith(fetchAndApply(event.request));
});
- www.google.com 调用 www.gstatic.com 上的静态资源
- 部署 Workers-Proxy A 反向代理 www.gstatic.com
- 部署 Workers-Proxy B 反向代理 www.google.com
- 为 Workers-Proxy B 配置文本替换: