之前的文章:

  1. Cloudflare workers不完全指南"

首先来看看Cloudflare Workers最基础也最强大的能力之一:处理客户端请求。在请求到达源服务器之前,对客户端发起的HTTP请求进行全面的“增删改查”操作。这让我们可以在网络边缘(Edge)(不知道啥叫网络边缘也没关系,反正就是在后端之前的一个代理)处理流量的超能力,无需改动任何后端代码,就能实现许多非常复杂的功能。

操作客户端请求有啥用?

在传统的Web架构中,请求从客户端发出后,通常直接转发给后端服务器。如果想对请求做一些预处理,往往需要在网关层(如Nginx、APISIX)或后端业务代码中实现。但有了Workers,我们可以在Cloudflare遍布全球的节点上,用几行简单的JavaScript代码实现同样甚至更强大的功能。

常见的应用场景包括但不限于:

  • A/B测试:根据用户的查询参数,Cookie或IP,将请求转发到不同的后端服务,测试新功能,比如根据查询参数?env=test来转发请求。
  • 访问控制:根据请求来源、Header等信息,实现简单的防火墙或认证逻辑,比如没有Token HTTP头就直接拒绝。
  • 请求规范化:统一修改或添加某些HTTP头,确保后端服务接收到的请求格式一致,比如增加client-id的HTTP头
  • 动态重定向:根据用户设备类型,将其重定向到PC版或移动版网站,根据User-Agent来生成不同的重定向请求。

下面看看怎么具体操作。

读取请求信息

Workers通过fetch事件监听器接收请求,事件处理函数的第一个参数request就是一个标准的Request对象,包含了所有客户端请求信息,在此基础上还提供了cloudflare的扩展信息对象cf, 这个cf包括但不限于客户端所在城市,所在国家,经纬度等额外的信息,这样后端就不需要根据IP来查询对应的信息来。

// worker.js
export default {
  async fetch(request, env, ctx) {
    // 1. 获取请求方法、URL和HTTP协议版本
    const method = request.method; // "GET", "POST", etc.
    const url = new URL(request.url);
    const httpProtocol = request.cf.httpProtocol; // "HTTP/2", "HTTP/1.1", etc.

    console.log(`收到一个 ${method} 请求,来自 ${url.pathname}`);

    // 2. 读取请求头 (Headers)
    // request.headers 是一个 Headers 对象,可以通过 get() 方法获取特定头的值
    const userAgent = request.headers.get('User-Agent') || 'N/A';
    console.log(`客户端 User-Agent 是: ${userAgent}`);

    // 3. 读取请求体 (Body)
    // request.body 是一个 ReadableStream
    // 即便是不需要读取请求体的GET请求,这个属性也存在
    let requestBody = "请求体为空或未读取";
    if (method === 'POST') {
      // 使用 .json() .text() .arrayBuffer() 等方法来读取流
      // 注意:一个请求体只能被读取一次!
      try {
        const data = await request.json();
        requestBody = JSON.stringify(data, null, 2);
      } catch (e) {
        requestBody = "无法解析为JSON";
      }
    }
    console.log(`请求体内容: \n${requestBody}`);

    // 为了演示,我们直接返回读取到的信息
    return new Response(`请求信息:\n方法: ${method}\n路径: ${url.pathname}\nUser-Agent: ${userAgent}\n请求体: ${requestBody}`,
      {
        headers: { 'Content-Type': 'text/plain; charset=utf-8' },
      }
    );
  },
};

核心知识点

  • request.url是一个字符串,通常需要用new URL(request.url)来方便地解析其各个部分(如主机名hostname、路径pathname、查询参数searchParams)。
  • request.body是一个可读流(ReadableStream)。这意味着请求体数据是分块到达的,这种设计对于处理大文件上传等场景至关重要,可以有效降低内存消耗。一旦调用了request.json()request.text()等方法,流就会被消耗,不能再次读取。

修改请求

直接修改传入的request对象是不被允许的。正确的做法是,基于原始请求,创建一个新的Request对象,并在构造时传入我们想要修改的属性。

示例1:修改URL(请求重写)

假设我们想将所有访问/v1/api的请求,都无缝地转发到后端的/api/v2路径,而对客户端保持透明。

// worker.js
export default {
  async fetch(request, env, ctx) {
    const url = new URL(request.url);

    // 检查路径是否匹配
    if (url.pathname.startsWith('/v1/api')) {
      // 替换路径部分
      url.pathname = url.pathname.replace('/v1/api', '/api/v2');

      // 使用修改后的URL创建一个新请求,并将其发送到源站
      const newRequest = new Request(url.toString(), request);
      return fetch(newRequest);
    }

    // 对于其他请求,正常转发
    return fetch(request);
  },
};

示例2:修改、添加、删除请求头

假设我们需要:

  1. 为每个请求添加一个唯一的追踪ID X-Request-Id
  2. 强制指定发往源站的Host头。
  3. 删除客户端可能发送的X-Forwarded-For头,以使用Cloudflare提供的真实IP头。
// worker.js
export default {
  async fetch(request, env, ctx) {
    // 1. 克隆原始请求的头,得到一个可修改的 Headers 对象
    const newHeaders = new Headers(request.headers);

    // 2. 添加一个新的头
    newHeaders.set('X-Request-Id', crypto.randomUUID());

    // 3. 修改一个已有的头(如果存在)
    // 注意:修改Host头需要你的Cloudflare套餐支持
    newHeaders.set('Host', 'api.my-backend.com');

    // 4. 删除一个头
    newHeaders.delete('X-Forwarded-For');

    // 5. 创建一个新请求,并应用修改后的头
    // new Request(request) 是一种快捷方式,等同于 new Request(request.url, request)
    const newRequest = new Request(request, {
      headers: newHeaders,
    });

    // 将带有新头的请求发送到源站
    return fetch(newRequest);
  },
};

总结

workers作为代理自然能够增删改查请求,而跟其他代理不同点在于,这个代理是可编程的,还不要钱。无论是简单的信息获取,还是复杂的请求重写和Header操作,都可以通过标准的Request和Headers API轻松实现。这种在网络边缘处理请求的能力,提供了强大的灵活性。

在下一篇文章中,我们将研究怎么增删改查后端的响应。