Cloudflare-worker学习


概述

通过 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应用包含两部分:

  1. 一个事件监听器,用于监听FetchEvents
  2. 一个事件处理程序,返回一个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.

  1. 事件监听器FetchEvent告知脚本,以侦听任何传入到您的Worker的请求。向事件处理程序传递了event对象,该对象包括event.requestRequest它是触发FetchEvent的HTTP请求的表示。
  2. 通过调用,.respondWith()我们可以拦截请求,以便发送回自定义响应(在这种情况下,为纯文本“ Hello worker!”)。
    • FetchEvent处理器通常会在达到高潮的呼叫的方法.respondWith()与无论是ResponsePromise<Response>一个确定响应。
    • FetchEvent对象还提供了其他两种方法来处理意外的异常和返回响应后可能完成的操作。

路由和过滤请求

既然我们已经对所有请求运行了非常基本的脚本,那么您通常希望做的下一件事是根据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—例如GETPOST
  • request.url —例如,根据查询参数或路径名进行过滤。
  • request.headers —根据特定的标头进行过滤。

查看对象所有属性的Request列表。

除标准请求属性外,Workers平台还使用一个cf对象填充请求,该对象包含许多有用的属性,例如regiontimezone

选项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 APICloudflare缓存中检索数据并将数据放入Cloudflare缓存中,从边缘计算自定义响应,将请求重定向到另一个服务,还有更多。

要获取灵感,请访问“与工人共建”打开外部链接 展示项目。


6.预览您的项目

准备预览代码时,请运行Wrangler的preview命令:

wrangler preview --watch

此命令将构建您的项目,将其上传到唯一的URL,然后在浏览器中打开一个选项卡以进行查看。这使您可以快速测试在实际Workers运行时上运行的项目,还可以选择甚至与其他人共享该项目。

--watch标志告诉Wrangler注意您的Workers项目目录是否有更改,并将自动使用最新的URL实时更新预览选项卡。

关于建筑的注意事项

正在运行wrangler previewwrangler publishwrangler 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:

  1. 登录到您的Cloudflare帐户打开外部链接然后选择工人
  2. 在右侧,查找“帐户ID”,然后单击“输入”下面的“单击以复制”。

注册移民

对于您在Cloudflare上注册的域,您需要两个ID:

  1. 登录到您的Cloudflare帐户打开外部链接 并选择所需的域。
  2. 选择导航栏上的概述选项卡。
  3. 向下滚动,直到在右侧看到区域ID和*帐户ID
  4. 单击单击以将输入复制到每个输入下方。

7b.配置项目

要配置您的项目,我们需要wrangler.toml在生成的项目根目录的文件中填写一些缺少的字段。该文件包含Wrangler连接到Cloudflare Workers API并发布代码所需的信息。

现在,仅account_id使用在运行wrangler whoami或从仪表板中查找到的值填写该字段。

wrangler.toml
name = "my-worker"
account_id = "$yourAccountId"

我们还要配置typeto "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_idroute

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.

双引号,替换为逗号

论正则的作用

image-20201225171059641

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));
});
参考链接:

文章作者: 古客
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 古客 !
评论
  目录